source code

This commit is contained in:
rustdesk
2021-03-29 15:59:14 +08:00
parent 002fce136c
commit d1013487e2
175 changed files with 35074 additions and 2 deletions

5
libs/systray-rs/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
/target/

View File

@@ -0,0 +1,32 @@
language: rust
rust:
- stable
- beta
- nightly
matrix:
allow_failures:
- rust: nightly
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- build-essential
- libgtk-3-dev
- libappindicator3-dev
- gcc-5
before_install: . ./ci/before_install.sh
script:
- RUST_BACKTRACE=1 PKG_CONFIG_PATH=$HOME/local/lib/pkgconfig LD_LIBRARY_PATH=$HOME/local/lib:$LD_LIBRARY_PATH cargo build --verbose
- RUST_BACKTRACE=1 PKG_CONFIG_PATH=$HOME/local/lib/pkgconfig LD_LIBRARY_PATH=$HOME/local/lib:$LD_LIBRARY_PATH cargo test --verbose
global_env:
secure: O40C4FadE2C8yApgCbQNYmeWQuytrhu4W3a2HKRvGgB39LP0ysMU2UKXQIyZqlZUS9mP9qi5HYN+GTt83aE3Ac0eAwRqq+9zMjC2qMaiZ1JBSfCJI5wiiIXP0HpbsxXipG2Z21aqupVfu0HjNP4RVkaZ7ONKAeLAieI06+7VHbMPw6mcJd4Drv8VTyKn89VvB4lxKexLcURfagoic3fzeFKaIIVBSqGHiXrURbpD5tffOnzc5YFWxeGKTVFl8WqQVrRk2gnl/39UhSsOHGuSExw5GSxh+OaNHTiAkvOaSQLa05Y5mkNlHAsMyqg1mW3mI2xuzCQaFFT5G5JF7uxvZsa4GfROaEG8r1CZvpWxG2NtpupXvIC25nN+QQeeMZv5PHaxlk9OkG0k+2+z1Tu0Yd05x/o3+52YFo3geVwDmI3zx4Zgg9u9nIwGhdtzqbKV2fQNnKbNWVQH6D5M1DlBMYyY25jpkehcazqUbLsJXJFIoMkXhdkjTIpZg4w+CQ617WCnoDhXh6+Iqkw+iBBJJugaf2D6qBpXNiLZNJwbv2M5fj8uDsDtsUvjg56qBw+g+TeHJDKjzpEId/zFrAe4lmuFjN4/SlDk3n5xjZ5eY4PGRp1K8DGgeBQI5gyvHR3H7lm4GE2NCEvNILYFjpANZsiWwDepb2/rHvYNiLK+jhc=
env:
- LLVM_VERSION=3.9 CLANG_VERSION=clang_3_9

View File

