mirror of
https://github.com/Stremio/stremio-shell-ng.git
synced 2026-03-11 17:15:49 +00:00
Single instance
This commit is contained in:
parent
bab059fe3f
commit
607122f034
8 changed files with 555 additions and 173 deletions
46
Cargo.lock
generated
46
Cargo.lock
generated
|
|
@ -19,7 +19,7 @@ checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -220,6 +220,16 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"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]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
|
|
@ -317,7 +327,7 @@ dependencies = [
|
||||||
"plotters",
|
"plotters",
|
||||||
"plotters-backend",
|
"plotters-backend",
|
||||||
"stretch",
|
"stretch",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
"winapi-build",
|
"winapi-build",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -343,7 +353,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9213e7b66aa06a7722828ee2980c1adff22a3922b582baaa1e62e30ca2a6c018"
|
checksum = "9213e7b66aa06a7722828ee2980c1adff22a3922b582baaa1e62e30ca2a6c018"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pathdiff",
|
"pathdiff",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -574,6 +584,7 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"embed-resource",
|
"embed-resource",
|
||||||
"flume",
|
"flume",
|
||||||
|
"kernel32-sys",
|
||||||
"libmpv",
|
"libmpv",
|
||||||
"libmpv-sys",
|
"libmpv-sys",
|
||||||
"native-windows-derive",
|
"native-windows-derive",
|
||||||
|
|
@ -587,8 +598,9 @@ dependencies = [
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
"webview2",
|
"webview2",
|
||||||
"webview2-sys",
|
"webview2-sys",
|
||||||
|
"whoami",
|
||||||
"win32job",
|
"win32job",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -821,7 +833,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"webview2-sys",
|
"webview2-sys",
|
||||||
"widestring",
|
"widestring",
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -831,7 +843,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24b7889e893ac4c50d7346356be3ce13a85e56512c38b8fde0526559b8012a4c"
|
checksum = "24b7889e893ac4c50d7346356be3ce13a85e56512c38b8fde0526559b8012a4c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"com",
|
"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]]
|
[[package]]
|
||||||
|
|
@ -847,9 +869,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b2a14136f6c8be9146ac6345774ab32cb93e7985319b4a1b42abb663bd64235"
|
checksum = "9b2a14136f6c8be9146ac6345774ab32cb93e7985319b4a1b42abb663bd64235"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror",
|
"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]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
|
@ -878,7 +906,7 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -893,5 +921,5 @@ version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,8 @@ bitflags = "1.2.1"
|
||||||
win32job = "1"
|
win32job = "1"
|
||||||
parse-display = "0.5.1"
|
parse-display = "0.5.1"
|
||||||
flume = "0.10.9"
|
flume = "0.10.9"
|
||||||
|
kernel32-sys = "0.2"
|
||||||
|
whoami = "1.2.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
embed-resource = "1.3"
|
embed-resource = "1.3"
|
||||||
|
|
|
||||||
150
src/main.rs
150
src/main.rs
|
|
@ -1,60 +1,90 @@
|
||||||
#![windows_subsystem = "windows"]
|
#![windows_subsystem = "windows"]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate bitflags;
|
extern crate bitflags;
|
||||||
use clap::Parser;
|
use std::io::Write;
|
||||||
use native_windows_gui::{self as nwg, NativeUi};
|
use std::path::Path;
|
||||||
mod stremio_app;
|
use std::process::exit;
|
||||||
use crate::stremio_app::{stremio_server::StremioServer, MainWindow};
|
use whoami::username;
|
||||||
|
|
||||||
const DEV_ENDPOINT: &str = "http://127.0.0.1:11470";
|
use clap::Parser;
|
||||||
const WEB_ENDPOINT: &str = "https://app.strem.io/shell-v4.4/";
|
use native_windows_gui::{self as nwg, NativeUi};
|
||||||
const STA_ENDPOINT: &str = "https://staging.strem.io/";
|
mod stremio_app;
|
||||||
|
use crate::stremio_app::{stremio_server::StremioServer, MainWindow, PipeClient};
|
||||||
#[derive(Parser, Debug)]
|
|
||||||
#[clap(version)]
|
const DEV_ENDPOINT: &str = "http://127.0.0.1:11470";
|
||||||
struct Opt {
|
const WEB_ENDPOINT: &str = "https://app.strem.io/shell-v4.4/";
|
||||||
#[clap(long, help = "Enable dev tools when pressing F12")]
|
const STA_ENDPOINT: &str = "https://staging.strem.io/";
|
||||||
dev_tools: bool,
|
|
||||||
#[clap(long, help = "Disable the server and load the WebUI from localhost")]
|
#[derive(Parser, Debug)]
|
||||||
development: bool,
|
#[clap(version)]
|
||||||
#[clap(long, help = "Shortcut for --webui-url=https://staging.strem.io/")]
|
struct Opt {
|
||||||
staging: bool,
|
command: Option<String>,
|
||||||
#[clap(long, default_value = WEB_ENDPOINT, help = "Override the WebUI URL")]
|
#[clap(long, help = "Enable dev tools when pressing F12")]
|
||||||
webui_url: String,
|
dev_tools: bool,
|
||||||
}
|
#[clap(long, help = "Disable the server and load the WebUI from localhost")]
|
||||||
|
development: bool,
|
||||||
fn main() {
|
#[clap(long, help = "Shortcut for --webui-url=https://staging.strem.io/")]
|
||||||
// native-windows-gui has some basic high DPI support with the high-dpi
|
staging: bool,
|
||||||
// feature. It supports the "System DPI Awareness" mode, but not the more
|
#[clap(long, default_value = WEB_ENDPOINT, help = "Override the WebUI URL")]
|
||||||
// advanced Per-Monitor (v2) DPI Awareness modes.
|
webui_url: String,
|
||||||
//
|
}
|
||||||
// Use an application manifest to get rid of this deprecated warning.
|
|
||||||
#[allow(deprecated)]
|
fn main() {
|
||||||
unsafe {
|
// native-windows-gui has some basic high DPI support with the high-dpi
|
||||||
nwg::set_dpi_awareness()
|
// feature. It supports the "System DPI Awareness" mode, but not the more
|
||||||
};
|
// advanced Per-Monitor (v2) DPI Awareness modes.
|
||||||
nwg::enable_visual_styles();
|
//
|
||||||
|
// Use an application manifest to get rid of this deprecated warning.
|
||||||
let opt = Opt::parse();
|
#[allow(deprecated)]
|
||||||
|
unsafe {
|
||||||
if !opt.development {
|
nwg::set_dpi_awareness()
|
||||||
StremioServer::new();
|
};
|
||||||
}
|
nwg::enable_visual_styles();
|
||||||
|
|
||||||
let webui_url = if opt.development && opt.webui_url == WEB_ENDPOINT {
|
let opt = Opt::parse();
|
||||||
DEV_ENDPOINT.to_string()
|
|
||||||
} else if opt.staging && opt.webui_url == WEB_ENDPOINT {
|
let command = match opt.command {
|
||||||
STA_ENDPOINT.to_string()
|
Some(file) => {
|
||||||
} else {
|
if Path::new(&file).exists() {
|
||||||
opt.webui_url
|
"file:///".to_string() + &file.replace('\\', "/")
|
||||||
};
|
} else {
|
||||||
|
file
|
||||||
nwg::init().expect("Failed to init Native Windows GUI");
|
}
|
||||||
let _app = MainWindow::build_ui(MainWindow {
|
}
|
||||||
webui_url,
|
None => "".to_string(),
|
||||||
dev_tools: opt.development || opt.dev_tools,
|
};
|
||||||
..Default::default()
|
|
||||||
})
|
// Single application IPC
|
||||||
.expect("Failed to build UI");
|
let mut commands_path = "//./pipe/com.stremio5.".to_string();
|
||||||
nwg::dispatch_thread_events();
|
// 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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
|
use crate::stremio_app::PipeServer;
|
||||||
use native_windows_derive::NwgUi;
|
use native_windows_derive::NwgUi;
|
||||||
use native_windows_gui as nwg;
|
use native_windows_gui as nwg;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::str;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use winapi::um::winuser::WS_EX_TOPMOST;
|
use winapi::um::winuser::WS_EX_TOPMOST;
|
||||||
|
|
||||||
|
|
@ -14,6 +18,8 @@ use crate::stremio_app::window_helper::WindowStyle;
|
||||||
|
|
||||||
#[derive(Default, NwgUi)]
|
#[derive(Default, NwgUi)]
|
||||||
pub struct MainWindow {
|
pub struct MainWindow {
|
||||||
|
pub command: String,
|
||||||
|
pub commands_path: Option<String>,
|
||||||
pub webui_url: String,
|
pub webui_url: String,
|
||||||
pub dev_tools: bool,
|
pub dev_tools: bool,
|
||||||
pub saved_window_style: RefCell<WindowStyle>,
|
pub saved_window_style: RefCell<WindowStyle>,
|
||||||
|
|
@ -42,6 +48,9 @@ pub struct MainWindow {
|
||||||
#[nwg_control]
|
#[nwg_control]
|
||||||
#[nwg_events(OnNotice: [Self::on_hide_splash_notice] )]
|
#[nwg_events(OnNotice: [Self::on_hide_splash_notice] )]
|
||||||
pub hide_splash_notice: nwg::Notice,
|
pub hide_splash_notice: nwg::Notice,
|
||||||
|
#[nwg_control]
|
||||||
|
#[nwg_events(OnNotice: [Self::on_focus_notice] )]
|
||||||
|
pub focus_notice: nwg::Notice,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MainWindow {
|
impl MainWindow {
|
||||||
|
|
@ -97,7 +106,30 @@ impl MainWindow {
|
||||||
.expect("Cannont obtain communication channel for the Web UI");
|
.expect("Cannont obtain communication channel for the Web UI");
|
||||||
let web_tx_player = web_tx.clone();
|
let web_tx_player = web_tx.clone();
|
||||||
let web_tx_web = web_tx.clone();
|
let web_tx_web = web_tx.clone();
|
||||||
|
let web_tx_arg = web_tx.clone();
|
||||||
let web_rx = web_rx.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
|
// Read message from player
|
||||||
thread::spawn(move || loop {
|
thread::spawn(move || loop {
|
||||||
player_rx
|
player_rx
|
||||||
|
|
@ -109,6 +141,7 @@ impl MainWindow {
|
||||||
let toggle_fullscreen_sender = self.toggle_fullscreen_notice.sender();
|
let toggle_fullscreen_sender = self.toggle_fullscreen_notice.sender();
|
||||||
let quit_sender = self.quit_notice.sender();
|
let quit_sender = self.quit_notice.sender();
|
||||||
let hide_splash_sender = self.hide_splash_notice.sender();
|
let hide_splash_sender = self.hide_splash_notice.sender();
|
||||||
|
let focus_sender = self.focus_notice.sender();
|
||||||
thread::spawn(move || loop {
|
thread::spawn(move || loop {
|
||||||
if let Some(msg) = web_rx
|
if let Some(msg) = web_rx
|
||||||
.recv()
|
.recv()
|
||||||
|
|
@ -127,6 +160,10 @@ impl MainWindow {
|
||||||
web_tx_web
|
web_tx_web
|
||||||
.send(RPCResponse::visibility_change(true, 1, false))
|
.send(RPCResponse::visibility_change(true, 1, false))
|
||||||
.ok();
|
.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") => {
|
Some("app-error") => {
|
||||||
hide_splash_sender.notice();
|
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-") => {
|
Some(player_command) if player_command.starts_with("mpv-") => {
|
||||||
let resp_json = serde_json::to_string(
|
let resp_json = serde_json::to_string(
|
||||||
&msg.args.expect("Cannot have method without args"),
|
&msg.args.expect("Cannot have method without args"),
|
||||||
|
|
@ -192,6 +232,13 @@ impl MainWindow {
|
||||||
fn on_hide_splash_notice(&self) {
|
fn on_hide_splash_notice(&self) {
|
||||||
self.splash_screen.hide();
|
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) {
|
fn on_toggle_topmost(&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();
|
||||||
|
|
|
||||||
|
|
@ -1,102 +1,105 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::{self, json};
|
use serde_json::{self, json};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
|
|
||||||
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
pub type Channel = RefCell<Option<(flume::Sender<String>, flume::Receiver<String>)>>;
|
pub type Channel = RefCell<Option<(flume::Sender<String>, flume::Receiver<String>)>>;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct RPCRequest {
|
pub struct RPCRequest {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub args: Option<Vec<serde_json::Value>>,
|
pub args: Option<Vec<serde_json::Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RPCRequest {
|
impl RPCRequest {
|
||||||
pub fn is_handshake(&self) -> bool {
|
pub fn is_handshake(&self) -> bool {
|
||||||
self.id == 0
|
self.id == 0
|
||||||
}
|
}
|
||||||
pub fn get_method(&self) -> Option<&str> {
|
pub fn get_method(&self) -> Option<&str> {
|
||||||
self.args
|
self.args
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|args| args.first())
|
.and_then(|args| args.first())
|
||||||
.and_then(|arg| arg.as_str())
|
.and_then(|arg| arg.as_str())
|
||||||
}
|
}
|
||||||
pub fn get_params(&self) -> Option<&serde_json::Value> {
|
pub fn get_params(&self) -> Option<&serde_json::Value> {
|
||||||
self.args
|
self.args
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|args| if args.len() > 1 { Some(&args[1]) } else { None })
|
.and_then(|args| if args.len() > 1 { Some(&args[1]) } else { None })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct RPCResponseDataTransport {
|
pub struct RPCResponseDataTransport {
|
||||||
pub properties: Vec<Vec<String>>,
|
pub properties: Vec<Vec<String>>,
|
||||||
pub signals: Vec<String>,
|
pub signals: Vec<String>,
|
||||||
pub methods: Vec<Vec<String>>,
|
pub methods: Vec<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct RPCResponseData {
|
pub struct RPCResponseData {
|
||||||
pub transport: RPCResponseDataTransport,
|
pub transport: RPCResponseDataTransport,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
|
#[derive(Default, Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct RPCResponse {
|
pub struct RPCResponse {
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub object: String,
|
pub object: String,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub response_type: u32,
|
pub response_type: u32,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub data: Option<RPCResponseData>,
|
pub data: Option<RPCResponseData>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub args: Option<serde_json::Value>,
|
pub args: Option<serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RPCResponse {
|
impl RPCResponse {
|
||||||
pub fn get_handshake() -> String {
|
pub fn get_handshake() -> String {
|
||||||
let resp = RPCResponse {
|
let resp = RPCResponse {
|
||||||
id: 0,
|
id: 0,
|
||||||
object: "transport".to_string(),
|
object: "transport".to_string(),
|
||||||
response_type: 3,
|
response_type: 3,
|
||||||
data: Some(RPCResponseData {
|
data: Some(RPCResponseData {
|
||||||
transport: RPCResponseDataTransport {
|
transport: RPCResponseDataTransport {
|
||||||
properties: vec![
|
properties: vec![
|
||||||
vec![],
|
vec![],
|
||||||
vec![
|
vec![
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
"shellVersion".to_string(),
|
"shellVersion".to_string(),
|
||||||
"".to_string(),
|
"".to_string(),
|
||||||
VERSION.to_string(),
|
VERSION.to_string(),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
signals: vec![],
|
signals: vec![],
|
||||||
methods: vec![vec!["onEvent".to_string(), "".to_string()]],
|
methods: vec![vec!["onEvent".to_string(), "".to_string()]],
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
serde_json::to_string(&resp).expect("Cannot build response")
|
serde_json::to_string(&resp).expect("Cannot build response")
|
||||||
}
|
}
|
||||||
pub fn response_message(msg: Option<serde_json::Value>) -> String {
|
pub fn response_message(msg: Option<serde_json::Value>) -> String {
|
||||||
let resp = RPCResponse {
|
let resp = RPCResponse {
|
||||||
id: 1,
|
id: 1,
|
||||||
object: "transport".to_string(),
|
object: "transport".to_string(),
|
||||||
response_type: 1,
|
response_type: 1,
|
||||||
args: msg,
|
args: msg,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
serde_json::to_string(&resp).expect("Cannot build response")
|
serde_json::to_string(&resp).expect("Cannot build response")
|
||||||
}
|
}
|
||||||
pub fn visibility_change(visible: bool, visibility: u32, is_full_screen: bool) -> String {
|
pub fn visibility_change(visible: bool, visibility: u32, is_full_screen: bool) -> String {
|
||||||
Self::response_message(Some(json!(["win-visibility-changed" ,{
|
Self::response_message(Some(json!(["win-visibility-changed" ,{
|
||||||
"visible": visible,
|
"visible": visible,
|
||||||
"visibility": visibility,
|
"visibility": visibility,
|
||||||
"isFullscreen": is_full_screen
|
"isFullscreen": is_full_screen
|
||||||
}])))
|
}])))
|
||||||
}
|
}
|
||||||
pub fn state_change(state: u32) -> String {
|
pub fn state_change(state: u32) -> String {
|
||||||
Self::response_message(Some(json!(["win-state-changed" ,{
|
Self::response_message(Some(json!(["win-state-changed" ,{
|
||||||
"state": state,
|
"state": state,
|
||||||
}])))
|
}])))
|
||||||
}
|
}
|
||||||
}
|
pub fn open_media(url: String) -> String {
|
||||||
|
Self::response_message(Some(json!(["open-media", url])))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,3 +14,5 @@ pub mod splash;
|
||||||
pub use splash::SplashImage;
|
pub use splash::SplashImage;
|
||||||
pub mod window_helper;
|
pub mod window_helper;
|
||||||
pub use window_helper::WindowStyle;
|
pub use window_helper::WindowStyle;
|
||||||
|
pub mod named_pipe;
|
||||||
|
pub use named_pipe::{PipeClient, PipeServer};
|
||||||
|
|
|
||||||
265
src/stremio_app/named_pipe.rs
Normal file
265
src/stremio_app/named_pipe.rs
Normal file
|
|
@ -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<HANDLE> {
|
||||||
|
let mut os_str: OsString = path.as_os_str().into();
|
||||||
|
os_str.push("\x00");
|
||||||
|
let u16_slice = os_str.encode_wide().collect::<Vec<u16>>();
|
||||||
|
|
||||||
|
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<P: AsRef<Path>>(path: P) -> io::Result<PipeClient> {
|
||||||
|
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<usize> {
|
||||||
|
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<usize> {
|
||||||
|
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<u16>,
|
||||||
|
next_pipe: Handle,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_u16s<S: AsRef<OsStr>>(s: S) -> io::Result<Vec<u16>> {
|
||||||
|
let mut maybe_result: Vec<u16> = 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<Handle> {
|
||||||
|
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<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||||
|
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<PipeClient> {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,8 +2,8 @@ use std::{cmp, mem};
|
||||||
use winapi::shared::windef::HWND;
|
use winapi::shared::windef::HWND;
|
||||||
use winapi::um::winuser::{
|
use winapi::um::winuser::{
|
||||||
GetForegroundWindow, GetSystemMetrics, GetWindowLongA, GetWindowRect, IsIconic, IsZoomed,
|
GetForegroundWindow, GetSystemMetrics, GetWindowLongA, GetWindowRect, IsIconic, IsZoomed,
|
||||||
SetWindowLongA, SetWindowPos, GWL_EXSTYLE, GWL_STYLE, HWND_NOTOPMOST, HWND_TOPMOST,
|
SetForegroundWindow, SetWindowLongA, SetWindowPos, GWL_EXSTYLE, GWL_STYLE, HWND_NOTOPMOST,
|
||||||
SM_CXSCREEN, SM_CYSCREEN, SWP_FRAMECHANGED, SWP_NOMOVE, SWP_NOSIZE, WS_CAPTION,
|
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_EX_CLIENTEDGE, WS_EX_DLGMODALFRAME, WS_EX_STATICEDGE, WS_EX_TOPMOST, WS_EX_WINDOWEDGE,
|
||||||
WS_THICKFRAME,
|
WS_THICKFRAME,
|
||||||
};
|
};
|
||||||
|
|
@ -136,4 +136,9 @@ impl WindowStyle {
|
||||||
}
|
}
|
||||||
self.ex_style = unsafe { GetWindowLongA(hwnd, GWL_EXSTYLE) };
|
self.ex_style = unsafe { GetWindowLongA(hwnd, GWL_EXSTYLE) };
|
||||||
}
|
}
|
||||||
|
pub fn set_active(&mut self, hwnd: HWND) {
|
||||||
|
unsafe {
|
||||||
|
SetForegroundWindow(hwnd);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue