From 607122f034874afcd54fc3bc25e16564b2723b3a Mon Sep 17 00:00:00 2001 From: Vladimir Borisov Date: Mon, 23 May 2022 13:39:14 +0300 Subject: [PATCH] Single instance --- Cargo.lock | 46 ++++-- Cargo.toml | 2 + src/main.rs | 150 ++++++++++------- src/stremio_app/app.rs | 47 ++++++ src/stremio_app/ipc.rs | 207 ++++++++++++------------ src/stremio_app/mod.rs | 2 + src/stremio_app/named_pipe.rs | 265 +++++++++++++++++++++++++++++++ src/stremio_app/window_helper.rs | 9 +- 8 files changed, 555 insertions(+), 173 deletions(-) create mode 100644 src/stremio_app/named_pipe.rs diff --git a/Cargo.lock b/Cargo.lock index f228899..bd6b9cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,7 +19,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ "hermit-abi", "libc", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -220,6 +220,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -317,7 +327,7 @@ dependencies = [ "plotters", "plotters-backend", "stretch", - "winapi", + "winapi 0.3.9", "winapi-build", ] @@ -343,7 +353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9213e7b66aa06a7722828ee2980c1adff22a3922b582baaa1e62e30ca2a6c018" dependencies = [ "pathdiff", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -574,6 +584,7 @@ dependencies = [ "clap", "embed-resource", "flume", + "kernel32-sys", "libmpv", "libmpv-sys", "native-windows-derive", @@ -587,8 +598,9 @@ dependencies = [ "urlencoding", "webview2", "webview2-sys", + "whoami", "win32job", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -821,7 +833,7 @@ dependencies = [ "once_cell", "webview2-sys", "widestring", - "winapi", + "winapi 0.3.9", ] [[package]] @@ -831,7 +843,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24b7889e893ac4c50d7346356be3ce13a85e56512c38b8fde0526559b8012a4c" dependencies = [ "com", - "winapi", + "winapi 0.3.9", +] + +[[package]] +name = "whoami" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" +dependencies = [ + "wasm-bindgen", + "web-sys", ] [[package]] @@ -847,9 +869,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a14136f6c8be9146ac6345774ab32cb93e7985319b4a1b42abb663bd64235" dependencies = [ "thiserror", - "winapi", + "winapi 0.3.9", ] +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + [[package]] name = "winapi" version = "0.3.9" @@ -878,7 +906,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi", + "winapi 0.3.9", ] [[package]] @@ -893,5 +921,5 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ - "winapi", + "winapi 0.3.9", ] diff --git a/Cargo.toml b/Cargo.toml index 3b467aa..f34a24c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,8 @@ bitflags = "1.2.1" win32job = "1" parse-display = "0.5.1" flume = "0.10.9" +kernel32-sys = "0.2" +whoami = "1.2.1" [build-dependencies] embed-resource = "1.3" diff --git a/src/main.rs b/src/main.rs index 5584ffc..c2d84fa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,60 +1,90 @@ -#![windows_subsystem = "windows"] -#[macro_use] -extern crate bitflags; -use clap::Parser; -use native_windows_gui::{self as nwg, NativeUi}; -mod stremio_app; -use crate::stremio_app::{stremio_server::StremioServer, MainWindow}; - -const DEV_ENDPOINT: &str = "http://127.0.0.1:11470"; -const WEB_ENDPOINT: &str = "https://app.strem.io/shell-v4.4/"; -const STA_ENDPOINT: &str = "https://staging.strem.io/"; - -#[derive(Parser, Debug)] -#[clap(version)] -struct Opt { - #[clap(long, help = "Enable dev tools when pressing F12")] - dev_tools: bool, - #[clap(long, help = "Disable the server and load the WebUI from localhost")] - development: bool, - #[clap(long, help = "Shortcut for --webui-url=https://staging.strem.io/")] - staging: bool, - #[clap(long, default_value = WEB_ENDPOINT, help = "Override the WebUI URL")] - webui_url: String, -} - -fn main() { - // native-windows-gui has some basic high DPI support with the high-dpi - // feature. It supports the "System DPI Awareness" mode, but not the more - // advanced Per-Monitor (v2) DPI Awareness modes. - // - // Use an application manifest to get rid of this deprecated warning. - #[allow(deprecated)] - unsafe { - nwg::set_dpi_awareness() - }; - nwg::enable_visual_styles(); - - let opt = Opt::parse(); - - if !opt.development { - StremioServer::new(); - } - - let webui_url = if opt.development && opt.webui_url == WEB_ENDPOINT { - DEV_ENDPOINT.to_string() - } else if opt.staging && opt.webui_url == WEB_ENDPOINT { - STA_ENDPOINT.to_string() - } else { - opt.webui_url - }; - - nwg::init().expect("Failed to init Native Windows GUI"); - let _app = MainWindow::build_ui(MainWindow { - webui_url, - dev_tools: opt.development || opt.dev_tools, - ..Default::default() - }) - .expect("Failed to build UI"); - nwg::dispatch_thread_events(); -} +#![windows_subsystem = "windows"] +#[macro_use] +extern crate bitflags; +use std::io::Write; +use std::path::Path; +use std::process::exit; +use whoami::username; + +use clap::Parser; +use native_windows_gui::{self as nwg, NativeUi}; +mod stremio_app; +use crate::stremio_app::{stremio_server::StremioServer, MainWindow, PipeClient}; + +const DEV_ENDPOINT: &str = "http://127.0.0.1:11470"; +const WEB_ENDPOINT: &str = "https://app.strem.io/shell-v4.4/"; +const STA_ENDPOINT: &str = "https://staging.strem.io/"; + +#[derive(Parser, Debug)] +#[clap(version)] +struct Opt { + command: Option, + #[clap(long, help = "Enable dev tools when pressing F12")] + dev_tools: bool, + #[clap(long, help = "Disable the server and load the WebUI from localhost")] + development: bool, + #[clap(long, help = "Shortcut for --webui-url=https://staging.strem.io/")] + staging: bool, + #[clap(long, default_value = WEB_ENDPOINT, help = "Override the WebUI URL")] + webui_url: String, +} + +fn main() { + // native-windows-gui has some basic high DPI support with the high-dpi + // feature. It supports the "System DPI Awareness" mode, but not the more + // advanced Per-Monitor (v2) DPI Awareness modes. + // + // Use an application manifest to get rid of this deprecated warning. + #[allow(deprecated)] + unsafe { + nwg::set_dpi_awareness() + }; + nwg::enable_visual_styles(); + + let opt = Opt::parse(); + + let command = match opt.command { + Some(file) => { + if Path::new(&file).exists() { + "file:///".to_string() + &file.replace('\\', "/") + } else { + file + } + } + None => "".to_string(), + }; + + // Single application IPC + let mut commands_path = "//./pipe/com.stremio5.".to_string(); + // Append the username so it works per User + 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); + } + // END IPC + + if !opt.development { + StremioServer::new(); + } + + let webui_url = if opt.development && opt.webui_url == WEB_ENDPOINT { + DEV_ENDPOINT.to_string() + } else if opt.staging && opt.webui_url == WEB_ENDPOINT { + STA_ENDPOINT.to_string() + } else { + opt.webui_url + }; + + nwg::init().expect("Failed to init Native Windows GUI"); + let _app = MainWindow::build_ui(MainWindow { + command, + commands_path: Some(commands_path), + webui_url, + dev_tools: opt.development || opt.dev_tools, + ..Default::default() + }) + .expect("Failed to build UI"); + nwg::dispatch_thread_events(); +} diff --git a/src/stremio_app/app.rs b/src/stremio_app/app.rs index b447b63..5b782b2 100644 --- a/src/stremio_app/app.rs +++ b/src/stremio_app/app.rs @@ -1,7 +1,11 @@ +use crate::stremio_app::PipeServer; use native_windows_derive::NwgUi; use native_windows_gui as nwg; use serde_json; use std::cell::RefCell; +use std::io::Read; +use std::path::Path; +use std::str; use std::thread; use winapi::um::winuser::WS_EX_TOPMOST; @@ -14,6 +18,8 @@ use crate::stremio_app::window_helper::WindowStyle; #[derive(Default, NwgUi)] pub struct MainWindow { + pub command: String, + pub commands_path: Option, pub webui_url: String, pub dev_tools: bool, pub saved_window_style: RefCell, @@ -42,6 +48,9 @@ pub struct MainWindow { #[nwg_control] #[nwg_events(OnNotice: [Self::on_hide_splash_notice] )] pub hide_splash_notice: nwg::Notice, + #[nwg_control] + #[nwg_events(OnNotice: [Self::on_focus_notice] )] + pub focus_notice: nwg::Notice, } impl MainWindow { @@ -97,7 +106,30 @@ impl MainWindow { .expect("Cannont obtain communication channel for the Web UI"); let web_tx_player = web_tx.clone(); let web_tx_web = web_tx.clone(); + let web_tx_arg = web_tx.clone(); let web_rx = web_rx.clone(); + let command_clone = self.command.clone(); + + // Single application IPC + let socket_path = Path::new( + self.commands_path + .as_ref() + .expect("Cannot initialie the single application IPC"), + ); + if let Ok(mut listener) = PipeServer::bind(socket_path) { + thread::spawn(move || loop { + if let Ok(mut stream) = listener.accept() { + let mut buf = vec![]; + stream.read_to_end(&mut buf).ok(); + if let Ok(s) = str::from_utf8(&buf) { + // ['open-media', url] + web_tx_arg.send(RPCResponse::open_media(s.to_string())).ok(); + println!("{}", s); + } + } + }); + } + // Read message from player thread::spawn(move || loop { player_rx @@ -109,6 +141,7 @@ impl MainWindow { let toggle_fullscreen_sender = self.toggle_fullscreen_notice.sender(); let quit_sender = self.quit_notice.sender(); let hide_splash_sender = self.hide_splash_notice.sender(); + let focus_sender = self.focus_notice.sender(); thread::spawn(move || loop { if let Some(msg) = web_rx .recv() @@ -127,6 +160,10 @@ impl MainWindow { web_tx_web .send(RPCResponse::visibility_change(true, 1, false)) .ok(); + let command_ref = command_clone.clone(); + if !command_ref.is_empty() { + web_tx_web.send(RPCResponse::open_media(command_ref)).ok(); + } } Some("app-error") => { hide_splash_sender.notice(); @@ -152,6 +189,9 @@ impl MainWindow { } } } + Some("win-focus") => { + focus_sender.notice(); + } Some(player_command) if player_command.starts_with("mpv-") => { let resp_json = serde_json::to_string( &msg.args.expect("Cannot have method without args"), @@ -192,6 +232,13 @@ impl MainWindow { fn on_hide_splash_notice(&self) { self.splash_screen.hide(); } + fn on_focus_notice(&self) { + self.window.set_visible(true); + if let Some(hwnd) = self.window.handle.hwnd() { + let mut saved_style = self.saved_window_style.borrow_mut(); + saved_style.set_active(hwnd); + } + } fn on_toggle_topmost(&self) { if let Some(hwnd) = self.window.handle.hwnd() { let mut saved_style = self.saved_window_style.borrow_mut(); diff --git a/src/stremio_app/ipc.rs b/src/stremio_app/ipc.rs index a1fe9d5..05dac0f 100644 --- a/src/stremio_app/ipc.rs +++ b/src/stremio_app/ipc.rs @@ -1,102 +1,105 @@ -use serde::{Deserialize, Serialize}; -use serde_json::{self, json}; -use std::cell::RefCell; - -const VERSION: &str = env!("CARGO_PKG_VERSION"); - -pub type Channel = RefCell, flume::Receiver)>>; - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct RPCRequest { - pub id: u64, - pub args: Option>, -} - -impl RPCRequest { - pub fn is_handshake(&self) -> bool { - self.id == 0 - } - pub fn get_method(&self) -> Option<&str> { - self.args - .as_ref() - .and_then(|args| args.first()) - .and_then(|arg| arg.as_str()) - } - pub fn get_params(&self) -> Option<&serde_json::Value> { - self.args - .as_ref() - .and_then(|args| if args.len() > 1 { Some(&args[1]) } else { None }) - } -} -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct RPCResponseDataTransport { - pub properties: Vec>, - pub signals: Vec, - pub methods: Vec>, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct RPCResponseData { - pub transport: RPCResponseDataTransport, -} - -#[derive(Default, Serialize, Deserialize, Debug, Clone)] -pub struct RPCResponse { - pub id: u64, - pub object: String, - #[serde(rename = "type")] - pub response_type: u32, - #[serde(skip_serializing_if = "Option::is_none")] - pub data: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub args: Option, -} - -impl RPCResponse { - pub fn get_handshake() -> String { - let resp = RPCResponse { - id: 0, - object: "transport".to_string(), - response_type: 3, - data: Some(RPCResponseData { - transport: RPCResponseDataTransport { - properties: vec![ - vec![], - vec![ - "".to_string(), - "shellVersion".to_string(), - "".to_string(), - VERSION.to_string(), - ], - ], - signals: vec![], - methods: vec![vec!["onEvent".to_string(), "".to_string()]], - }, - }), - ..Default::default() - }; - serde_json::to_string(&resp).expect("Cannot build response") - } - pub fn response_message(msg: Option) -> String { - let resp = RPCResponse { - id: 1, - object: "transport".to_string(), - response_type: 1, - args: msg, - ..Default::default() - }; - serde_json::to_string(&resp).expect("Cannot build response") - } - pub fn visibility_change(visible: bool, visibility: u32, is_full_screen: bool) -> String { - Self::response_message(Some(json!(["win-visibility-changed" ,{ - "visible": visible, - "visibility": visibility, - "isFullscreen": is_full_screen - }]))) - } - pub fn state_change(state: u32) -> String { - Self::response_message(Some(json!(["win-state-changed" ,{ - "state": state, - }]))) - } -} +use serde::{Deserialize, Serialize}; +use serde_json::{self, json}; +use std::cell::RefCell; + +const VERSION: &str = env!("CARGO_PKG_VERSION"); + +pub type Channel = RefCell, flume::Receiver)>>; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RPCRequest { + pub id: u64, + pub args: Option>, +} + +impl RPCRequest { + pub fn is_handshake(&self) -> bool { + self.id == 0 + } + pub fn get_method(&self) -> Option<&str> { + self.args + .as_ref() + .and_then(|args| args.first()) + .and_then(|arg| arg.as_str()) + } + pub fn get_params(&self) -> Option<&serde_json::Value> { + self.args + .as_ref() + .and_then(|args| if args.len() > 1 { Some(&args[1]) } else { None }) + } +} +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RPCResponseDataTransport { + pub properties: Vec>, + pub signals: Vec, + pub methods: Vec>, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RPCResponseData { + pub transport: RPCResponseDataTransport, +} + +#[derive(Default, Serialize, Deserialize, Debug, Clone)] +pub struct RPCResponse { + pub id: u64, + pub object: String, + #[serde(rename = "type")] + pub response_type: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub data: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub args: Option, +} + +impl RPCResponse { + pub fn get_handshake() -> String { + let resp = RPCResponse { + id: 0, + object: "transport".to_string(), + response_type: 3, + data: Some(RPCResponseData { + transport: RPCResponseDataTransport { + properties: vec![ + vec![], + vec![ + "".to_string(), + "shellVersion".to_string(), + "".to_string(), + VERSION.to_string(), + ], + ], + signals: vec![], + methods: vec![vec!["onEvent".to_string(), "".to_string()]], + }, + }), + ..Default::default() + }; + serde_json::to_string(&resp).expect("Cannot build response") + } + pub fn response_message(msg: Option) -> String { + let resp = RPCResponse { + id: 1, + object: "transport".to_string(), + response_type: 1, + args: msg, + ..Default::default() + }; + serde_json::to_string(&resp).expect("Cannot build response") + } + pub fn visibility_change(visible: bool, visibility: u32, is_full_screen: bool) -> String { + Self::response_message(Some(json!(["win-visibility-changed" ,{ + "visible": visible, + "visibility": visibility, + "isFullscreen": is_full_screen + }]))) + } + pub fn state_change(state: u32) -> String { + Self::response_message(Some(json!(["win-state-changed" ,{ + "state": state, + }]))) + } + pub fn open_media(url: String) -> String { + Self::response_message(Some(json!(["open-media", url]))) + } +} diff --git a/src/stremio_app/mod.rs b/src/stremio_app/mod.rs index d30e458..4644d6e 100644 --- a/src/stremio_app/mod.rs +++ b/src/stremio_app/mod.rs @@ -14,3 +14,5 @@ pub mod splash; pub use splash::SplashImage; pub mod window_helper; pub use window_helper::WindowStyle; +pub mod named_pipe; +pub use named_pipe::{PipeClient, PipeServer}; diff --git a/src/stremio_app/named_pipe.rs b/src/stremio_app/named_pipe.rs new file mode 100644 index 0000000..d7c051d --- /dev/null +++ b/src/stremio_app/named_pipe.rs @@ -0,0 +1,265 @@ +// Based on +// https://gitlab.com/tbsaunde/windows-named-pipe/-/blob/f4fd29191f0541f85f818885275dc4573d4059ec/src/lib.rs + +use kernel32::{ + CloseHandle, ConnectNamedPipe, CreateFileW, CreateNamedPipeW, DisconnectNamedPipe, + FlushFileBuffers, ReadFile, WaitNamedPipeW, WriteFile, +}; +use std::ffi::OsStr; +use std::ffi::OsString; +use std::io::{self, Read, Write}; +use std::os::windows::prelude::OsStrExt; +use std::path::Path; +use winapi::shared::minwindef::{DWORD, LPCVOID, LPVOID}; +use winapi::shared::winerror; +use winapi::um::fileapi::OPEN_EXISTING; +use winapi::um::handleapi::INVALID_HANDLE_VALUE; +use winapi::um::winbase::{ + FILE_FLAG_FIRST_PIPE_INSTANCE, PIPE_ACCESS_DUPLEX, PIPE_READMODE_BYTE, PIPE_TYPE_BYTE, + PIPE_UNLIMITED_INSTANCES, PIPE_WAIT, +}; +use winapi::um::winnt::{FILE_ATTRIBUTE_NORMAL, GENERIC_READ, GENERIC_WRITE, HANDLE}; + +#[derive(Debug)] +pub struct PipeClient { + is_server: bool, + handle: Handle, +} + +impl PipeClient { + fn create_pipe(path: &Path) -> io::Result { + let mut os_str: OsString = path.as_os_str().into(); + os_str.push("\x00"); + let u16_slice = os_str.encode_wide().collect::>(); + + unsafe { WaitNamedPipeW(u16_slice.as_ptr(), 0) }; + let handle = unsafe { + CreateFileW( + u16_slice.as_ptr(), + GENERIC_READ | GENERIC_WRITE, + 0, + std::ptr::null_mut(), + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + std::ptr::null_mut(), + ) + }; + + if handle != INVALID_HANDLE_VALUE { + Ok(handle) + } else { + Err(io::Error::last_os_error()) + } + } + + pub fn connect>(path: P) -> io::Result { + let handle = PipeClient::create_pipe(path.as_ref())?; + + Ok(PipeClient { + handle: Handle { inner: handle }, + is_server: false, + }) + } +} + +impl Drop for PipeClient { + fn drop(&mut self) { + unsafe { FlushFileBuffers(self.handle.inner) }; + if self.is_server { + unsafe { DisconnectNamedPipe(self.handle.inner) }; + } + } +} + +impl Read for PipeClient { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let mut bytes_read = 0; + let ok = unsafe { + ReadFile( + self.handle.inner, + buf.as_mut_ptr() as LPVOID, + buf.len() as DWORD, + &mut bytes_read, + std::ptr::null_mut(), + ) + }; + + if ok != 0 { + Ok(bytes_read as usize) + } else { + match io::Error::last_os_error().raw_os_error().map(|x| x as u32) { + Some(winerror::ERROR_PIPE_NOT_CONNECTED) => Ok(0), + Some(err) => Err(io::Error::from_raw_os_error(err as i32)), + _ => panic!(""), + } + } + } +} + +impl Write for PipeClient { + fn write(&mut self, buf: &[u8]) -> io::Result { + let mut bytes_written = 0; + let ok = unsafe { + WriteFile( + self.handle.inner, + buf.as_ptr() as LPCVOID, + buf.len() as DWORD, + &mut bytes_written, + std::ptr::null_mut(), + ) + }; + + if ok != 0 { + Ok(bytes_written as usize) + } else { + Err(io::Error::last_os_error()) + } + } + + fn flush(&mut self) -> io::Result<()> { + let ok = unsafe { FlushFileBuffers(self.handle.inner) }; + + if ok != 0 { + Ok(()) + } else { + Err(io::Error::last_os_error()) + } + } +} + +#[derive(Debug)] +pub struct PipeServer { + path: Vec, + next_pipe: Handle, +} + +fn to_u16s>(s: S) -> io::Result> { + let mut maybe_result: Vec = s.as_ref().encode_wide().collect(); + if maybe_result.iter().any(|&u| u == 0) { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "strings passed to WinAPI cannot contain NULs", + )); + } + maybe_result.push(0); + Ok(maybe_result) +} + +impl PipeServer { + fn create_pipe(path: &[u16], first: bool) -> io::Result { + let mut access_flags = PIPE_ACCESS_DUPLEX; + if first { + access_flags |= FILE_FLAG_FIRST_PIPE_INSTANCE; + } + let handle = unsafe { + CreateNamedPipeW( + path.as_ptr(), + access_flags, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + 65536, + 65536, + 50, + std::ptr::null_mut(), + ) + }; + + if handle != INVALID_HANDLE_VALUE { + Ok(Handle { inner: handle }) + } else { + Err(io::Error::last_os_error()) + } + } + + fn connect_pipe(handle: &Handle) -> io::Result<()> { + let result = unsafe { ConnectNamedPipe(handle.inner, std::ptr::null_mut()) }; + + if result != 0 { + Ok(()) + } else { + match io::Error::last_os_error().raw_os_error().map(|x| x as u32) { + Some(winerror::ERROR_PIPE_CONNECTED) => Ok(()), + Some(err) => Err(io::Error::from_raw_os_error(err as i32)), + _ => panic!(""), + } + } + } + + pub fn bind>(path: P) -> io::Result { + let path = to_u16s(path.as_ref().as_os_str())?; + let next_pipe = PipeServer::create_pipe(&path, true)?; + Ok(PipeServer { path, next_pipe }) + } + + pub fn accept(&mut self) -> io::Result { + let handle = std::mem::replace( + &mut self.next_pipe, + PipeServer::create_pipe(&self.path, false)?, + ); + + PipeServer::connect_pipe(&handle)?; + + Ok(PipeClient { + handle, + is_server: true, + }) + } +} + +#[derive(Debug)] +struct Handle { + inner: HANDLE, +} + +impl Drop for Handle { + fn drop(&mut self) { + unsafe { CloseHandle(self.inner) }; + } +} + +unsafe impl Sync for Handle {} +unsafe impl Send for Handle {} + +#[cfg(test)] +mod tests { + use super::*; + use std::thread; + + macro_rules! or_panic { + ($e:expr) => { + match $e { + Ok(e) => e, + Err(e) => { + panic!("{}", e); + } + } + }; + } + + #[test] + fn duplex_communication() { + let socket_path = Path::new("//./pipe/basicsock"); + println!("{:?}", socket_path); + let msg1 = b"hello"; + let msg2 = b"world!"; + + let mut listener = or_panic!(PipeServer::bind(socket_path)); + let thread = thread::spawn(move || { + let mut stream = or_panic!(listener.accept()); + let mut buf = [0; 5]; + or_panic!(stream.read(&mut buf)); + assert_eq!(&msg1[..], &buf[..]); + or_panic!(stream.write_all(msg2)); + }); + + let mut stream = or_panic!(PipeClient::connect(socket_path)); + + or_panic!(stream.write_all(msg1)); + let mut buf = vec![]; + or_panic!(stream.read_to_end(&mut buf)); + assert_eq!(&msg2[..], &buf[..]); + drop(stream); + + thread.join().unwrap(); + } +} diff --git a/src/stremio_app/window_helper.rs b/src/stremio_app/window_helper.rs index 47b734d..4084279 100644 --- a/src/stremio_app/window_helper.rs +++ b/src/stremio_app/window_helper.rs @@ -2,8 +2,8 @@ use std::{cmp, mem}; use winapi::shared::windef::HWND; use winapi::um::winuser::{ GetForegroundWindow, GetSystemMetrics, GetWindowLongA, GetWindowRect, IsIconic, IsZoomed, - SetWindowLongA, SetWindowPos, GWL_EXSTYLE, GWL_STYLE, HWND_NOTOPMOST, HWND_TOPMOST, - SM_CXSCREEN, SM_CYSCREEN, SWP_FRAMECHANGED, SWP_NOMOVE, SWP_NOSIZE, WS_CAPTION, + SetForegroundWindow, 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, }; @@ -136,4 +136,9 @@ impl WindowStyle { } self.ex_style = unsafe { GetWindowLongA(hwnd, GWL_EXSTYLE) }; } + pub fn set_active(&mut self, hwnd: HWND) { + unsafe { + SetForegroundWindow(hwnd); + } + } }