@@ -0,0 +1,33 @@
# 0.4.0 (2020-02-15)
## Features
- Brought up to Rust 2018 (thanks to https://github.com/udoprog)
## Bugfixes
- Updated libappindicator to compile on modern rust
# 0.3.0 (2018-04-28)
## Bugfixes
- Update gtk so linux version will run again
# 0.2.0 (2017-05-04)
## Features
- Add Linux Support
# 0.1.1 (2017-02-28)
## Bugfixes
- Some cleanup and CI work
# 0.1.0 (2017-02-22)
## Features
- Basic Win32 systray support

View File

@@ -0,0 +1,28 @@
[package]
name = "systray"
version = "0.4.1"
authors = ["Kyle Machulis <kyle@machul.is>"]
description = "Rust library for making minimal cross-platform systray GUIs"
license = "BSD-3-Clause"
homepage = "http://github.com/qdot/systray-rs"
repository = "https://github.com/qdot/systray-rs.git"
readme = "README.md"
keywords = ["gui"]
edition = "2018"
[dependencies]
log= "0.4"
[target.'cfg(target_os = "windows")'.dependencies]
winapi= { version = "0.3", features = ["shellapi", "libloaderapi", "errhandlingapi", "impl-default"] }
libc= "0.2"
[target.'cfg(target_os = "linux")'.dependencies]
gtk= "0.9"
glib= "0.10"
libappindicator= "0.5"
# [target.'cfg(target_os = "macos")'.dependencies]
# objc="*"
# cocoa="*"
# core-foundation="*"

View File

@@ -0,0 +1,28 @@
Copyright (c) 2016, Kyle Machulis
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the project nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

58
libs/systray-rs/README.md Normal file
View File

@@ -0,0 +1,58 @@
# systray-rs
[![Crates.io](https://img.shields.io/crates/v/systray)](https://crates.io/crates/systray) [![Crates.io](https://img.shields.io/crates/d/systray)](https://crates.io/crates/systray)
[![Build Status](https://travis-ci.org/qdot/systray-rs.svg?branch=master)](https://travis-ci.org/qdot/systray-rs) [![Build status](https://ci.appveyor.com/api/projects/status/lhqm3lucb5w5559b?svg=true)](https://ci.appveyor.com/project/qdot/systray-rs)
systray-rs is a Rust library that makes it easy for applications to
have minimal UI in a platform specific way. It wraps the platform
specific calls required to show an icon in the system tray, as well as
add menu entries.
systray-rs is heavily influenced by
[the systray library for the Go Language](https://github.com/getlantern/systray).
systray-rs currently supports:
- Linux GTK
- Win32
Cocoa core still needed!
# License
systray-rs includes some code
from [winapi-rs, by retep998](https://github.com/retep998/winapi-rs).
This code is covered under the MIT license. This code will be removed
once winapi-rs has a 0.3 crate available.
systray-rs is BSD licensed.
Copyright (c) 2016-2020, Nonpolynomial Labs, LLC
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the project nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,22 @@
version: "0.1.0.{build}"
environment:
matrix:
- TARGET: nightly-x86_64-pc-windows-msvc
- TARGET: nightly-i686-pc-windows-msvc
- TARGET: nightly-x86_64-pc-windows-gnu
- TARGET: nightly-i686-pc-windows-gnu
install:
- ps: Start-FileDownload "https://static.rust-lang.org/dist/rust-${env:TARGET}.exe" -FileName "rust-install.exe"
- ps: .\rust-install.exe /VERYSILENT /NORESTART /DIR="C:\rust" | Out-Null
- ps: $env:PATH="$env:PATH;C:\rust\bin"
- rustc -vV
- cargo -vV
- erase rust-install.exe
build_script:
- cargo build
# Skip packaging step while we're running off a local winapi build
#- cargo package
skip_commits:
files:
- README.md
- .travis.yml

View File

@@ -0,0 +1,39 @@
set -e
pushd ~
# Workaround for Travis CI macOS bug (https://github.com/travis-ci/travis-ci/issues/6307)
if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
rvm get head || true
fi
function llvm_version_triple() {
if [ "$1" == "3.8" ]; then
echo "3.8.0"
elif [ "$1" == "3.9" ]; then
echo "3.9.0"
fi
}
function llvm_download() {
export LLVM_VERSION_TRIPLE=`llvm_version_triple ${LLVM_VERSION}`
export LLVM=clang+llvm-${LLVM_VERSION_TRIPLE}-x86_64-$1
wget http://llvm.org/releases/${LLVM_VERSION_TRIPLE}/${LLVM}.tar.xz
mkdir llvm
tar -xf ${LLVM}.tar.xz -C llvm --strip-components=1
export LLVM_CONFIG_PATH=`pwd`/llvm/bin/llvm-config
if [ "${TRAVIS_OS_NAME}" == "osx" ]; then
cp llvm/lib/libclang.dylib /usr/local/lib/libclang.dylib
fi
}
if [ "${TRAVIS_OS_NAME}" == "linux" ]; then
llvm_download linux-gnu-ubuntu-14.04
else
llvm_download apple-darwin
fi
popd
set +e

View File

@@ -0,0 +1,43 @@
#![windows_subsystem = "windows"]
//#[cfg(target_os = "windows")]
fn main() -> Result<(), systray::Error> {
let mut app;
match systray::Application::new() {
Ok(w) => app = w,
Err(_) => panic!("Can't create window!"),
}
// w.set_icon_from_file(&"C:\\Users\\qdot\\code\\git-projects\\systray-rs\\resources\\rust.ico".to_string());
// w.set_tooltip(&"Whatever".to_string());
app.set_icon_from_file("/usr/share/gxkb/flags/ua.png")?;
app.add_menu_item("Print a thing", |_| {
println!("Printing a thing!");
Ok::<_, systray::Error>(())
})?;
app.add_menu_item("Add Menu Item", |window| {
window.add_menu_item("Interior item", |_| {
println!("what");
Ok::<_, systray::Error>(())
})?;
window.add_menu_separator()?;
Ok::<_, systray::Error>(())
})?;
app.add_menu_separator()?;
app.add_menu_item("Quit", |window| {
window.quit();
Ok::<_, systray::Error>(())
})?;
println!("Waiting on message!");
app.wait_for_message()?;
Ok(())
}
// #[cfg(not(target_os = "windows"))]
// fn main() {
// panic!("Not implemented on this platform!");
// }

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -0,0 +1,28 @@
use crate::Error;
use std;
pub struct Window {}
impl Window {
pub fn new() -> Result<Window, Error> {
Err(Error::NotImplementedError)
}
pub fn quit(&self) {
unimplemented!()
}
pub fn set_tooltip(&self, _: &str) -> Result<(), Error> {
unimplemented!()
}
pub fn add_menu_item<F>(&self, _: &str, _: F) -> Result<u32, Error>
where
F: std::ops::Fn(&Window) -> () + 'static,
{
unimplemented!()
}
pub fn wait_for_message(&mut self) {
unimplemented!()
}
pub fn set_icon_from_buffer(&self, _: &[u8], _: u32, _: u32) -> Result<(), Error> {
unimplemented!()
}
}

View File

@@ -0,0 +1,182 @@
use crate::{Error, SystrayEvent};
use glib;
use gtk::{
self, MenuShellExt, GtkMenuItemExt, WidgetExt
};
use libappindicator::{AppIndicator, AppIndicatorStatus};
use std::{
self,
cell::RefCell,
collections::HashMap,
sync::mpsc::{channel, Sender},
thread,
};
// Gtk specific struct that will live only in the Gtk thread, since a lot of the
// base types involved don't implement Send (for good reason).
pub struct GtkSystrayApp {
menu: gtk::Menu,
ai: RefCell<AppIndicator>,
menu_items: RefCell<HashMap<u32, gtk::MenuItem>>,
event_tx: Sender<SystrayEvent>,
}
thread_local!(static GTK_STASH: RefCell<Option<GtkSystrayApp>> = RefCell::new(None));
pub struct MenuItemInfo {
mid: u32,
title: String,
tooltip: String,
disabled: bool,
checked: bool,
}
type Callback = Box<(Fn(&GtkSystrayApp) -> () + 'static)>;
// Convenience function to clean up thread local unwrapping
fn run_on_gtk_thread<F>(f: F)
where
F: std::ops::Fn(&GtkSystrayApp) -> () + Send + 'static,
{
// Note this is glib, not gtk. Calling gtk::idle_add will panic us due to
// being on different threads. glib::idle_add can run across threads.
glib::idle_add(move || {
GTK_STASH.with(|stash| {
let stash = stash.borrow();
let stash = stash.as_ref();
if let Some(stash) = stash {
f(stash);
}
});
gtk::prelude::Continue(false)
});
}
impl GtkSystrayApp {
pub fn new(event_tx: Sender<SystrayEvent>) -> Result<GtkSystrayApp, Error> {
if let Err(e) = gtk::init() {
return Err(Error::OsError(format!("{}", "Gtk init error!")));
}
let mut m = gtk::Menu::new();
let mut ai = AppIndicator::new("", "");
ai.set_status(AppIndicatorStatus::Active);
ai.set_menu(&mut m);
Ok(GtkSystrayApp {
menu: m,
ai: RefCell::new(ai),
menu_items: RefCell::new(HashMap::new()),
event_tx: event_tx,
})
}
pub fn systray_menu_selected(&self, menu_id: u32) {
self.event_tx
.send(SystrayEvent {
menu_index: menu_id as u32,
})
.ok();
}
pub fn add_menu_separator(&self, item_idx: u32) {
//let mut menu_items = self.menu_items.borrow_mut();
let m = gtk::SeparatorMenuItem::new();
self.menu.append(&m);
//menu_items.insert(item_idx, m);
self.menu.show_all();
}
pub fn add_menu_entry(&self, item_idx: u32, item_name: &str) {
let mut menu_items = self.menu_items.borrow_mut();
if menu_items.contains_key(&item_idx) {
let m: &gtk::MenuItem = menu_items.get(&item_idx).unwrap();
m.set_label(item_name);
self.menu.show_all();
return;
}
let m = gtk::MenuItem::new_with_label(item_name);
self.menu.append(&m);
m.connect_activate(move |_| {
run_on_gtk_thread(move |stash: &GtkSystrayApp| {
stash.systray_menu_selected(item_idx);
});
});
menu_items.insert(item_idx, m);
self.menu.show_all();
}
pub fn set_icon_from_file(&self, file: &str) {
let mut ai = self.ai.borrow_mut();
ai.set_icon_full(file, "icon");
}
}
pub struct Window {
gtk_loop: Option<thread::JoinHandle<()>>,
}
impl Window {
pub fn new(event_tx: Sender<SystrayEvent>) -> Result<Window, Error> {
let (tx, rx) = channel();
let gtk_loop = thread::spawn(move || {
GTK_STASH.with(|stash| match GtkSystrayApp::new(event_tx) {
Ok(data) => {
(*stash.borrow_mut()) = Some(data);
tx.send(Ok(()));
}
Err(e) => {
tx.send(Err(e));
return;
}
});
gtk::main();
});
match rx.recv().unwrap() {
Ok(()) => Ok(Window {
gtk_loop: Some(gtk_loop),
}),
Err(e) => Err(e),
}
}
pub fn add_menu_entry(&self, item_idx: u32, item_name: &str) -> Result<(), Error> {
let n = item_name.to_owned().clone();
run_on_gtk_thread(move |stash: &GtkSystrayApp| {
stash.add_menu_entry(item_idx, &n);
});
Ok(())
}
pub fn add_menu_separator(&self, item_idx: u32) -> Result<(), Error> {
run_on_gtk_thread(move |stash: &GtkSystrayApp| {
stash.add_menu_separator(item_idx);
});
Ok(())
}
pub fn set_icon_from_file(&self, file: &str) -> Result<(), Error> {
let n = file.to_owned().clone();
run_on_gtk_thread(move |stash: &GtkSystrayApp| {
stash.set_icon_from_file(&n);
});
Ok(())
}
pub fn set_icon_from_resource(&self, resource: &str) -> Result<(), Error> {
panic!("Not implemented on this platform!");
}
pub fn shutdown(&self) -> Result<(), Error> {
Ok(())
}
pub fn set_tooltip(&self, tooltip: &str) -> Result<(), Error> {
panic!("Not implemented on this platform!");
}
pub fn quit(&self) {
glib::idle_add(|| {
gtk::main_quit();
glib::Continue(false)
});
}
}

View File

@@ -0,0 +1,11 @@
#[cfg(target_os = "windows")]
#[path = "win32/mod.rs"]
pub mod api;
#[cfg(target_os = "linux")]
#[path = "linux/mod.rs"]
pub mod api;
#[cfg(target_os = "macos")]
#[path = "cocoa/mod.rs"]
pub mod api;

View File

@@ -0,0 +1,460 @@
use crate::{Error, SystrayEvent};
use std;
use std::cell::RefCell;
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use std::sync::mpsc::{channel, Sender};
use std::thread;
use winapi::{
ctypes::{c_ulong, c_ushort},
shared::{
basetsd::ULONG_PTR,
guiddef::GUID,
minwindef::{DWORD, HINSTANCE, LPARAM, LRESULT, PBYTE, TRUE, UINT, WPARAM},
ntdef::LPCWSTR,
windef::{HBITMAP, HBRUSH, HICON, HMENU, HWND, POINT},
},
um::{
errhandlingapi, libloaderapi,
shellapi::{
self, NIF_ICON, NIF_MESSAGE, NIF_TIP, NIM_ADD, NIM_DELETE, NIM_MODIFY, NOTIFYICONDATAW,
},
winuser::{
self, CW_USEDEFAULT, IMAGE_ICON, LR_DEFAULTCOLOR, LR_LOADFROMFILE, MENUINFO,
MENUITEMINFOW, MFT_SEPARATOR, MFT_STRING, MIIM_FTYPE, MIIM_ID, MIIM_STATE, MIIM_STRING,
MIM_APPLYTOSUBMENUS, MIM_STYLE, MNS_NOTIFYBYPOS, WM_DESTROY, WM_USER, WNDCLASSW,
WS_OVERLAPPEDWINDOW,
},
},
};
// Got this idea from glutin. Yay open source! Boo stupid winproc! Even more boo
// doing SetLongPtr tho.
thread_local!(static WININFO_STASH: RefCell<Option<WindowsLoopData>> = RefCell::new(None));
fn to_wstring(str: &str) -> Vec<u16> {
OsStr::new(str)
.encode_wide()
.chain(Some(0).into_iter())
.collect::<Vec<_>>()
}
#[derive(Clone)]
struct WindowInfo {
pub hwnd: HWND,
pub hinstance: HINSTANCE,
pub hmenu: HMENU,
}
unsafe impl Send for WindowInfo {}
unsafe impl Sync for WindowInfo {}
#[derive(Clone)]
struct WindowsLoopData {
pub info: WindowInfo,
pub tx: Sender<SystrayEvent>,
}
unsafe fn get_win_os_error(msg: &str) -> Error {
Error::OsError(format!("{}: {}", &msg, errhandlingapi::GetLastError()))
}
unsafe extern "system" fn window_proc(
h_wnd: HWND,
msg: UINT,
w_param: WPARAM,
l_param: LPARAM,
) -> LRESULT {
if msg == winuser::WM_MENUCOMMAND {
WININFO_STASH.with(|stash| {
let stash = stash.borrow();
let stash = stash.as_ref();
if let Some(stash) = stash {
let menu_id = winuser::GetMenuItemID(stash.info.hmenu, w_param as i32) as i32;
if menu_id != -1 {
stash
.tx
.send(SystrayEvent {
menu_index: menu_id as u32,
})
.ok();
}
}
});
}
if msg == WM_USER + 1 {
if l_param as UINT == winuser::WM_LBUTTONUP || l_param as UINT == winuser::WM_RBUTTONUP {
let mut p = POINT { x: 0, y: 0 };
if winuser::GetCursorPos(&mut p as *mut POINT) == 0 {
return 1;
}
winuser::SetForegroundWindow(h_wnd);
WININFO_STASH.with(|stash| {
let stash = stash.borrow();
let stash = stash.as_ref();
if let Some(stash) = stash {
winuser::TrackPopupMenu(
stash.info.hmenu,
0,
p.x,
p.y,
(winuser::TPM_BOTTOMALIGN | winuser::TPM_LEFTALIGN) as i32,
h_wnd,
std::ptr::null_mut(),
);
}
});
}
}
if msg == winuser::WM_DESTROY {
winuser::PostQuitMessage(0);
}
return winuser::DefWindowProcW(h_wnd, msg, w_param, l_param);
}
fn get_nid_struct(hwnd: &HWND) -> NOTIFYICONDATAW {
NOTIFYICONDATAW {
cbSize: std::mem::size_of::<NOTIFYICONDATAW>() as DWORD,
hWnd: *hwnd,
uID: 0x1 as UINT,
uFlags: 0 as UINT,
uCallbackMessage: 0 as UINT,
hIcon: 0 as HICON,
szTip: [0 as u16; 128],
dwState: 0 as DWORD,
dwStateMask: 0 as DWORD,
szInfo: [0 as u16; 256],
u: Default::default(),
szInfoTitle: [0 as u16; 64],
dwInfoFlags: 0 as UINT,
guidItem: GUID {
Data1: 0 as c_ulong,
Data2: 0 as c_ushort,
Data3: 0 as c_ushort,
Data4: [0; 8],
},
hBalloonIcon: 0 as HICON,
}
}
fn get_menu_item_struct() -> MENUITEMINFOW {
MENUITEMINFOW {
cbSize: std::mem::size_of::<MENUITEMINFOW>() as UINT,
fMask: 0 as UINT,
fType: 0 as UINT,
fState: 0 as UINT,
wID: 0 as UINT,
hSubMenu: 0 as HMENU,
hbmpChecked: 0 as HBITMAP,
hbmpUnchecked: 0 as HBITMAP,
dwItemData: 0 as ULONG_PTR,
dwTypeData: std::ptr::null_mut(),
cch: 0 as u32,
hbmpItem: 0 as HBITMAP,
}
}
unsafe fn init_window() -> Result<WindowInfo, Error> {
let class_name = to_wstring("my_window");
let hinstance: HINSTANCE = libloaderapi::GetModuleHandleA(std::ptr::null_mut());
let wnd = WNDCLASSW {
style: 0,
lpfnWndProc: Some(window_proc),
cbClsExtra: 0,
cbWndExtra: 0,
hInstance: 0 as HINSTANCE,
hIcon: winuser::LoadIconW(0 as HINSTANCE, winuser::IDI_APPLICATION),
hCursor: winuser::LoadCursorW(0 as HINSTANCE, winuser::IDI_APPLICATION),
hbrBackground: 16 as HBRUSH,
lpszMenuName: 0 as LPCWSTR,
lpszClassName: class_name.as_ptr(),
};
if winuser::RegisterClassW(&wnd) == 0 {
return Err(get_win_os_error("Error creating window class"));
}
let hwnd = winuser::CreateWindowExW(
0,
class_name.as_ptr(),
to_wstring("rust_systray_window").as_ptr(),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
0,
CW_USEDEFAULT,
0,
0 as HWND,
0 as HMENU,
0 as HINSTANCE,
std::ptr::null_mut(),
);
if hwnd == std::ptr::null_mut() {
return Err(get_win_os_error("Error creating window"));
}
let mut nid = get_nid_struct(&hwnd);
nid.uID = 0x1;
nid.uFlags = NIF_MESSAGE;
nid.uCallbackMessage = WM_USER + 1;
if shellapi::Shell_NotifyIconW(NIM_ADD, &mut nid as *mut NOTIFYICONDATAW) == 0 {
return Err(get_win_os_error("Error adding menu icon"));
}
// Setup menu
let hmenu = winuser::CreatePopupMenu();
let m = MENUINFO {
cbSize: std::mem::size_of::<MENUINFO>() as DWORD,
fMask: MIM_APPLYTOSUBMENUS | MIM_STYLE,
dwStyle: MNS_NOTIFYBYPOS,
cyMax: 0 as UINT,
hbrBack: 0 as HBRUSH,
dwContextHelpID: 0 as DWORD,
dwMenuData: 0 as ULONG_PTR,
};
if winuser::SetMenuInfo(hmenu, &m as *const MENUINFO) == 0 {
return Err(get_win_os_error("Error setting up menu"));
}
Ok(WindowInfo {
hwnd: hwnd,
hmenu: hmenu,
hinstance: hinstance,
})
}
unsafe fn run_loop() {
log::debug!("Running windows loop");
// Run message loop
let mut msg = winuser::MSG {
hwnd: 0 as HWND,
message: 0 as UINT,
wParam: 0 as WPARAM,
lParam: 0 as LPARAM,
time: 0 as DWORD,
pt: POINT { x: 0, y: 0 },
};
loop {
winuser::GetMessageW(&mut msg, 0 as HWND, 0, 0);
if msg.message == winuser::WM_QUIT {
break;
}
winuser::TranslateMessage(&mut msg);
winuser::DispatchMessageW(&mut msg);
}
log::debug!("Leaving windows run loop");
}
pub struct Window {
info: WindowInfo,
windows_loop: Option<thread::JoinHandle<()>>,
}
impl Window {
pub fn new(event_tx: Sender<SystrayEvent>) -> Result<Window, Error> {
let (tx, rx) = channel();
let windows_loop = thread::spawn(move || {
unsafe {
let i = init_window();
let k;
match i {
Ok(j) => {
tx.send(Ok(j.clone())).ok();
k = j;
}
Err(e) => {
// If creation didn't work, return out of the thread.
tx.send(Err(e)).ok();
return;
}
};
WININFO_STASH.with(|stash| {
let data = WindowsLoopData {
info: k,
tx: event_tx,
};
(*stash.borrow_mut()) = Some(data);
});
run_loop();
}
});
let info = match rx.recv().unwrap() {
Ok(i) => i,
Err(e) => {
return Err(e);
}
};
let w = Window {
info: info,
windows_loop: Some(windows_loop),
};
Ok(w)
}
pub fn quit(&mut self) {
unsafe {
winuser::PostMessageW(self.info.hwnd, WM_DESTROY, 0 as WPARAM, 0 as LPARAM);
}
if let Some(t) = self.windows_loop.take() {
t.join().ok();
}
}
pub fn set_tooltip(&self, tooltip: &str) -> Result<(), Error> {
// Add Tooltip
log::debug!("Setting tooltip to {}", tooltip);
// Gross way to convert String to [i8; 128]
// TODO: Clean up conversion, test for length so we don't panic at runtime
let tt = tooltip.as_bytes().clone();
let mut nid = get_nid_struct(&self.info.hwnd);
for i in 0..tt.len() {
nid.szTip[i] = tt[i] as u16;
}
nid.uFlags = NIF_TIP;
unsafe {
if shellapi::Shell_NotifyIconW(NIM_MODIFY, &mut nid as *mut NOTIFYICONDATAW) == 0 {
return Err(get_win_os_error("Error setting tooltip"));
}
}
Ok(())
}
pub fn add_menu_entry(&self, item_idx: u32, item_name: &str) -> Result<(), Error> {
let mut st = to_wstring(item_name);
let mut item = get_menu_item_struct();
item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_ID | MIIM_STATE;
item.fType = MFT_STRING;
item.wID = item_idx;
item.dwTypeData = st.as_mut_ptr();
item.cch = (item_name.len() * 2) as u32;
unsafe {
if winuser::InsertMenuItemW(self.info.hmenu, item_idx, 1, &item as *const MENUITEMINFOW)
== 0
{
return Err(get_win_os_error("Error inserting menu item"));
}
}
Ok(())
}
pub fn remove_menu_entry(&self, item_idx: u32) {
unsafe { winuser::RemoveMenu(self.info.hmenu, item_idx, 1 as _); }
}
pub fn add_menu_separator(&self, item_idx: u32) -> Result<(), Error> {
let mut item = get_menu_item_struct();
item.fMask = MIIM_FTYPE;
item.fType = MFT_SEPARATOR;
item.wID = item_idx;
unsafe {
if winuser::InsertMenuItemW(self.info.hmenu, item_idx, 1, &item as *const MENUITEMINFOW)
== 0
{
return Err(get_win_os_error("Error inserting separator"));
}
}
Ok(())
}
fn set_icon(&self, icon: HICON) -> Result<(), Error> {
unsafe {
let mut nid = get_nid_struct(&self.info.hwnd);
nid.uFlags = NIF_ICON;
nid.hIcon = icon;
if shellapi::Shell_NotifyIconW(NIM_MODIFY, &mut nid as *mut NOTIFYICONDATAW) == 0 {
return Err(get_win_os_error("Error setting icon"));
}
}
Ok(())
}
pub fn set_icon_from_resource(&self, resource_name: &str) -> Result<(), Error> {
let icon;
unsafe {
icon = winuser::LoadImageW(
self.info.hinstance,
to_wstring(&resource_name).as_ptr(),
IMAGE_ICON,
64,
64,
0,
) as HICON;
if icon == std::ptr::null_mut() as HICON {
return Err(get_win_os_error("Error setting icon from resource"));
}
}
self.set_icon(icon)
}
pub fn set_icon_from_file(&self, icon_file: &str) -> Result<(), Error> {
let wstr_icon_file = to_wstring(&icon_file);
let hicon;
unsafe {
hicon = winuser::LoadImageW(
std::ptr::null_mut() as HINSTANCE,
wstr_icon_file.as_ptr(),
IMAGE_ICON,
64,
64,
LR_LOADFROMFILE,
) as HICON;
if hicon == std::ptr::null_mut() as HICON {
return Err(get_win_os_error("Error setting icon from file"));
}
}
self.set_icon(hicon)
}
pub fn set_icon_from_buffer(
&self,
buffer: &[u8],
width: u32,
height: u32,
) -> Result<(), Error> {
let offset = unsafe {
winuser::LookupIconIdFromDirectoryEx(
buffer.as_ptr() as PBYTE,
TRUE,
width as i32,
height as i32,
LR_DEFAULTCOLOR,
)
};
if offset != 0 {
let icon_data = &buffer[offset as usize..];
let hicon = unsafe {
winuser::CreateIconFromResourceEx(
icon_data.as_ptr() as PBYTE,
icon_data.len() as u32,
TRUE,
0x30000,
width as i32,
height as i32,
LR_DEFAULTCOLOR,
)
};
if hicon == std::ptr::null_mut() as HICON {
return Err(unsafe { get_win_os_error("Cannot load icon from the buffer") });
}
self.set_icon(hicon)
} else {
Err(unsafe { get_win_os_error("Error setting icon from buffer") })
}
}
pub fn shutdown(&self) -> Result<(), Error> {
unsafe {
let mut nid = get_nid_struct(&self.info.hwnd);
nid.uFlags = NIF_ICON;
if shellapi::Shell_NotifyIconW(NIM_DELETE, &mut nid as *mut NOTIFYICONDATAW) == 0 {
return Err(get_win_os_error("Error deleting icon from menu"));
}
}
Ok(())
}
}
impl Drop for Window {
fn drop(&mut self) {
self.shutdown().ok();
}
}

192
libs/systray-rs/src/lib.rs Normal file
View File

@@ -0,0 +1,192 @@
// Systray Lib
pub mod api;
use std::{
collections::HashMap,
error, fmt,
sync::mpsc::{channel, Receiver},
};
type BoxedError = Box<dyn error::Error + Send + Sync + 'static>;
#[derive(Debug)]
pub enum Error {
OsError(String),
NotImplementedError,
UnknownError,
Error(BoxedError),
}
impl From<BoxedError> for Error {
fn from(value: BoxedError) -> Self {
Error::Error(value)
}
}
pub struct SystrayEvent {
menu_index: u32,
}
impl error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
use self::Error::*;
match *self {
OsError(ref err_str) => write!(f, "OsError: {}", err_str),
NotImplementedError => write!(f, "Functionality is not implemented yet"),
UnknownError => write!(f, "Unknown error occurrred"),
Error(ref e) => write!(f, "Error: {}", e),
}
}
}
pub struct Application {
window: api::api::Window,
menu_idx: u32,
callback: HashMap<u32, Callback>,
// Each platform-specific window module will set up its own thread for
// dealing with the OS main loop. Use this channel for receiving events from
// that thread.
rx: Receiver<SystrayEvent>,
timer: Option<(std::time::Duration, Callback)>,
}
type Callback =
Box<(dyn FnMut(&mut Application) -> Result<(), BoxedError> + Send + Sync + 'static)>;
fn make_callback<F, E>(mut f: F) -> Callback
where
F: FnMut(&mut Application) -> Result<(), E> + Send + Sync + 'static,
E: error::Error + Send + Sync + 'static,
{
Box::new(move |a: &mut Application| match f(a) {
Ok(()) => Ok(()),
Err(e) => Err(Box::new(e) as BoxedError),
}) as Callback
}
impl Application {
pub fn new() -> Result<Application, Error> {
let (event_tx, event_rx) = channel();
match api::api::Window::new(event_tx) {
Ok(w) => Ok(Application {
window: w,
menu_idx: 0,
callback: HashMap::new(),
rx: event_rx,
timer: None,
}),
Err(e) => Err(e),
}
}
pub fn set_timer<F, E>(
&mut self,
interval: std::time::Duration,
callback: F,
) -> Result<(), Error>
where
F: FnMut(&mut Application) -> Result<(), E> + Send + Sync + 'static,
E: error::Error + Send + Sync + 'static,
{
self.timer = Some((interval, make_callback(callback)));
Ok(())
}
pub fn add_menu_item<F, E>(&mut self, item_name: &str, f: F) -> Result<u32, Error>
where
F: FnMut(&mut Application) -> Result<(), E> + Send + Sync + 'static,
E: error::Error + Send + Sync + 'static,
{
let idx = self.menu_idx;
if let Err(e) = self.window.add_menu_entry(idx, item_name) {
return Err(e);
}
self.callback.insert(idx, make_callback(f));
self.menu_idx += 1;
Ok(idx)
}
pub fn remove_menu_item(&mut self, pos: u32) {
self.window.remove_menu_entry(pos);
self.callback.remove(&pos);
}
pub fn add_menu_separator(&mut self) -> Result<u32, Error> {
let idx = self.menu_idx;
if let Err(e) = self.window.add_menu_separator(idx) {
return Err(e);
}
self.menu_idx += 1;
Ok(idx)
}
pub fn set_icon_from_file(&self, file: &str) -> Result<(), Error> {
self.window.set_icon_from_file(file)
}
pub fn set_icon_from_resource(&self, resource: &str) -> Result<(), Error> {
self.window.set_icon_from_resource(resource)
}
#[cfg(target_os = "windows")]
pub fn set_icon_from_buffer(
&self,
buffer: &[u8],
width: u32,
height: u32,
) -> Result<(), Error> {
self.window.set_icon_from_buffer(buffer, width, height)
}
pub fn shutdown(&self) -> Result<(), Error> {
self.window.shutdown()
}
pub fn set_tooltip(&self, tooltip: &str) -> Result<(), Error> {
self.window.set_tooltip(tooltip)
}
pub fn quit(&mut self) {
self.window.quit()
}
pub fn wait_for_message(&mut self) -> Result<(), Error> {
loop {
let mut msg = None;
if let Some((interval, _)) = self.timer.as_ref() {
match self.rx.recv_timeout(interval.clone()) {
Ok(m) => msg = Some(m),
Err(_) => {}
}
} else {
match self.rx.recv() {
Ok(m) => msg = Some(m),
Err(_) => {
self.quit();
break;
}
}
}
if let Some(msg) = msg {
if let Some(mut f) = self.callback.remove(&msg.menu_index) {
f(self)?;
self.callback.insert(msg.menu_index, f);
}
} else if let Some((interval, mut callback)) = self.timer.take() {
callback(self)?;
self.timer = Some((interval, callback));
}
}
Ok(())
}
}
impl Drop for Application {
fn drop(&mut self) {
self.shutdown().ok();
}
}