mirror of
https://github.com/Stremio/stremio-shell-ng.git
synced 2026-05-20 08:52:01 +00:00
Compare commits
17 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b644519919 | ||
|
|
d7df369616 | ||
|
|
dd12f3a855 | ||
|
|
4b310e9afd | ||
|
|
49dcebab4e | ||
|
|
271382c752 | ||
|
|
59fe6b77a0 | ||
|
|
a5f0f62d02 | ||
|
|
5632cd7dc4 | ||
|
|
a6026fb568 | ||
|
|
b8b045b286 | ||
|
|
b7c0fd4f20 | ||
|
|
4c92d6658b | ||
|
|
60ccf06a29 | ||
|
|
b06ce07077 | ||
|
|
368a0062ed | ||
|
|
0d519db7b4 |
8 changed files with 362 additions and 30 deletions
|
|
@ -13,6 +13,7 @@ native-windows-gui = { git = "https://github.com/Stremio/native-windows-gui", fe
|
|||
] }
|
||||
native-windows-derive = "1"
|
||||
winapi = { version = "0.3.9", features = [
|
||||
"dwmapi",
|
||||
"libloaderapi",
|
||||
"handleapi",
|
||||
"jobapi2",
|
||||
|
|
|
|||
11
src/main.rs
11
src/main.rs
|
|
@ -88,8 +88,15 @@ fn main() {
|
|||
commands_path.push_str(&username());
|
||||
let socket_path = Path::new(&commands_path);
|
||||
if let Ok(mut stream) = PipeClient::connect(socket_path) {
|
||||
stream.write_all(command.as_bytes()).ok();
|
||||
exit(0);
|
||||
let forwarded = stream
|
||||
.write_all(command.as_bytes())
|
||||
.and_then(|_| stream.flush())
|
||||
.is_ok();
|
||||
drop(stream);
|
||||
if forwarded {
|
||||
exit(0);
|
||||
}
|
||||
eprintln!("Failed to forward command to existing Stremio instance; launching new instance");
|
||||
}
|
||||
// END IPC
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ use crate::stremio_app::{
|
|||
systray::SystemTray,
|
||||
updater,
|
||||
window_helper::WindowStyle,
|
||||
window_settings::WindowSettings,
|
||||
PipeServer,
|
||||
};
|
||||
|
||||
|
|
@ -41,6 +42,7 @@ pub struct MainWindow {
|
|||
pub force_update: bool,
|
||||
pub release_candidate: bool,
|
||||
pub autoupdater_setup_file: Arc<Mutex<Option<PathBuf>>>,
|
||||
pub requested_fullscreen: Arc<Mutex<Option<bool>>>,
|
||||
pub saved_window_style: RefCell<WindowStyle>,
|
||||
#[nwg_resource]
|
||||
pub embed: nwg::EmbedResource,
|
||||
|
|
@ -53,14 +55,15 @@ pub struct MainWindow {
|
|||
OnPaint: [Self::on_paint],
|
||||
OnMinMaxInfo: [Self::on_min_max(SELF, EVT_DATA)],
|
||||
OnWindowMinimize: [Self::transmit_window_state_change],
|
||||
OnWindowMaximize: [Self::transmit_window_state_change],
|
||||
OnWindowMaximize: [Self::on_window_state_changed],
|
||||
OnWindowFocus: [Self::transmit_window_state_change],
|
||||
OnResizeEnd: [Self::save_window_settings],
|
||||
)]
|
||||
pub window: nwg::Window,
|
||||
#[nwg_partial(parent: window)]
|
||||
#[nwg_events(
|
||||
(tray, MousePressLeftUp): [Self::on_show],
|
||||
(tray_exit, OnMenuItemSelected): [nwg::stop_thread_dispatch()],
|
||||
(tray_exit, OnMenuItemSelected): [Self::on_exit],
|
||||
(tray_show_hide, OnMenuItemSelected): [Self::on_show_hide],
|
||||
(tray_topmost, OnMenuItemSelected): [Self::on_toggle_topmost],
|
||||
)]
|
||||
|
|
@ -130,7 +133,13 @@ impl MainWindow {
|
|||
self.webview.dev_tools.set(self.dev_tools).ok();
|
||||
if let Some(hwnd) = self.window.handle.hwnd() {
|
||||
if let Ok(mut saved_style) = self.saved_window_style.try_borrow_mut() {
|
||||
saved_style.center_window(hwnd, WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT);
|
||||
saved_style.set_title_bar_color(hwnd);
|
||||
if let Some(window_settings) = WindowSettings::load() {
|
||||
saved_style
|
||||
.restore_window_placement(hwnd, window_settings.to_window_placement());
|
||||
} else {
|
||||
saved_style.center_window(hwnd, WINDOW_MIN_WIDTH, WINDOW_MIN_HEIGHT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -247,6 +256,7 @@ impl MainWindow {
|
|||
let hide_splash_sender = self.hide_splash_notice.sender();
|
||||
let focus_sender = self.focus_notice.sender();
|
||||
let autoupdater_setup_mutex = self.autoupdater_setup_file.clone();
|
||||
let requested_fullscreen = self.requested_fullscreen.clone();
|
||||
thread::spawn(move || loop {
|
||||
if let Some(msg) = web_rx
|
||||
.recv()
|
||||
|
|
@ -258,7 +268,16 @@ impl MainWindow {
|
|||
None if msg.is_handshake() => {
|
||||
web_tx_web.send(RPCResponse::get_handshake()).ok();
|
||||
}
|
||||
Some("win-set-visibility") => toggle_fullscreen_sender.notice(),
|
||||
Some("win-set-visibility") => {
|
||||
if let Some(fullscreen) = msg
|
||||
.get_params()
|
||||
.and_then(|params| params.get("fullscreen"))
|
||||
.and_then(|value| value.as_bool())
|
||||
{
|
||||
*requested_fullscreen.lock().unwrap() = Some(fullscreen);
|
||||
toggle_fullscreen_sender.notice();
|
||||
}
|
||||
}
|
||||
Some("quit") => quit_sender.notice(),
|
||||
Some("app-ready") => {
|
||||
hide_splash_sender.notice();
|
||||
|
|
@ -298,6 +317,52 @@ impl MainWindow {
|
|||
}
|
||||
}
|
||||
}
|
||||
Some("play-external") => {
|
||||
if let Some(arg) = msg.get_params() {
|
||||
let arg = arg.as_str().unwrap_or("");
|
||||
let arg_lc = arg.to_lowercase();
|
||||
const ALLOWED_SCHEMES: &[&str] = &["mpv://", "vlc://", "potplayer://"];
|
||||
let allowed = ALLOWED_SCHEMES.iter().any(|s| arg_lc.starts_with(s));
|
||||
if !arg.is_empty() && allowed {
|
||||
if let Some(stream_url) =
|
||||
arg_lc.starts_with("mpv://").then(|| &arg[6..])
|
||||
{
|
||||
// `--` ends mpv's option parsing; the stream URL can't smuggle flags.
|
||||
let mpv_paths: Vec<String> = vec![
|
||||
std::env::var("ProgramFiles")
|
||||
.ok()
|
||||
.map(|v| format!("{v}\\mpv\\mpv.exe")),
|
||||
std::env::var("ProgramFiles(x86)")
|
||||
.ok()
|
||||
.map(|v| format!("{v}\\mpv\\mpv.exe")),
|
||||
std::env::var("LOCALAPPDATA")
|
||||
.ok()
|
||||
.map(|v| format!("{v}\\Programs\\mpv\\mpv.exe")),
|
||||
std::env::var("LOCALAPPDATA")
|
||||
.ok()
|
||||
.map(|v| format!("{v}\\mpv\\mpv.exe")),
|
||||
Some("mpv.exe".to_string()),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect();
|
||||
for path in &mpv_paths {
|
||||
if Command::new(path)
|
||||
.arg("--")
|
||||
.arg(stream_url)
|
||||
.creation_flags(CREATE_BREAKAWAY_FROM_JOB)
|
||||
.spawn()
|
||||
.is_ok()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
open::that(arg).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some("win-focus") => {
|
||||
focus_sender.notice();
|
||||
}
|
||||
|
|
@ -360,10 +425,35 @@ impl MainWindow {
|
|||
self.webview.fit_to_window(self.window.handle.hwnd());
|
||||
}
|
||||
}
|
||||
fn on_window_state_changed(&self) {
|
||||
self.save_window_settings();
|
||||
self.transmit_window_state_change();
|
||||
}
|
||||
fn save_window_settings(&self) {
|
||||
if self
|
||||
.saved_window_style
|
||||
.try_borrow()
|
||||
.map(|style| style.full_screen)
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let Some(hwnd) = self.window.handle.hwnd() {
|
||||
if let Err(err) = WindowSettings::save(hwnd) {
|
||||
eprintln!("Cannot save window settings: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
fn on_toggle_fullscreen_notice(&self) {
|
||||
if let Some(hwnd) = self.window.handle.hwnd() {
|
||||
if let Ok(mut saved_style) = self.saved_window_style.try_borrow_mut() {
|
||||
saved_style.toggle_full_screen(hwnd);
|
||||
let target = self
|
||||
.requested_fullscreen
|
||||
.lock()
|
||||
.unwrap()
|
||||
.take()
|
||||
.unwrap_or(!saved_style.full_screen);
|
||||
saved_style.set_full_screen(hwnd, target);
|
||||
self.tray.tray_topmost.set_enabled(!saved_style.full_screen);
|
||||
self.tray
|
||||
.tray_topmost
|
||||
|
|
@ -422,8 +512,13 @@ impl MainWindow {
|
|||
if let nwg::EventData::OnWindowClose(data) = data {
|
||||
data.close(false);
|
||||
}
|
||||
self.save_window_settings();
|
||||
self.window.set_visible(false);
|
||||
self.tray.tray_show_hide.set_checked(self.window.visible());
|
||||
self.transmit_window_visibility_change();
|
||||
}
|
||||
fn on_exit(&self) {
|
||||
self.save_window_settings();
|
||||
nwg::stop_thread_dispatch();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ pub mod named_pipe;
|
|||
pub mod splash;
|
||||
pub mod systray;
|
||||
pub mod window_helper;
|
||||
pub mod window_settings;
|
||||
pub use named_pipe::{PipeClient, PipeServer};
|
||||
pub mod constants;
|
||||
pub mod updater;
|
||||
|
|
|
|||
|
|
@ -203,6 +203,7 @@ pub enum StrProp {
|
|||
SubBorderColor,
|
||||
SubColor,
|
||||
TrackList,
|
||||
Vf,
|
||||
VideoParams,
|
||||
Vo,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,10 +85,14 @@ impl StremioServer {
|
|||
let http_endpoint = String::new();
|
||||
loop {
|
||||
let mut buffer = [0; SRV_BUFFER_SIZE];
|
||||
let on = stdout.read(&mut buffer[..]).unwrap_or(!0);
|
||||
if on > buffer.len() {
|
||||
continue;
|
||||
}
|
||||
let on = match stdout.read(&mut buffer[..]) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => n,
|
||||
Err(err) => {
|
||||
eprintln!("server stdout read error: {err}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
std::io::stdout().write_all(&buffer).ok();
|
||||
let string_data = String::from_utf8_lossy(&buffer[..on]);
|
||||
{
|
||||
|
|
@ -116,10 +120,6 @@ impl StremioServer {
|
|||
.collect::<Vec<&str>>()
|
||||
.join("\n");
|
||||
};
|
||||
if on == 0 {
|
||||
// Server terminated
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -128,10 +128,14 @@ impl StremioServer {
|
|||
let err_thread = thread::spawn(move || {
|
||||
let mut buffer = [0; SRV_BUFFER_SIZE];
|
||||
loop {
|
||||
let en = stderr.read(&mut buffer[..]).unwrap_or(!0);
|
||||
if en > buffer.len() {
|
||||
continue;
|
||||
}
|
||||
let en = match stderr.read(&mut buffer[..]) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => n,
|
||||
Err(err) => {
|
||||
eprintln!("server stderr read error: {err}");
|
||||
break;
|
||||
}
|
||||
};
|
||||
std::io::stderr().write_all(&buffer).ok();
|
||||
let string_data = String::from_utf8_lossy(&buffer[..en]);
|
||||
// eprint!("{:?}", &buffer);
|
||||
|
|
@ -148,10 +152,6 @@ impl StremioServer {
|
|||
.collect::<Vec<&str>>()
|
||||
.join("\n");
|
||||
};
|
||||
if en == 0 {
|
||||
// Server terminated
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
out_thread.join().ok();
|
||||
|
|
|
|||
|
|
@ -1,13 +1,26 @@
|
|||
use std::{cmp, mem};
|
||||
use winapi::ctypes::c_void;
|
||||
use winapi::shared::minwindef::DWORD;
|
||||
use winapi::shared::windef::HWND;
|
||||
use winapi::um::dwmapi::DwmSetWindowAttribute;
|
||||
use winapi::um::winuser::{
|
||||
GetForegroundWindow, GetMonitorInfoA, GetSystemMetrics, GetWindowLongA, GetWindowRect,
|
||||
IsIconic, IsZoomed, MonitorFromWindow, SetForegroundWindow, SetWindowLongA, SetWindowPos,
|
||||
GWL_EXSTYLE, GWL_STYLE, HWND_NOTOPMOST, HWND_TOPMOST, MONITORINFO, MONITOR_DEFAULTTONEAREST,
|
||||
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,
|
||||
IsIconic, IsZoomed, MonitorFromWindow, SetForegroundWindow, SetWindowLongA, SetWindowPlacement,
|
||||
SetWindowPos, GWL_EXSTYLE, GWL_STYLE, HWND_NOTOPMOST, HWND_TOPMOST, MONITORINFO,
|
||||
MONITOR_DEFAULTTONEAREST, SM_CXSCREEN, SM_CYSCREEN, SWP_FRAMECHANGED, SWP_NOMOVE, SWP_NOSIZE,
|
||||
WINDOWPLACEMENT, WS_CAPTION, WS_EX_CLIENTEDGE, WS_EX_DLGMODALFRAME, WS_EX_STATICEDGE,
|
||||
WS_EX_TOPMOST, WS_EX_WINDOWEDGE, WS_THICKFRAME,
|
||||
};
|
||||
|
||||
const DWMWA_CAPTION_COLOR: DWORD = 35;
|
||||
const DWMWA_TEXT_COLOR: DWORD = 36;
|
||||
const STREMIO_CAPTION_COLOR: DWORD = colorref(0x15, 0x12, 0x2b);
|
||||
const WHITE_TEXT_COLOR: DWORD = colorref(0xff, 0xff, 0xff);
|
||||
|
||||
const fn colorref(red: DWORD, green: DWORD, blue: DWORD) -> DWORD {
|
||||
red | (green << 8) | (blue << 16)
|
||||
}
|
||||
|
||||
// https://doc.qt.io/qt-5/qt.html#WindowState-enum
|
||||
bitflags! {
|
||||
struct WindowState: u8 {
|
||||
|
|
@ -71,8 +84,41 @@ impl WindowStyle {
|
|||
self.pos = ((monitor_w - self.size.0) / 2, (monitor_h - self.size.1) / 2);
|
||||
self.show_window_at(hwnd, HWND_NOTOPMOST);
|
||||
}
|
||||
pub fn toggle_full_screen(&mut self, hwnd: HWND) {
|
||||
if self.full_screen {
|
||||
pub fn restore_window_placement(&mut self, hwnd: HWND, placement: WINDOWPLACEMENT) {
|
||||
self.pos = (
|
||||
placement.rcNormalPosition.left,
|
||||
placement.rcNormalPosition.top,
|
||||
);
|
||||
self.size = (
|
||||
placement.rcNormalPosition.right - placement.rcNormalPosition.left,
|
||||
placement.rcNormalPosition.bottom - placement.rcNormalPosition.top,
|
||||
);
|
||||
unsafe {
|
||||
SetWindowPlacement(hwnd, &placement);
|
||||
}
|
||||
}
|
||||
pub fn set_title_bar_color(&self, hwnd: HWND) {
|
||||
unsafe {
|
||||
DwmSetWindowAttribute(
|
||||
hwnd,
|
||||
DWMWA_CAPTION_COLOR,
|
||||
&STREMIO_CAPTION_COLOR as *const _ as *const c_void,
|
||||
mem::size_of_val(&STREMIO_CAPTION_COLOR) as DWORD,
|
||||
);
|
||||
DwmSetWindowAttribute(
|
||||
hwnd,
|
||||
DWMWA_TEXT_COLOR,
|
||||
&WHITE_TEXT_COLOR as *const _ as *const c_void,
|
||||
mem::size_of_val(&WHITE_TEXT_COLOR) as DWORD,
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn set_full_screen(&mut self, hwnd: HWND, full_screen: bool) {
|
||||
if self.full_screen == full_screen {
|
||||
return;
|
||||
}
|
||||
|
||||
if !full_screen {
|
||||
let topmost = if self.ex_style as u32 & WS_EX_TOPMOST == WS_EX_TOPMOST {
|
||||
HWND_TOPMOST
|
||||
} else {
|
||||
|
|
|
|||
181
src/stremio_app/window_settings.rs
Normal file
181
src/stremio_app/window_settings.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{env, fs, io, path::PathBuf};
|
||||
use winapi::shared::windef::{HWND, POINT, RECT};
|
||||
use winapi::um::winuser::{
|
||||
GetWindowPlacement, IsIconic, SW_SHOWMAXIMIZED, SW_SHOWNORMAL, WINDOWPLACEMENT,
|
||||
};
|
||||
|
||||
const WINDOW_SETTINGS_FILE: &str = "window-state.json";
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct WindowSettings {
|
||||
show_cmd: u32,
|
||||
min_position: Point,
|
||||
max_position: Point,
|
||||
normal_position: Rect,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct Point {
|
||||
x: i32,
|
||||
y: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
struct Rect {
|
||||
left: i32,
|
||||
top: i32,
|
||||
right: i32,
|
||||
bottom: i32,
|
||||
}
|
||||
|
||||
impl WindowSettings {
|
||||
pub fn load() -> Option<Self> {
|
||||
fs::read_to_string(settings_path())
|
||||
.ok()
|
||||
.and_then(|settings| serde_json::from_str(&settings).ok())
|
||||
}
|
||||
|
||||
pub fn save(hwnd: HWND) -> io::Result<()> {
|
||||
let Some(settings) = Self::from_window(hwnd) else {
|
||||
return Ok(());
|
||||
};
|
||||
let path = settings_path();
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let json = serde_json::to_string_pretty(&settings).map_err(io::Error::other)?;
|
||||
fs::write(path, json)
|
||||
}
|
||||
|
||||
pub fn to_window_placement(&self) -> WINDOWPLACEMENT {
|
||||
let mut placement = WINDOWPLACEMENT {
|
||||
length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
|
||||
flags: 0,
|
||||
showCmd: self.show_cmd,
|
||||
ptMinPosition: self.min_position.clone().into(),
|
||||
ptMaxPosition: self.max_position.clone().into(),
|
||||
rcNormalPosition: self.normal_position.clone().into(),
|
||||
};
|
||||
if !is_restorable_size(&placement.rcNormalPosition) {
|
||||
placement.showCmd = SW_SHOWNORMAL as u32;
|
||||
}
|
||||
placement
|
||||
}
|
||||
|
||||
fn from_window(hwnd: HWND) -> Option<Self> {
|
||||
if unsafe { IsIconic(hwnd) } != 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut placement = WINDOWPLACEMENT {
|
||||
length: std::mem::size_of::<WINDOWPLACEMENT>() as u32,
|
||||
flags: 0,
|
||||
showCmd: 0,
|
||||
ptMinPosition: POINT { x: 0, y: 0 },
|
||||
ptMaxPosition: POINT { x: 0, y: 0 },
|
||||
rcNormalPosition: RECT {
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
};
|
||||
|
||||
if unsafe { GetWindowPlacement(hwnd, &mut placement) } == 0 {
|
||||
return None;
|
||||
}
|
||||
if !is_restorable_size(&placement.rcNormalPosition) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(WindowSettings {
|
||||
show_cmd: if placement.showCmd == SW_SHOWMAXIMIZED as u32 {
|
||||
SW_SHOWMAXIMIZED as u32
|
||||
} else {
|
||||
SW_SHOWNORMAL as u32
|
||||
},
|
||||
min_position: placement.ptMinPosition.into(),
|
||||
max_position: placement.ptMaxPosition.into(),
|
||||
normal_position: placement.rcNormalPosition.into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn settings_path() -> PathBuf {
|
||||
env::var_os("APPDATA")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(env::temp_dir)
|
||||
.join("Stremio")
|
||||
.join(WINDOW_SETTINGS_FILE)
|
||||
}
|
||||
|
||||
fn is_restorable_size(rect: &RECT) -> bool {
|
||||
rect.right > rect.left && rect.bottom > rect.top
|
||||
}
|
||||
|
||||
impl From<POINT> for Point {
|
||||
fn from(point: POINT) -> Self {
|
||||
Point {
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point> for POINT {
|
||||
fn from(point: Point) -> Self {
|
||||
POINT {
|
||||
x: point.x,
|
||||
y: point.y,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<RECT> for Rect {
|
||||
fn from(rect: RECT) -> Self {
|
||||
Rect {
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rect> for RECT {
|
||||
fn from(rect: Rect) -> Self {
|
||||
RECT {
|
||||
left: rect.left,
|
||||
top: rect.top,
|
||||
right: rect.right,
|
||||
bottom: rect.bottom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::is_restorable_size;
|
||||
use winapi::shared::windef::RECT;
|
||||
|
||||
#[test]
|
||||
fn rejects_empty_window_rect() {
|
||||
assert!(!is_restorable_size(&RECT {
|
||||
left: 10,
|
||||
top: 10,
|
||||
right: 10,
|
||||
bottom: 20,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accepts_non_empty_window_rect() {
|
||||
assert!(is_restorable_size(&RECT {
|
||||
left: 10,
|
||||
top: 10,
|
||||
right: 20,
|
||||
bottom: 20,
|
||||
}));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue