diff --git a/src/stremio_app/app.rs b/src/stremio_app/app.rs index 220828c..796a922 100644 --- a/src/stremio_app/app.rs +++ b/src/stremio_app/app.rs @@ -7,9 +7,10 @@ use std::sync::Arc; use std::thread; use winapi::shared::windef::HWND__; use winapi::um::winuser::{ - 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, + GetForegroundWindow, GetSystemMetrics, GetWindowLongA, IsIconic, IsZoomed, SetWindowLongA, + SetWindowPos, GWL_EXSTYLE, GWL_STYLE, HWND_NOTOPMOST, HWND_TOPMOST, SM_CXSCREEN, SM_CYSCREEN, + SWP_FRAMECHANGED, SWP_NOMOVE, SWP_NOSIZE, WS_CAPTION, WS_EX_CLIENTEDGE, WS_EX_DLGMODALFRAME, + WS_EX_STATICEDGE, WS_EX_TOPMOST, WS_EX_WINDOWEDGE, WS_THICKFRAME, }; use crate::stremio_app::ipc::{RPCRequest, RPCResponse, RPCResponseData, RPCResponseDataTransport}; @@ -17,6 +18,7 @@ use crate::stremio_app::stremio_player::Player; use crate::stremio_app::stremio_wevbiew::WebView; use crate::stremio_app::systray::SystemTray; +// https://doc.qt.io/qt-5/qt.html#WindowState-enum bitflags! { struct WindowState: u8 { const MINIMIZED = 0x01; @@ -37,28 +39,20 @@ pub struct WindowStyle { 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 + let mut state: WindowState = WindowState::empty(); + if 0 != unsafe { IsIconic(hwnd) } { + state |= WindowState::MINIMIZED; + } + if 0 != unsafe { IsZoomed(hwnd) } { + state |= WindowState::MAXIMIZED; + } + if hwnd == unsafe { GetForegroundWindow() } { + state |= WindowState::ACTIVE + } + if self.full_screen { + state |= WindowState::FULL_SCREEN; + } + state.bits() as u32 } } @@ -71,10 +65,10 @@ 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(SELF, EVT_DATA)], OnInit: [Self::on_init], OnPaint: [Self::on_paint], OnMinMaxInfo: [Self::on_min_max(SELF, EVT_DATA)], OnWindowMaximize: [Self::on_maximize] )] + #[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::transmit_window_state_change], OnWindowMinimize: [Self::transmit_window_state_change] )] 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]) ] + #[nwg_events((tray, MousePressLeftUp): [Self::on_show_hide], (tray_exit, OnMenuItemSelected): [Self::on_quit_notice], (tray_show_hide, OnMenuItemSelected): [Self::on_show_hide], (tray_topmost, OnMenuItemSelected): [Self::on_toggle_topmost]) ] pub tray: SystemTray, #[nwg_partial(parent: window)] pub webview: WebView, @@ -101,6 +95,33 @@ impl MainWindow { const BG_COLOR: [u8; 3] = [27, 17, 38]; const MIN_WIDTH: i32 = 1000; const MIN_HEIGHT: i32 = 600; + fn transmit_window_full_screen_change(&self, prevent_close: bool) { + 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(), + prevent_close as u32, + saved_style.full_screen, + )) + .ok(); + } + fn transmit_window_state_change(&self) { + if let Some(hwnd) = self.window.handle.hwnd() { + 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 style = self.saved_window_style.borrow(); + let state = style.clone().get_window_state(hwnd); + web_tx_app.send(RPCResponse::state_change(state)).ok(); + } + } fn on_init(&self) { self.webview.endpoint.set(self.webui_url.clone()).ok(); let small_side = cmp::min(nwg::Monitor::width(), nwg::Monitor::height()) * 70 / 100; @@ -252,20 +273,35 @@ impl MainWindow { self.splash_frame.set_size(w, h); self.splash.set_size(s, s); self.splash.set_position(w as i32 / 2 - s as i32 / 2, 0); + } else { + self.transmit_window_state_change(); } } fn on_toggle_fullscreen_notice(&self) { if let Some(hwnd) = self.window.handle.hwnd() { let mut saved_style = self.saved_window_style.borrow_mut(); if saved_style.full_screen { + let topmost = if saved_style.ex_style as u32 & WS_EX_TOPMOST == WS_EX_TOPMOST { + HWND_TOPMOST + } else { + HWND_NOTOPMOST + }; unsafe { SetWindowLongA(hwnd, GWL_STYLE, saved_style.style); SetWindowLongA(hwnd, GWL_EXSTYLE, saved_style.ex_style); + SetWindowPos( + hwnd, + topmost, + saved_style.pos.0, + saved_style.pos.1, + saved_style.size.0 as i32, + saved_style.size.1 as i32, + SWP_FRAMECHANGED, + ); } - self.window - .set_position(saved_style.pos.0, saved_style.pos.1); - self.window.set_size(saved_style.size.0, saved_style.size.1); saved_style.full_screen = false; + self.tray.tray_topmost.set_enabled(true); + self.tray.tray_topmost.set_checked(topmost == HWND_TOPMOST); } else { saved_style.pos = self.window.position(); saved_style.size = self.window.size(); @@ -286,29 +322,21 @@ impl MainWindow { | WS_EX_CLIENTEDGE as i32 | WS_EX_STATICEDGE as i32), ); + SetWindowPos( + hwnd, + HWND_NOTOPMOST, + 0, + 0, + GetSystemMetrics(SM_CXSCREEN), + GetSystemMetrics(SM_CYSCREEN), + SWP_FRAMECHANGED, + ); } - self.window.set_position(0, 0); - self.window - .set_size(unsafe { GetSystemMetrics(SM_CXSCREEN) as u32 }, unsafe { - GetSystemMetrics(SM_CYSCREEN) as u32 - }); saved_style.full_screen = true; + self.tray.tray_topmost.set_enabled(false); } - let visibility = saved_style.clone().get_window_state(hwnd); - - 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(); - web_tx_app - .send(RPCResponse::visibility_change( - true, - visibility, - saved_style.full_screen, - )) - .ok(); } + self.transmit_window_full_screen_change(true); } fn on_quit_notice(&self) { nwg::stop_thread_dispatch(); @@ -316,40 +344,44 @@ impl MainWindow { fn on_hide_splash_notice(&self) { self.splash_frame.set_visible(false); } - fn on_maximize(&self) {} + fn on_toggle_topmost(&self) { + if let Some(hwnd) = self.window.handle.hwnd() { + let topmost = if unsafe { GetWindowLongA(hwnd, GWL_EXSTYLE) } as u32 & WS_EX_TOPMOST + == WS_EX_TOPMOST + { + HWND_NOTOPMOST + } else { + HWND_TOPMOST + }; + unsafe { + SetWindowPos( + hwnd, + topmost, + 0, + 0, + 0, + 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED, + ); + } + let mut saved_style = self.saved_window_style.borrow_mut(); + saved_style.ex_style = unsafe { GetWindowLongA(hwnd, GWL_EXSTYLE) }; + self.tray + .tray_topmost + .set_checked((saved_style.ex_style as u32 & WS_EX_TOPMOST) == WS_EX_TOPMOST); + } + } 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(); + self.transmit_window_state_change(); } 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(); + if let nwg::EventData::OnWindowClose(data) = data { + data.close(false); } + self.window.set_visible(false); + self.tray.tray_show_hide.set_checked(self.window.visible()); + self.transmit_window_full_screen_change(false); } } diff --git a/src/stremio_app/ipc.rs b/src/stremio_app/ipc.rs index 8305bce..808c6e8 100644 --- a/src/stremio_app/ipc.rs +++ b/src/stremio_app/ipc.rs @@ -1,8 +1,8 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{self, json}; use std::cell::RefCell; use std::sync::mpsc; use std::sync::{Arc, Mutex}; -use serde::{Deserialize, Serialize}; -use serde_json::{self, json}; pub type Channel = RefCell, Arc>>)>>; @@ -51,4 +51,16 @@ impl RPCResponse { }; serde_json::to_string(&resp).expect("Cannot build response") } + pub fn state_change(state: u32) -> String { + let resp = RPCResponse { + id: 1, + object: "transport".to_string(), + response_type: 1, + args: Some(json!(["win-state-changed" ,{ + "state": state, + }])), + ..Default::default() + }; + serde_json::to_string(&resp).expect("Cannot build response") + } } diff --git a/src/stremio_app/systray.rs b/src/stremio_app/systray.rs index e670ca9..4f4cdfb 100644 --- a/src/stremio_app/systray.rs +++ b/src/stremio_app/systray.rs @@ -8,7 +8,7 @@ pub struct SystemTray { #[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])] + #[nwg_events(OnContextMenu: [Self::show_menu])] pub tray: nwg::TrayNotification, #[nwg_control(popup: true)] pub tray_menu: nwg::Menu, @@ -25,4 +25,4 @@ impl SystemTray { let (x, y) = nwg::GlobalCursor::position(); self.tray_menu.popup(x, y); } -} \ No newline at end of file +}