mirror of
https://github.com/Stremio/stremio-shell-ng.git
synced 2026-04-21 16:01:56 +00:00
Better window events; pause on minimize; always on top
This commit is contained in:
parent
29817c614d
commit
e4a262c6d9
3 changed files with 126 additions and 82 deletions
|
|
@ -7,9 +7,10 @@ use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use winapi::shared::windef::HWND__;
|
use winapi::shared::windef::HWND__;
|
||||||
use winapi::um::winuser::{
|
use winapi::um::winuser::{
|
||||||
GetActiveWindow, GetSystemMetrics, GetWindowLongA, IsIconic, IsZoomed, SetWindowLongA,
|
GetForegroundWindow, GetSystemMetrics, GetWindowLongA, IsIconic, IsZoomed, SetWindowLongA,
|
||||||
GWL_EXSTYLE, GWL_STYLE, SM_CXSCREEN, SM_CYSCREEN, WS_CAPTION, WS_EX_CLIENTEDGE,
|
SetWindowPos, GWL_EXSTYLE, GWL_STYLE, HWND_NOTOPMOST, HWND_TOPMOST, SM_CXSCREEN, SM_CYSCREEN,
|
||||||
WS_EX_DLGMODALFRAME, WS_EX_STATICEDGE, WS_EX_WINDOWEDGE, WS_THICKFRAME,
|
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};
|
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::stremio_wevbiew::WebView;
|
||||||
use crate::stremio_app::systray::SystemTray;
|
use crate::stremio_app::systray::SystemTray;
|
||||||
|
|
||||||
|
// https://doc.qt.io/qt-5/qt.html#WindowState-enum
|
||||||
bitflags! {
|
bitflags! {
|
||||||
struct WindowState: u8 {
|
struct WindowState: u8 {
|
||||||
const MINIMIZED = 0x01;
|
const MINIMIZED = 0x01;
|
||||||
|
|
@ -37,28 +39,20 @@ pub struct WindowStyle {
|
||||||
|
|
||||||
impl WindowStyle {
|
impl WindowStyle {
|
||||||
pub fn get_window_state(self, hwnd: *mut HWND__) -> u32 {
|
pub fn get_window_state(self, hwnd: *mut HWND__) -> u32 {
|
||||||
let mut visibility: WindowState = WindowState::empty();
|
let mut state: WindowState = WindowState::empty();
|
||||||
visibility |= if 0 != unsafe { IsIconic(hwnd) } {
|
if 0 != unsafe { IsIconic(hwnd) } {
|
||||||
WindowState::MINIMIZED
|
state |= WindowState::MINIMIZED;
|
||||||
} else {
|
}
|
||||||
WindowState::empty()
|
if 0 != unsafe { IsZoomed(hwnd) } {
|
||||||
};
|
state |= WindowState::MAXIMIZED;
|
||||||
visibility |= if 0 != unsafe { IsZoomed(hwnd) } {
|
}
|
||||||
WindowState::MAXIMIZED
|
if hwnd == unsafe { GetForegroundWindow() } {
|
||||||
} else {
|
state |= WindowState::ACTIVE
|
||||||
WindowState::empty()
|
}
|
||||||
};
|
if self.full_screen {
|
||||||
visibility |= if hwnd == unsafe { GetActiveWindow() } {
|
state |= WindowState::FULL_SCREEN;
|
||||||
WindowState::ACTIVE
|
}
|
||||||
} else {
|
state.bits() as u32
|
||||||
WindowState::empty()
|
|
||||||
};
|
|
||||||
visibility |= if self.full_screen {
|
|
||||||
WindowState::FULL_SCREEN
|
|
||||||
} else {
|
|
||||||
WindowState::empty()
|
|
||||||
};
|
|
||||||
visibility.bits() as u32
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,10 +65,10 @@ pub struct MainWindow {
|
||||||
#[nwg_resource(source_embed: Some(&data.embed), source_embed_str: Some("MAINICON"))]
|
#[nwg_resource(source_embed: Some(&data.embed), source_embed_str: Some("MAINICON"))]
|
||||||
pub window_icon: nwg::Icon,
|
pub window_icon: nwg::Icon,
|
||||||
#[nwg_control(icon: Some(&data.window_icon), title: "Stremio", flags: "MAIN_WINDOW|VISIBLE")]
|
#[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,
|
pub window: nwg::Window,
|
||||||
#[nwg_partial(parent: 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,
|
pub tray: SystemTray,
|
||||||
#[nwg_partial(parent: window)]
|
#[nwg_partial(parent: window)]
|
||||||
pub webview: WebView,
|
pub webview: WebView,
|
||||||
|
|
@ -101,6 +95,33 @@ impl MainWindow {
|
||||||
const BG_COLOR: [u8; 3] = [27, 17, 38];
|
const BG_COLOR: [u8; 3] = [27, 17, 38];
|
||||||
const MIN_WIDTH: i32 = 1000;
|
const MIN_WIDTH: i32 = 1000;
|
||||||
const MIN_HEIGHT: i32 = 600;
|
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) {
|
fn on_init(&self) {
|
||||||
self.webview.endpoint.set(self.webui_url.clone()).ok();
|
self.webview.endpoint.set(self.webui_url.clone()).ok();
|
||||||
let small_side = cmp::min(nwg::Monitor::width(), nwg::Monitor::height()) * 70 / 100;
|
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_frame.set_size(w, h);
|
||||||
self.splash.set_size(s, s);
|
self.splash.set_size(s, s);
|
||||||
self.splash.set_position(w as i32 / 2 - s as i32 / 2, 0);
|
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) {
|
fn on_toggle_fullscreen_notice(&self) {
|
||||||
if let Some(hwnd) = self.window.handle.hwnd() {
|
if let Some(hwnd) = self.window.handle.hwnd() {
|
||||||
let mut saved_style = self.saved_window_style.borrow_mut();
|
let mut saved_style = self.saved_window_style.borrow_mut();
|
||||||
if saved_style.full_screen {
|
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 {
|
unsafe {
|
||||||
SetWindowLongA(hwnd, GWL_STYLE, saved_style.style);
|
SetWindowLongA(hwnd, GWL_STYLE, saved_style.style);
|
||||||
SetWindowLongA(hwnd, GWL_EXSTYLE, saved_style.ex_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;
|
saved_style.full_screen = false;
|
||||||
|
self.tray.tray_topmost.set_enabled(true);
|
||||||
|
self.tray.tray_topmost.set_checked(topmost == HWND_TOPMOST);
|
||||||
} else {
|
} else {
|
||||||
saved_style.pos = self.window.position();
|
saved_style.pos = self.window.position();
|
||||||
saved_style.size = self.window.size();
|
saved_style.size = self.window.size();
|
||||||
|
|
@ -286,29 +322,21 @@ impl MainWindow {
|
||||||
| WS_EX_CLIENTEDGE as i32
|
| WS_EX_CLIENTEDGE as i32
|
||||||
| WS_EX_STATICEDGE 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;
|
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) {
|
fn on_quit_notice(&self) {
|
||||||
nwg::stop_thread_dispatch();
|
nwg::stop_thread_dispatch();
|
||||||
|
|
@ -316,40 +344,44 @@ impl MainWindow {
|
||||||
fn on_hide_splash_notice(&self) {
|
fn on_hide_splash_notice(&self) {
|
||||||
self.splash_frame.set_visible(false);
|
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) {
|
fn on_show_hide(&self) {
|
||||||
self.window.set_visible(!self.window.visible());
|
self.window.set_visible(!self.window.visible());
|
||||||
self.tray.tray_show_hide.set_checked(self.window.visible());
|
self.tray.tray_show_hide.set_checked(self.window.visible());
|
||||||
let web_channel = self.webview.channel.borrow();
|
self.transmit_window_state_change();
|
||||||
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) {
|
fn on_quit(&self, data: &nwg::EventData) {
|
||||||
if let Some(hwnd) = self.window.handle.hwnd() {
|
|
||||||
if let nwg::EventData::OnWindowClose(data) = data {
|
if let nwg::EventData::OnWindowClose(data) = data {
|
||||||
data.close(false);
|
data.close(false);
|
||||||
}
|
}
|
||||||
self.window.set_visible(false);
|
self.window.set_visible(false);
|
||||||
let web_channel = self.webview.channel.borrow();
|
self.tray.tray_show_hide.set_checked(self.window.visible());
|
||||||
let (web_tx, _) = web_channel
|
self.transmit_window_full_screen_change(false);
|
||||||
.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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::{self, json};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::{self, json};
|
|
||||||
|
|
||||||
pub type Channel = RefCell<Option<(mpsc::Sender<String>, Arc<Mutex<mpsc::Receiver<String>>>)>>;
|
pub type Channel = RefCell<Option<(mpsc::Sender<String>, Arc<Mutex<mpsc::Receiver<String>>>)>>;
|
||||||
|
|
||||||
|
|
@ -51,4 +51,16 @@ impl RPCResponse {
|
||||||
};
|
};
|
||||||
serde_json::to_string(&resp).expect("Cannot build response")
|
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ pub struct SystemTray {
|
||||||
#[nwg_resource(source_embed: Some(&data.embed), source_embed_str: Some("MAINICON"))]
|
#[nwg_resource(source_embed: Some(&data.embed), source_embed_str: Some("MAINICON"))]
|
||||||
pub tray_icon: nwg::Icon,
|
pub tray_icon: nwg::Icon,
|
||||||
#[nwg_control(icon: Some(&data.tray_icon), tip: Some("Stremio"))]
|
#[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,
|
pub tray: nwg::TrayNotification,
|
||||||
#[nwg_control(popup: true)]
|
#[nwg_control(popup: true)]
|
||||||
pub tray_menu: nwg::Menu,
|
pub tray_menu: nwg::Menu,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue