diff --git a/Cargo.lock b/Cargo.lock index 3c84f83..dafcc3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -557,7 +557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d123397e75f904758fef490775a00b0ada545ab409cb0163d919799e5a30119b" dependencies = [ "autocxx-engine", - "env_logger", + "env_logger 0.9.3", "indexmap 1.9.3", "syn 2.0.111", ] @@ -1751,6 +1751,16 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" version = "0.9.3" @@ -1764,6 +1774,19 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + [[package]] name = "equator" version = "0.4.2" @@ -2955,6 +2978,30 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "jni" version = "0.21.1" @@ -4272,6 +4319,21 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "portable-atomic" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -5048,6 +5110,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shell-words" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" + [[package]] name = "shlex" version = "1.3.0" @@ -6376,16 +6444,18 @@ dependencies = [ ] [[package]] -name = "wayvr_ipc" +name = "wayvrctl" version = "0.1.0" -source = "git+https://github.com/olekolek1000/wayvr-ipc.git?rev=6d253ef9e36db0f181566030a4990454ecb60395#6d253ef9e36db0f181566030a4990454ecb60395" dependencies = [ "anyhow", - "bytes", + "clap", + "env_logger 0.11.8", "log", "serde", "serde_json", - "smallvec", + "shell-words", + "tokio", + "wayvr-ipc", ] [[package]] @@ -7055,7 +7125,6 @@ dependencies = [ "wayland-client", "wayland-egl", "wayvr-ipc", - "wayvr_ipc", "wgui", "winit", "wlx-capture", diff --git a/Cargo.toml b/Cargo.toml index 5ec137c..e718939 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,17 +18,21 @@ members = [ "wlx-capture", "dash-frontend", "wayvr-ipc", + "wayvrctl", ] resolver = "3" [workspace.dependencies] anyhow = "1.0.100" glam = { version = "0.30.9", features = ["mint", "serde"] } +clap = { version = "4.5.53", features = ["derive"] } idmap = "0.2.2" idmap-derive = "0.2.22" log = "0.4.29" regex = "1.12.2" rust-embed = "8.9.0" +serde = { version = "1", features = ["derive"] } +serde_json = "1.0.145" slotmap = "1.1.1" vulkano = { version = "0.35.2", default-features = false, features = [ "macros", diff --git a/dash-frontend/Cargo.toml b/dash-frontend/Cargo.toml index 6211da2..463d61c 100644 --- a/dash-frontend/Cargo.toml +++ b/dash-frontend/Cargo.toml @@ -7,12 +7,12 @@ edition = "2024" anyhow.workspace = true wgui = { path = "../wgui/" } glam = { workspace = true, features = ["mint", "serde"] } -log = { workspace = true } -rust-embed = { workspace = true } +log.workspace = true +rust-embed.workspace = true chrono = "0.4.42" gio = "0.21.5" gtk = "0.18.2" -serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.145" +serde.workspace = true +serde_json.workspace = true wlx-common = { path = "../wlx-common" } wayvr_ipc = { workspace = true } diff --git a/dash-frontend/assets/lang/ja.json b/dash-frontend/assets/lang/ja.json index 160ea57..e357ec4 100644 --- a/dash-frontend/assets/lang/ja.json +++ b/dash-frontend/assets/lang/ja.json @@ -1,70 +1,70 @@ { - "HOME_SCREEN": "ホーム", - "MONADO_RUNTIME": "「Monado」ランタイム", - "APPLICATIONS": "アプリケーション", - "GAMES": "ゲーム", - "SETTINGS": "設定", - "PROCESSES": "プロセス", - "HELLO_USER": "こんにちは、{USER}!", - "GENERAL_SETTINGS": "全般設定", - "APPLICATION_LAUNCHER": "アプリケーションランチャー", - "APP_SETTINGS": { - "HIDE_USERNAME": "ユーザー名を表示しない", - "OPAQUE_BACKGROUND": "不透明な背景", - "RUN_IN_XWAYLAND_MODE_BY_DEFAULT": "XWaylandモードでデフォルトで実行する", - "WLX_OVERLAY_S_SETTINGS": "WlxOverlay-Sの設定", - "HEADSET_SETTINGS": "ヘッドセット設定", - "BRIGHTNESS": "明るさ", - "WLX": { - "NOTIFICATIONS_ENABLED": "通知", - "NOTIFICATIONS_SOUND_ENABLED": "通知音", - "KEYBOARD_SOUND_ENABLED": "キーボード音", - "BLOCK_GAME_INPUT": "ゲーム入力をブロック", - "SPACE_DRAG_MULTIPLIER": "スペースドラッグ乗数", - "SPACE_DRAG_ROTATION_ENABLED": "スペースドラッグでの回転", - "SHOW_SKYBOX": "スカイボックス", - "ENABLE_PASSTHROUGH": "パススルー" - }, - "RESTART_SOFTWARE": "ソフトウェアを再起動" - }, - "HELLO": "こんにちは!", - "AUDIO": { - "VOLUME": "音量", - "SETTINGS": "オーディオ設定", - "AUTO_SWITCH_TO_VR_AUDIO": "VRオーディオに自動切り替え", - "SPEAKERS": "スピーカー", - "MICROPHONES": "マイク", - "CARDS": "カード", - "SELECT_AUDIO_CARD_PROFILE": "オーディオカードプロファイルを選択", - "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "VRスピーカーが見つかりませんでした。手動で切り替えてください。", - "NO_VR_MICROPHONE_SWITCH_MANUALLY": "VRマイクが見つかりませんでした。手動で切り替えてください。", - "FAILED_TO_SWITCH_MICROPHONE": "マイクの切り替えに失敗しました", - "MICROPHONE_SET_SUCCESSFULLY": "マイクの設定が完了しました", - "SPEAKERS_SET_SUCCESSFULLY": "スピーカーを設定しました", - "DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED": "デバイスが見つかり、初期化されましたが、切り替えられていません" - }, - "ACTIONS": { - "RECENTER_PLAYSPACE": "プレイスペースを再中央" - }, - "LIST_OF_DISPLAYS": "ディスプレイリスト", - "LIST_OF_PROCESSES": "プロセスのリスト", - "NO_DISPLAYS_FOUND": "ディスプレイが見つかりません", - "ADD_DISPLAY": "ディスプレイを追加", - "POPUP_ADD_DISPLAY": { - "RESOLUTION": "解像度" - }, - "WIDTH": "幅", - "HEIGHT": "高さ", - "DISPLAY_PORTRAIT_MODE": "縦向きモード", - "HIDE": "隠す", - "REMOVE": "削除", - "SHOW": "表示", - "DISPLAY_OPTIONS": "表示オプション", - "PROCESS_LIST": { - "NO_PROCESSES_FOUND": "プロセスが見つかりませんでした", - "LOCATED_ON": "に", - "TERMINATE_PROCESS_NAMED_X": "プロセス \"{PROCESS_NAME}\" を終了します" - }, - "FAILED_TO_LAUNCH_APPLICATION": "アプリケーションの起動に失敗しました:", - "APPLICATION_LAUNCHED_ON": "{DISPLAY_NAME}でアプリケーションが起動しました。" -} \ No newline at end of file + "HOME_SCREEN": "ホーム", + "MONADO_RUNTIME": "「Monado」ランタイム", + "APPLICATIONS": "アプリケーション", + "GAMES": "ゲーム", + "SETTINGS": "設定", + "PROCESSES": "プロセス", + "HELLO_USER": "こんにちは、{USER}!", + "GENERAL_SETTINGS": "全般設定", + "APPLICATION_LAUNCHER": "アプリケーションランチャー", + "APP_SETTINGS": { + "HIDE_USERNAME": "ユーザー名を表示しない", + "OPAQUE_BACKGROUND": "不透明な背景", + "RUN_IN_XWAYLAND_MODE_BY_DEFAULT": "XWaylandモードでデフォルトで実行する", + "WLX_OVERLAY_S_SETTINGS": "WlxOverlay-Sの設定", + "HEADSET_SETTINGS": "ヘッドセット設定", + "BRIGHTNESS": "明るさ", + "WLX": { + "NOTIFICATIONS_ENABLED": "通知", + "NOTIFICATIONS_SOUND_ENABLED": "通知音", + "KEYBOARD_SOUND_ENABLED": "キーボード音", + "BLOCK_GAME_INPUT": "ゲーム入力をブロック", + "SPACE_DRAG_MULTIPLIER": "スペースドラッグ乗数", + "SPACE_DRAG_ROTATION_ENABLED": "スペースドラッグでの回転", + "SHOW_SKYBOX": "スカイボックス", + "ENABLE_PASSTHROUGH": "パススルー" + }, + "RESTART_SOFTWARE": "ソフトウェアを再起動" + }, + "HELLO": "こんにちは!", + "AUDIO": { + "VOLUME": "音量", + "SETTINGS": "オーディオ設定", + "AUTO_SWITCH_TO_VR_AUDIO": "VRオーディオに自動切り替え", + "SPEAKERS": "スピーカー", + "MICROPHONES": "マイク", + "CARDS": "カード", + "SELECT_AUDIO_CARD_PROFILE": "オーディオカードプロファイルを選択", + "NO_VR_SPEAKERS_FOUND_SWITCH_MANUALLY": "VRスピーカーが見つかりませんでした。手動で切り替えてください。", + "NO_VR_MICROPHONE_SWITCH_MANUALLY": "VRマイクが見つかりませんでした。手動で切り替えてください。", + "FAILED_TO_SWITCH_MICROPHONE": "マイクの切り替えに失敗しました", + "MICROPHONE_SET_SUCCESSFULLY": "マイクの設定が完了しました", + "SPEAKERS_SET_SUCCESSFULLY": "スピーカーを設定しました", + "DEVICE_FOUND_AND_INITIALIZED_BUT_NOT_SWITCHED": "デバイスが見つかり、初期化されましたが、切り替えられていません" + }, + "ACTIONS": { + "RECENTER_PLAYSPACE": "プレイスペースを再中央" + }, + "LIST_OF_DISPLAYS": "ディスプレイリスト", + "LIST_OF_PROCESSES": "プロセスのリスト", + "NO_DISPLAYS_FOUND": "ディスプレイが見つかりません", + "ADD_DISPLAY": "ディスプレイを追加", + "POPUP_ADD_DISPLAY": { + "RESOLUTION": "解像度" + }, + "WIDTH": "幅", + "HEIGHT": "高さ", + "DISPLAY_PORTRAIT_MODE": "縦向きモード", + "HIDE": "隠す", + "REMOVE": "削除", + "SHOW": "表示", + "DISPLAY_OPTIONS": "表示オプション", + "PROCESS_LIST": { + "NO_PROCESSES_FOUND": "プロセスが見つかりませんでした", + "LOCATED_ON": "に", + "TERMINATE_PROCESS_NAMED_X": "プロセス \"{PROCESS_NAME}\" を終了します" + }, + "FAILED_TO_LAUNCH_APPLICATION": "アプリケーションの起動に失敗しました:", + "APPLICATION_LAUNCHED_ON": "{DISPLAY_NAME}でアプリケーションが起動しました。" +} diff --git a/wayvr-ipc/Cargo.toml b/wayvr-ipc/Cargo.toml index 20e9928..b392eb4 100644 --- a/wayvr-ipc/Cargo.toml +++ b/wayvr-ipc/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] bytes = "1.9.0" smallvec = "1.13.2" -serde = { version = "1", features = ["derive"] } +serde.workspace = true anyhow = "1.0.93" log = "0.4.22" @@ -14,7 +14,7 @@ log = "0.4.22" interprocess = { version = "2.2.2", features = ["tokio"], optional = true } tokio = { version = "1.43.1", features = ["macros"], optional = true } tokio-util = { version = "0.7.13", optional = true } -serde_json = "1.0.135" +serde_json.workspace = true [features] default = ["client"] diff --git a/wayvr-ipc/src/client.rs b/wayvr-ipc/src/client.rs index 46cd0d2..bba9a98 100644 --- a/wayvr-ipc/src/client.rs +++ b/wayvr-ipc/src/client.rs @@ -1,8 +1,7 @@ use bytes::BufMut; use interprocess::local_socket::{ - self, - tokio::{prelude::*, Stream}, - GenericNamespaced, + self, GenericNamespaced, + tokio::{Stream, prelude::*}, }; use serde::Serialize; use smallvec::SmallVec; @@ -486,6 +485,14 @@ impl WayVRClient { send_only!(client, &PacketClient::WlxHaptics(params)); Ok(()) } + + pub async fn fn_wlx_modify_panel( + client: WayVRClientMutex, + params: packet_client::WlxModifyPanelParams, + ) -> anyhow::Result<()> { + send_only!(client, &PacketClient::WlxModifyPanel(params)); + Ok(()) + } } impl Drop for WayVRClient { diff --git a/wayvr-ipc/src/packet_client.rs b/wayvr-ipc/src/packet_client.rs index 5378f93..83d7e91 100644 --- a/wayvr-ipc/src/packet_client.rs +++ b/wayvr-ipc/src/packet_client.rs @@ -48,6 +48,22 @@ pub struct WlxHapticsParams { pub frequency: f32, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum WlxModifyPanelCommand { + SetText(String), + SetColor(String), + SetImage(String), + SetVisible(bool), + SetStickyState(bool), +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct WlxModifyPanelParams { + pub overlay: String, + pub element: String, + pub command: WlxModifyPanelCommand, +} + #[derive(Debug, Serialize, Deserialize)] pub enum PacketClient { Handshake(Handshake), @@ -68,4 +84,5 @@ pub enum PacketClient { WvrProcessTerminate(packet_server::WvrProcessHandle), WlxHaptics(WlxHapticsParams), WlxInputState(Serial), + WlxModifyPanel(WlxModifyPanelParams), } diff --git a/wayvr-ipc/src/util/handle.rs b/wayvr-ipc/src/util/handle.rs index a408d41..63aaa7b 100644 --- a/wayvr-ipc/src/util/handle.rs +++ b/wayvr-ipc/src/util/handle.rs @@ -1,167 +1,164 @@ #[macro_export] macro_rules! gen_id { - ( + ( $container_name:ident, $instance_name:ident, $cell_name:ident, $handle_name:ident) => { - //ThingCell - pub struct $cell_name { - pub obj: $instance_name, - pub generation: u64, - } + //ThingCell + pub struct $cell_name { + pub obj: $instance_name, + pub generation: u64, + } - //ThingVec - pub struct $container_name { - // Vec> - pub vec: Vec>, + //ThingVec + pub struct $container_name { + // Vec> + pub vec: Vec>, - cur_generation: u64, - } + cur_generation: u64, + } - //ThingHandle - #[derive(Default, Clone, Copy, PartialEq, Hash, Eq)] - pub struct $handle_name { - idx: u32, - generation: u64, - } + //ThingHandle + #[derive(Default, Clone, Copy, PartialEq, Hash, Eq)] + pub struct $handle_name { + idx: u32, + generation: u64, + } - #[allow(dead_code)] - impl $handle_name { - pub fn reset(&mut self) { - self.generation = 0; - } + #[allow(dead_code)] + impl $handle_name { + pub fn reset(&mut self) { + self.generation = 0; + } - pub fn is_set(&self) -> bool { - self.generation > 0 - } + pub fn is_set(&self) -> bool { + self.generation > 0 + } - pub fn id(&self) -> u32 { - self.idx - } + pub fn id(&self) -> u32 { + self.idx + } - pub fn new(idx: u32, generation: u64) -> Self { - Self { idx, generation } - } - } + pub fn new(idx: u32, generation: u64) -> Self { + Self { idx, generation } + } + } - //ThingVec - #[allow(dead_code)] - impl $container_name { - pub fn new() -> Self { - Self { - vec: Vec::new(), - cur_generation: 0, - } - } + //ThingVec + #[allow(dead_code)] + impl $container_name { + pub fn new() -> Self { + Self { + vec: Vec::new(), + cur_generation: 0, + } + } - pub fn iter(&self, callback: &dyn Fn($handle_name, &$instance_name)) { - for (idx, opt_cell) in self.vec.iter().enumerate() { - if let Some(cell) = opt_cell { - let handle = $container_name::get_handle(&cell, idx); - callback(handle, &cell.obj); - } - } - } + pub fn iter(&self, callback: &dyn Fn($handle_name, &$instance_name)) { + for (idx, opt_cell) in self.vec.iter().enumerate() { + if let Some(cell) = opt_cell { + let handle = $container_name::get_handle(&cell, idx); + callback(handle, &cell.obj); + } + } + } - pub fn iter_mut( - &mut self, - callback: &mut dyn FnMut($handle_name, &mut $instance_name), - ) { - for (idx, opt_cell) in self.vec.iter_mut().enumerate() { - if let Some(cell) = opt_cell { - let handle = $container_name::get_handle(&cell, idx); - callback(handle, &mut cell.obj); - } - } - } + pub fn iter_mut(&mut self, callback: &mut dyn FnMut($handle_name, &mut $instance_name)) { + for (idx, opt_cell) in self.vec.iter_mut().enumerate() { + if let Some(cell) = opt_cell { + let handle = $container_name::get_handle(&cell, idx); + callback(handle, &mut cell.obj); + } + } + } - pub fn get_handle(cell: &$cell_name, idx: usize) -> $handle_name { - $handle_name { - idx: idx as u32, - generation: cell.generation, - } - } + pub fn get_handle(cell: &$cell_name, idx: usize) -> $handle_name { + $handle_name { + idx: idx as u32, + generation: cell.generation, + } + } - fn find_unused_idx(&mut self) -> Option { - for (num, obj) in self.vec.iter().enumerate() { - if obj.is_none() { - return Some(num as u32); - } - } - None - } + fn find_unused_idx(&mut self) -> Option { + for (num, obj) in self.vec.iter().enumerate() { + if obj.is_none() { + return Some(num as u32); + } + } + None + } - pub fn add(&mut self, obj: $instance_name) -> $handle_name { - self.cur_generation += 1; - let generation = self.cur_generation; + pub fn add(&mut self, obj: $instance_name) -> $handle_name { + self.cur_generation += 1; + let generation = self.cur_generation; - let unused_idx = self.find_unused_idx(); + let unused_idx = self.find_unused_idx(); - let idx = if let Some(idx) = unused_idx { - idx - } else { - self.vec.len() as u32 - }; + let idx = if let Some(idx) = unused_idx { + idx + } else { + self.vec.len() as u32 + }; - let handle = $handle_name { idx, generation }; + let handle = $handle_name { idx, generation }; - let cell = $cell_name { obj, generation }; + let cell = $cell_name { obj, generation }; - if let Some(idx) = unused_idx { - self.vec[idx as usize] = Some(cell); - } else { - self.vec.push(Some(cell)) - } + if let Some(idx) = unused_idx { + self.vec[idx as usize] = Some(cell); + } else { + self.vec.push(Some(cell)) + } - handle - } + handle + } - pub fn remove(&mut self, handle: &$handle_name) { - // Out of bounds, ignore - if handle.idx as usize >= self.vec.len() { - return; - } + pub fn remove(&mut self, handle: &$handle_name) { + // Out of bounds, ignore + if handle.idx as usize >= self.vec.len() { + return; + } - // Remove only if the generation matches - if let Some(cell) = &self.vec[handle.idx as usize] { - if cell.generation == handle.generation { - self.vec[handle.idx as usize] = None; - } - } - } + // Remove only if the generation matches + if let Some(cell) = &self.vec[handle.idx as usize] { + if cell.generation == handle.generation { + self.vec[handle.idx as usize] = None; + } + } + } - pub fn get(&self, handle: &$handle_name) -> Option<&$instance_name> { - // Out of bounds, ignore - if handle.idx as usize >= self.vec.len() { - return None; - } + pub fn get(&self, handle: &$handle_name) -> Option<&$instance_name> { + // Out of bounds, ignore + if handle.idx as usize >= self.vec.len() { + return None; + } - if let Some(cell) = &self.vec[handle.idx as usize] { - if cell.generation == handle.generation { - return Some(&cell.obj); - } - } + if let Some(cell) = &self.vec[handle.idx as usize] { + if cell.generation == handle.generation { + return Some(&cell.obj); + } + } - None - } + None + } - pub fn get_mut(&mut self, handle: &$handle_name) -> Option<&mut $instance_name> { - // Out of bounds, ignore - if handle.idx as usize >= self.vec.len() { - return None; - } + pub fn get_mut(&mut self, handle: &$handle_name) -> Option<&mut $instance_name> { + // Out of bounds, ignore + if handle.idx as usize >= self.vec.len() { + return None; + } - if let Some(cell) = &mut self.vec[handle.idx as usize] { - if cell.generation == handle.generation { - return Some(&mut cell.obj); - } - } + if let Some(cell) = &mut self.vec[handle.idx as usize] { + if cell.generation == handle.generation { + return Some(&mut cell.obj); + } + } - None - } - } - }; + None + } + } + }; } /* Example usage: diff --git a/wayvrctl/Cargo.toml b/wayvrctl/Cargo.toml new file mode 100644 index 0000000..69b520b --- /dev/null +++ b/wayvrctl/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "wayvrctl" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow.workspace = true +log.workspace = true +clap.workspace = true +serde.workspace = true +serde_json.workspace = true +env_logger = "0.11.8" +tokio = "1.48.0" +wayvr-ipc = { path = "../wayvr-ipc" } +shell-words = "1.1.1" diff --git a/wayvrctl/src/helper.rs b/wayvrctl/src/helper.rs new file mode 100644 index 0000000..19c9197 --- /dev/null +++ b/wayvrctl/src/helper.rs @@ -0,0 +1,267 @@ +use std::collections::HashMap; + +use anyhow::Context; +use serde::Serialize; +use wayvr_ipc::{ + client::{WayVRClient, WayVRClientMutex}, + ipc, packet_client, packet_server, +}; + +pub struct WayVRClientState { + pub wayvr_client: WayVRClientMutex, + pub serial_generator: ipc::SerialGenerator, + pub pretty_print: bool, +} + +fn handle_empty_result(result: anyhow::Result<()>) { + if let Err(e) = result { + log::error!("{e:?}"); + } +} + +fn handle_result(pretty_print: bool, result: anyhow::Result) { + match result { + Ok(t) => { + let maybe_json = if pretty_print { + serde_json::to_string_pretty(&t) + } else { + serde_json::to_string(&t) + }; + + match maybe_json { + Ok(json_string) => println!("{}", json_string), + Err(e) => log::error!("Failed to serialize JSON: {e:?}"), + } + } + Err(e) => log::error!("{e:?}"), + } +} + +pub async fn wvr_display_create( + state: &mut WayVRClientState, + width: u16, + height: u16, + name: String, + scale: Option, + attach_to: packet_client::AttachTo, +) { + handle_result( + state.pretty_print, + WayVRClient::fn_wvr_display_create( + state.wayvr_client.clone(), + state.serial_generator.increment_get(), + packet_client::WvrDisplayCreateParams { + width, + height, + name, + scale, + attach_to, + }, + ) + .await + .context("failed to create display"), + ); +} + +pub async fn wvr_display_list(state: &mut WayVRClientState) { + handle_result( + state.pretty_print, + WayVRClient::fn_wvr_display_list( + state.wayvr_client.clone(), + state.serial_generator.increment_get(), + ) + .await + .context("failed to fetch displays"), + ); +} + +pub async fn wvr_display_get( + state: &mut WayVRClientState, + handle: packet_server::WvrDisplayHandle, +) { + handle_result( + state.pretty_print, + WayVRClient::fn_wvr_display_get( + state.wayvr_client.clone(), + state.serial_generator.increment_get(), + handle, + ) + .await + .context("failed to fetch display"), + ); +} + +pub async fn wvr_display_window_list( + state: &mut WayVRClientState, + handle: packet_server::WvrDisplayHandle, +) { + handle_result( + state.pretty_print, + WayVRClient::fn_wvr_display_window_list( + state.wayvr_client.clone(), + state.serial_generator.increment_get(), + handle, + ) + .await + .context("failed to list window displays"), + ); +} + +pub async fn wvr_display_remove( + state: &mut WayVRClientState, + handle: packet_server::WvrDisplayHandle, +) { + handle_result( + state.pretty_print, + WayVRClient::fn_wvr_display_remove( + state.wayvr_client.clone(), + state.serial_generator.increment_get(), + handle, + ) + .await + .context("failed to remove display"), + ); +} + +pub async fn wvr_display_set_visible( + state: &mut WayVRClientState, + handle: packet_server::WvrDisplayHandle, + visible: bool, +) { + handle_empty_result( + WayVRClient::fn_wvr_display_set_visible(state.wayvr_client.clone(), handle, visible) + .await + .context("failed to set display visibility"), + ) +} + +pub async fn wvr_window_set_visible( + state: &mut WayVRClientState, + handle: packet_server::WvrWindowHandle, + visible: bool, +) { + handle_empty_result( + WayVRClient::fn_wvr_window_set_visible(state.wayvr_client.clone(), handle, visible) + .await + .context("failed to set window visibility"), + ) +} + +pub async fn wvr_process_get( + state: &mut WayVRClientState, + handle: packet_server::WvrProcessHandle, +) { + handle_result( + state.pretty_print, + WayVRClient::fn_wvr_process_get( + state.wayvr_client.clone(), + state.serial_generator.increment_get(), + handle, + ) + .await + .context("failed to get process"), + ); +} + +pub async fn wvr_process_list(state: &mut WayVRClientState) { + handle_result( + state.pretty_print, + WayVRClient::fn_wvr_process_list( + state.wayvr_client.clone(), + state.serial_generator.increment_get(), + ) + .await + .context("failed to list processes"), + ) +} + +pub async fn wvr_process_terminate( + state: &mut WayVRClientState, + handle: packet_server::WvrProcessHandle, +) { + handle_empty_result( + WayVRClient::fn_wvr_process_terminate(state.wayvr_client.clone(), handle) + .await + .context("failed to terminate process"), + ) +} + +pub async fn wvr_process_launch( + state: &mut WayVRClientState, + exec: String, + name: String, + env: Vec, + target_display: packet_server::WvrDisplayHandle, + args: String, + userdata: HashMap, +) { + handle_result( + state.pretty_print, + WayVRClient::fn_wvr_process_launch( + state.wayvr_client.clone(), + state.serial_generator.increment_get(), + packet_client::WvrProcessLaunchParams { + env, + exec, + name, + target_display, + args, + userdata, + }, + ) + .await + .context("failed to launch process"), + ) +} + +pub async fn wlx_haptics( + state: &mut WayVRClientState, + intensity: f32, + duration: f32, + frequency: f32, +) { + handle_empty_result( + WayVRClient::fn_wlx_haptics( + state.wayvr_client.clone(), + packet_client::WlxHapticsParams { + intensity, + duration, + frequency, + }, + ) + .await + .context("failed to trigger haptics"), + ) +} + +pub async fn wlx_panel_modify( + state: &mut WayVRClientState, + overlay: String, + element: String, + command: packet_client::WlxModifyPanelCommand, +) { + handle_empty_result( + WayVRClient::fn_wlx_modify_panel( + state.wayvr_client.clone(), + packet_client::WlxModifyPanelParams { + overlay, + element, + command, + }, + ) + .await + .context("failed to modify panel"), + ) +} + +pub async fn wlx_input_state(state: &mut WayVRClientState) { + handle_result( + state.pretty_print, + WayVRClient::fn_wlx_input_state( + state.wayvr_client.clone(), + state.serial_generator.increment_get(), + ) + .await + .context("failed to get input state"), + ) +} diff --git a/wayvrctl/src/main.rs b/wayvrctl/src/main.rs new file mode 100644 index 0000000..084fa87 --- /dev/null +++ b/wayvrctl/src/main.rs @@ -0,0 +1,327 @@ +use std::{ + collections::HashMap, + process::{self, ExitCode}, + time::Duration, +}; + +use anyhow::Context; +use clap::Parser; +use env_logger::Env; +use wayvr_ipc::{client::WayVRClient, ipc, packet_client}; + +use crate::helper::{ + WayVRClientState, wlx_haptics, wlx_input_state, wlx_panel_modify, wvr_display_create, + wvr_display_get, wvr_display_list, wvr_display_remove, wvr_display_set_visible, + wvr_display_window_list, wvr_process_get, wvr_process_launch, wvr_process_list, + wvr_process_terminate, wvr_window_set_visible, +}; + +mod helper; + +#[tokio::main(flavor = "current_thread")] +async fn main() -> ExitCode { + env_logger::init_from_env(Env::default().default_filter_or("info")); + let args = Args::parse(); + + let mut state = WayVRClientState { + wayvr_client: WayVRClient::new(&format!("wayvrctl-{}", process::id())) + .await + .inspect_err(|e| { + log::error!("Failed to initialize WayVR connection: {e:?}"); + process::exit(1); + }) + .unwrap(), + serial_generator: ipc::SerialGenerator::new(), + pretty_print: args.pretty, + }; + + let maybe_err = if let Subcommands::Batch { fail_fast } = args.command { + run_batch(&mut state, fail_fast).await + } else { + run_once(&mut state, args).await + }; + + if let Err(e) = maybe_err { + log::error!("{e:?}"); + return ExitCode::FAILURE; + } else { + std::thread::sleep(Duration::from_millis(20)); + } + + ExitCode::SUCCESS +} + +async fn run_batch(state: &mut WayVRClientState, fail_fast: bool) -> anyhow::Result<()> { + let stdin = std::io::stdin(); + + for (line_no, line) in stdin.lines().enumerate() { + let line = line.context("error reading stdin")?; + + if line.trim().is_empty() || line.trim_start().starts_with('#') { + continue; + } + + if let Err(e) = parse_run_line(state, &line) + .await + .with_context(|| format!("error on line {}", line_no + 1)) + { + if fail_fast { + return Err(e); + } else { + log::error!("{e:?}"); + } + } + } + Ok(()) +} + +async fn parse_run_line(state: &mut WayVRClientState, line: &str) -> anyhow::Result<()> { + let mut argv = shell_words::split(&line).with_context(|| format!("parse error"))?; + + // clap expects argv[0] to be the binary name + argv.insert(0, env!("CARGO_PKG_NAME").to_string()); + + let args = Args::try_parse_from(argv).with_context(|| format!("invalid arguments"))?; + run_once(state, args).await?; + + Ok(()) +} + +async fn run_once(state: &mut WayVRClientState, args: Args) -> anyhow::Result<()> { + match args.command { + Subcommands::Batch { .. } => { + log::warn!("Ignoring recursive batch command"); + } + Subcommands::InputState => { + wlx_input_state(state).await; + } + Subcommands::DisplayCreate { + width, + height, + name, + scale, + } => { + wvr_display_create( + state, + width, + height, + name, + scale, + packet_client::AttachTo::None, + ) + .await; + } + Subcommands::DisplayList => { + wvr_display_list(state).await; + } + Subcommands::DisplayGet { handle } => { + let handle = serde_json::from_str(&handle).context("Invalid handle")?; + wvr_display_get(state, handle).await; + } + Subcommands::DisplayWindowList { handle } => { + let handle = serde_json::from_str(&handle).context("Invalid handle")?; + wvr_display_window_list(state, handle).await; + } + Subcommands::DisplayRemove { handle } => { + let handle = serde_json::from_str(&handle).context("Invalid handle")?; + wvr_display_remove(state, handle).await; + } + Subcommands::DisplaySetVisible { + handle, + visible_0_or_1, + } => { + let handle = serde_json::from_str(&handle).context("Invalid handle")?; + wvr_display_set_visible(state, handle, visible_0_or_1 != 0).await; + } + Subcommands::WindowSetVisible { + handle, + visible_0_or_1, + } => { + let handle = serde_json::from_str(&handle).context("Invalid handle")?; + wvr_window_set_visible(state, handle, visible_0_or_1 != 0).await; + } + Subcommands::ProcessGet { handle } => { + let handle = serde_json::from_str(&handle).context("Invalid handle")?; + wvr_process_get(state, handle).await; + } + Subcommands::ProcessList => { + wvr_process_list(state).await; + } + Subcommands::ProcessTerminate { handle } => { + let handle = serde_json::from_str(&handle).context("Invalid handle")?; + wvr_process_terminate(state, handle).await; + } + Subcommands::ProcessLaunch { + exec, + name, + env, + target_display, + args, + } => { + let handle = serde_json::from_str(&target_display).context("Invalid target_display")?; + wvr_process_launch(state, exec, name, env, handle, args, HashMap::new()).await; + } + Subcommands::Haptics { + intensity, + duration, + frequency, + } => { + wlx_haptics(state, intensity, duration, frequency).await; + } + Subcommands::PanelModify { + overlay, + element, + command, + } => { + let command = match command { + SubcommandPanelModify::SetText { text } => { + packet_client::WlxModifyPanelCommand::SetText(text.join(" ")) + } + SubcommandPanelModify::SetColor { hex_color } => { + packet_client::WlxModifyPanelCommand::SetColor(hex_color) + } + SubcommandPanelModify::SetImage { absolute_path } => { + packet_client::WlxModifyPanelCommand::SetImage(absolute_path) + } + SubcommandPanelModify::SetVisible { visible_0_or_1 } => { + packet_client::WlxModifyPanelCommand::SetVisible(visible_0_or_1 != 0) + } + SubcommandPanelModify::SetStickyState { + sticky_state_0_or_1, + } => packet_client::WlxModifyPanelCommand::SetStickyState(sticky_state_0_or_1 != 0), + }; + + wlx_panel_modify(state, overlay, element, command).await; + } + } + Ok(()) +} + +/// A command-line interface for WayVR IPC +#[derive(clap::Parser, Debug)] +#[command(version, about, long_about = None)] +struct Args { + /// The command to run + #[command(subcommand)] + command: Subcommands, + + /// Pretty-print JSON output + #[arg(short, long)] + pretty: bool, +} + +#[derive(clap::Parser, Debug)] +enum Subcommands { + /// Read commands from stdout, one per line. + Batch { + /// Stop on the first error + #[arg(short, long)] + fail_fast: bool, + }, + /// Get the positions of HMD & controllers + InputState, + /// Create a new WayVR display + DisplayCreate { + width: u16, + height: u16, + name: String, + #[arg(short, long)] + scale: Option, + //attach_to: packet_client::AttachTo, + }, + /// List WayVR displays + DisplayList, + /// Retrieve information about a single WayVR display + DisplayGet { + /// A display handle JSON returned by DisplayList or DisplayCreate + handle: String, + }, + /// List windows attached to a WayVR display + DisplayWindowList { + /// A display handle JSON returned by DisplayList or DisplayCreate + handle: String, + }, + /// Delete a WayVR display + DisplayRemove { + /// A display handle JSON returned by DisplayList or DisplayCreate + handle: String, + }, + /// Change the visibility of a WayVR display + DisplaySetVisible { + /// A display handle JSON returned by DisplayList or DisplayCreate + handle: String, + visible_0_or_1: u8, + }, + + // DisplaySetLayout skipped + /// Change the visibility of a window on a WayVR display + WindowSetVisible { + /// A JSON window handle returned by DisplayWindowList + handle: String, + visible_0_or_1: u8, + }, + /// Retrieve information about a WayVR-managed process + ProcessGet { + /// A JSON process handle returned by ProcessList or ProcessLaunch + handle: String, + }, + /// List all processes managed by WayVR + ProcessList, + /// Terminate a WayVR-managed process + ProcessTerminate { + /// A JSON process handle returned by ProcessList or ProcessLaunch + handle: String, + }, + /// Launch a new process inside WayVR + ProcessLaunch { + exec: String, + name: String, + env: Vec, + /// A display handle JSON returned by DisplayList or DisplayCreate + target_display: String, + args: String, + }, + /// Trigger haptics on the user's controller + Haptics { + #[arg(short, long, default_value = "0.25")] + intensity: f32, + #[arg(short, long, default_value = "0.1")] + duration: f32, + #[arg(short, long, default_value = "0.1")] + frequency: f32, + }, + /// Apply a modification to a panel element + PanelModify { + /// The name of the overlay (XML file name without extension) + overlay: String, + /// The id of the element to modify, as set in the XML + element: String, + /// Command to execute + #[command(subcommand)] + command: SubcommandPanelModify, + }, +} + +#[derive(clap::Parser, Debug)] +enum SubcommandPanelModify { + /// Set the text of a