From 381f6c4a8d9137b40801c7f2e9a48fc85fe0b508 Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Mon, 26 Jul 2021 18:20:37 +0300 Subject: [PATCH] Tray icon; win visibility WIP --- Cargo.lock | 1 + Cargo.toml | 5 +- src/main.rs | 2 + src/stremio_app/app.rs | 97 ++++++++++++++++++++++++++++++++++---- src/stremio_app/mod.rs | 3 ++ src/stremio_app/systray.rs | 28 +++++++++++ 6 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 src/stremio_app/systray.rs diff --git a/Cargo.lock b/Cargo.lock index 56439e4..653df64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -500,6 +500,7 @@ dependencies = [ name = "stremio-shell-ng" version = "0.1.0" dependencies = [ + "bitflags", "embed-resource", "mpv", "native-windows-derive", diff --git a/Cargo.toml b/Cargo.toml index 6594f91..1ab065f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2018" [dependencies] once_cell = "1.3.1" -native-windows-gui = { version = "1.0.4", features = ["high-dpi", "notice"] } +native-windows-gui = { version = "1.0.4", features = ["high-dpi", "notice", "tray-notification", "menu"] } native-windows-derive = "1.0.3" winapi = { version = "0.3.9", features = [ "libloaderapi", "handleapi", "wincon", "winuser" @@ -18,5 +18,6 @@ serde_json = "1.0" structopt = "0.3" open = "1" urlencoding = "2.1.0" +bitflags = "1.2.1" [build-dependencies] -embed-resource = "1.3" \ No newline at end of file +embed-resource = "1.3" diff --git a/src/main.rs b/src/main.rs index f6b61d4..a927311 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +#[macro_use] +extern crate bitflags; use native_windows_gui::{self as nwg, NativeUi}; use structopt::StructOpt; use winapi::um::wincon::GetConsoleWindow; diff --git a/src/stremio_app/app.rs b/src/stremio_app/app.rs index e007f47..220828c 100644 --- a/src/stremio_app/app.rs +++ b/src/stremio_app/app.rs @@ -5,17 +5,28 @@ use std::cell::RefCell; use std::cmp; use std::sync::Arc; use std::thread; +use winapi::shared::windef::HWND__; use winapi::um::winuser::{ - GetSystemMetrics, GetWindowLongA, SetWindowLongA, GWL_EXSTYLE, GWL_STYLE, SM_CXSCREEN, - SM_CYSCREEN, WS_CAPTION, WS_EX_CLIENTEDGE, WS_EX_DLGMODALFRAME, WS_EX_STATICEDGE, - WS_EX_WINDOWEDGE, WS_THICKFRAME, + GetActiveWindow, GetSystemMetrics, GetWindowLongA, IsIconic, IsZoomed, SetWindowLongA, + GWL_EXSTYLE, GWL_STYLE, SM_CXSCREEN, SM_CYSCREEN, WS_CAPTION, WS_EX_CLIENTEDGE, + WS_EX_DLGMODALFRAME, WS_EX_STATICEDGE, WS_EX_WINDOWEDGE, WS_THICKFRAME, }; use crate::stremio_app::ipc::{RPCRequest, RPCResponse, RPCResponseData, RPCResponseDataTransport}; use crate::stremio_app::stremio_player::Player; use crate::stremio_app::stremio_wevbiew::WebView; +use crate::stremio_app::systray::SystemTray; -#[derive(Default)] +bitflags! { + struct WindowState: u8 { + const MINIMIZED = 0x01; + const MAXIMIZED = 0x02; + const FULL_SCREEN = 0x04; + const ACTIVE = 0x08; + } +} + +#[derive(Default, Clone)] pub struct WindowStyle { pub full_screen: bool, pub pos: (i32, i32), @@ -23,6 +34,34 @@ pub struct WindowStyle { pub style: i32, pub ex_style: i32, } + +impl WindowStyle { + pub fn get_window_state(self, hwnd: *mut HWND__) -> u32 { + let mut visibility: WindowState = WindowState::empty(); + visibility |= if 0 != unsafe { IsIconic(hwnd) } { + WindowState::MINIMIZED + } else { + WindowState::empty() + }; + visibility |= if 0 != unsafe { IsZoomed(hwnd) } { + WindowState::MAXIMIZED + } else { + WindowState::empty() + }; + visibility |= if hwnd == unsafe { GetActiveWindow() } { + WindowState::ACTIVE + } else { + WindowState::empty() + }; + visibility |= if self.full_screen { + WindowState::FULL_SCREEN + } else { + WindowState::empty() + }; + visibility.bits() as u32 + } +} + #[derive(Default, NwgUi)] pub struct MainWindow { pub webui_url: String, @@ -32,9 +71,12 @@ pub struct MainWindow { #[nwg_resource(source_embed: Some(&data.embed), source_embed_str: Some("MAINICON"))] pub window_icon: nwg::Icon, #[nwg_control(icon: Some(&data.window_icon), title: "Stremio", flags: "MAIN_WINDOW|VISIBLE")] - #[nwg_events( OnWindowClose: [Self::on_quit], OnInit: [Self::on_init], OnPaint: [Self::on_paint], OnMinMaxInfo: [Self::on_min_max(SELF, EVT_DATA)] )] + #[nwg_events( OnWindowClose: [Self::on_quit(SELF, EVT_DATA)], OnInit: [Self::on_init], OnPaint: [Self::on_paint], OnMinMaxInfo: [Self::on_min_max(SELF, EVT_DATA)], OnWindowMaximize: [Self::on_maximize] )] pub window: nwg::Window, #[nwg_partial(parent: window)] + #[nwg_events((tray_exit, OnMenuItemSelected): [Self::on_quit_notice], (tray_show_hide, OnMenuItemSelected): [Self::on_show_hide]) ] + pub tray: SystemTray, + #[nwg_partial(parent: window)] pub webview: WebView, #[nwg_partial(parent: window)] pub player: Player, @@ -73,6 +115,8 @@ impl MainWindow { .set_size(dimensions.0 as u32, dimensions.1 as u32); self.window.set_position(x, y); + self.tray.tray_show_hide.set_checked(true); + let player_channel = self.player.channel.borrow(); let (player_tx, player_rx) = player_channel .as_ref() @@ -250,6 +294,8 @@ impl MainWindow { }); saved_style.full_screen = true; } + let visibility = saved_style.clone().get_window_state(hwnd); + let web_channel = self.webview.channel.borrow(); let (web_tx, _) = web_channel .as_ref() @@ -258,19 +304,52 @@ impl MainWindow { web_tx_app .send(RPCResponse::visibility_change( true, - 1, + visibility, saved_style.full_screen, )) .ok(); } } fn on_quit_notice(&self) { - self.on_quit(); + nwg::stop_thread_dispatch(); } fn on_hide_splash_notice(&self) { self.splash_frame.set_visible(false); } - fn on_quit(&self) { - nwg::stop_thread_dispatch(); + fn on_maximize(&self) {} + fn on_show_hide(&self) { + self.window.set_visible(!self.window.visible()); + self.tray.tray_show_hide.set_checked(self.window.visible()); + let web_channel = self.webview.channel.borrow(); + let (web_tx, _) = web_channel + .as_ref() + .expect("Cannont obtain communication channel for the Web UI"); + let web_tx_app = web_tx.clone(); + let saved_style = self.saved_window_style.borrow(); + web_tx_app + .send(RPCResponse::visibility_change( + self.window.visible(), + 1, + saved_style.full_screen, + )) + .ok(); + } + fn on_quit(&self, data: &nwg::EventData) { + if let Some(hwnd) = self.window.handle.hwnd() { + if let nwg::EventData::OnWindowClose(data) = data { + data.close(false); + } + self.window.set_visible(false); + let web_channel = self.webview.channel.borrow(); + let (web_tx, _) = web_channel + .as_ref() + .expect("Cannont obtain communication channel for the Web UI"); + let web_tx_app = web_tx.clone(); + let saved_style = self.saved_window_style.borrow(); + let visibility = saved_style.clone().get_window_state(hwnd); + web_tx_app + .send(RPCResponse::visibility_change(false, visibility, false)) + .ok(); + } } } diff --git a/src/stremio_app/mod.rs b/src/stremio_app/mod.rs index b4c70fa..4358b29 100644 --- a/src/stremio_app/mod.rs +++ b/src/stremio_app/mod.rs @@ -8,3 +8,6 @@ pub mod stremio_server; pub use stremio_server::StremioServer; pub mod ipc; pub use ipc::{Channel, RPCRequest, RPCResponse, RPCResponseData, RPCResponseDataTransport}; +pub mod systray; +pub use systray::SystemTray; + diff --git a/src/stremio_app/systray.rs b/src/stremio_app/systray.rs new file mode 100644 index 0000000..e670ca9 --- /dev/null +++ b/src/stremio_app/systray.rs @@ -0,0 +1,28 @@ +use native_windows_derive::NwgPartial; +use native_windows_gui as nwg; + +#[derive(Default, NwgPartial)] +pub struct SystemTray { + #[nwg_resource] + pub embed: nwg::EmbedResource, + #[nwg_resource(source_embed: Some(&data.embed), source_embed_str: Some("MAINICON"))] + pub tray_icon: nwg::Icon, + #[nwg_control(icon: Some(&data.tray_icon), tip: Some("Stremio"))] + #[nwg_events(MousePressLeftUp: [Self::show_menu], OnContextMenu: [Self::show_menu])] + pub tray: nwg::TrayNotification, + #[nwg_control(popup: true)] + pub tray_menu: nwg::Menu, + #[nwg_control(parent: tray_menu, text: "&Show Window")] + pub tray_show_hide: nwg::MenuItem, + #[nwg_control(parent: tray_menu, text: "Always on &Top")] + pub tray_topmost: nwg::MenuItem, + #[nwg_control(parent: tray_menu, text: "E&xit")] + pub tray_exit: nwg::MenuItem, +} + +impl SystemTray { + fn show_menu(&self) { + let (x, y) = nwg::GlobalCursor::position(); + self.tray_menu.popup(x, y); + } +} \ No newline at end of file