mirror of
https://github.com/weyne85/rustdesk.git
synced 2025-10-29 17:00:05 +00:00
source code
This commit is contained in:
28
libs/systray-rs/src/api/cocoa/mod.rs
Normal file
28
libs/systray-rs/src/api/cocoa/mod.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
182
libs/systray-rs/src/api/linux/mod.rs
Normal file
182
libs/systray-rs/src/api/linux/mod.rs
Normal 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: >k::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)
|
||||
});
|
||||
}
|
||||
}
|
||||
11
libs/systray-rs/src/api/mod.rs
Normal file
11
libs/systray-rs/src/api/mod.rs
Normal 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;
|
||||
460
libs/systray-rs/src/api/win32/mod.rs
Normal file
460
libs/systray-rs/src/api/win32/mod.rs
Normal 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
192
libs/systray-rs/src/lib.rs
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user