fix(launcher): bind swap keys and avoid child pid errors
This commit is contained in:
parent
dc0efc8f1a
commit
0ab5ce82ed
@ -594,20 +594,114 @@ fn quick_toggle_key_from_env() -> Option<KeyCode> {
|
|||||||
/// Parses a launcher/operator key alias into an evdev key code.
|
/// Parses a launcher/operator key alias into an evdev key code.
|
||||||
fn parse_quick_toggle_key(raw: &str) -> Option<KeyCode> {
|
fn parse_quick_toggle_key(raw: &str) -> Option<KeyCode> {
|
||||||
let normalized = raw.trim().to_ascii_lowercase();
|
let normalized = raw.trim().to_ascii_lowercase();
|
||||||
|
if matches!(normalized.as_str(), "" | "off" | "none" | "disabled") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(letter) = parse_quick_toggle_letter(&normalized) {
|
||||||
|
return Some(letter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(digit) = parse_quick_toggle_digit(&normalized) {
|
||||||
|
return Some(digit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(function) = parse_quick_toggle_function_key(&normalized) {
|
||||||
|
return Some(function);
|
||||||
|
}
|
||||||
|
|
||||||
match normalized.as_str() {
|
match normalized.as_str() {
|
||||||
"" | "off" | "none" | "disabled" => None,
|
|
||||||
"scrolllock" | "scroll_lock" | "scroll-lock" => Some(KeyCode::KEY_SCROLLLOCK),
|
"scrolllock" | "scroll_lock" | "scroll-lock" => Some(KeyCode::KEY_SCROLLLOCK),
|
||||||
"sysrq" | "sysreq" | "prtsc" | "printscreen" | "print_screen" | "print-screen" => {
|
"sysrq" | "sysreq" | "prtsc" | "printscreen" | "print_screen" | "print-screen" => {
|
||||||
Some(KeyCode::KEY_SYSRQ)
|
Some(KeyCode::KEY_SYSRQ)
|
||||||
}
|
}
|
||||||
"pause" | "pausebreak" | "pause_break" | "pause-break" => Some(KeyCode::KEY_PAUSE),
|
"pause" | "pausebreak" | "pause_break" | "pause-break" => Some(KeyCode::KEY_PAUSE),
|
||||||
"f12" => Some(KeyCode::KEY_F12),
|
"escape" | "esc" => Some(KeyCode::KEY_ESC),
|
||||||
"f11" => Some(KeyCode::KEY_F11),
|
"tab" => Some(KeyCode::KEY_TAB),
|
||||||
"f10" => Some(KeyCode::KEY_F10),
|
"capslock" | "caps_lock" | "caps-lock" => Some(KeyCode::KEY_CAPSLOCK),
|
||||||
|
"backspace" | "back_space" | "back-space" => Some(KeyCode::KEY_BACKSPACE),
|
||||||
|
"space" | "spacebar" => Some(KeyCode::KEY_SPACE),
|
||||||
|
"enter" | "return" => Some(KeyCode::KEY_ENTER),
|
||||||
|
"insert" => Some(KeyCode::KEY_INSERT),
|
||||||
|
"delete" | "del" => Some(KeyCode::KEY_DELETE),
|
||||||
|
"home" => Some(KeyCode::KEY_HOME),
|
||||||
|
"end" => Some(KeyCode::KEY_END),
|
||||||
|
"pageup" | "page_up" | "page-up" => Some(KeyCode::KEY_PAGEUP),
|
||||||
|
"pagedown" | "page_down" | "page-down" => Some(KeyCode::KEY_PAGEDOWN),
|
||||||
|
"left" => Some(KeyCode::KEY_LEFT),
|
||||||
|
"right" => Some(KeyCode::KEY_RIGHT),
|
||||||
|
"up" => Some(KeyCode::KEY_UP),
|
||||||
|
"down" => Some(KeyCode::KEY_DOWN),
|
||||||
_ => Some(KeyCode::KEY_PAUSE),
|
_ => Some(KeyCode::KEY_PAUSE),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_quick_toggle_letter(raw: &str) -> Option<KeyCode> {
|
||||||
|
match raw {
|
||||||
|
"a" => Some(KeyCode::KEY_A),
|
||||||
|
"b" => Some(KeyCode::KEY_B),
|
||||||
|
"c" => Some(KeyCode::KEY_C),
|
||||||
|
"d" => Some(KeyCode::KEY_D),
|
||||||
|
"e" => Some(KeyCode::KEY_E),
|
||||||
|
"f" => Some(KeyCode::KEY_F),
|
||||||
|
"g" => Some(KeyCode::KEY_G),
|
||||||
|
"h" => Some(KeyCode::KEY_H),
|
||||||
|
"i" => Some(KeyCode::KEY_I),
|
||||||
|
"j" => Some(KeyCode::KEY_J),
|
||||||
|
"k" => Some(KeyCode::KEY_K),
|
||||||
|
"l" => Some(KeyCode::KEY_L),
|
||||||
|
"m" => Some(KeyCode::KEY_M),
|
||||||
|
"n" => Some(KeyCode::KEY_N),
|
||||||
|
"o" => Some(KeyCode::KEY_O),
|
||||||
|
"p" => Some(KeyCode::KEY_P),
|
||||||
|
"q" => Some(KeyCode::KEY_Q),
|
||||||
|
"r" => Some(KeyCode::KEY_R),
|
||||||
|
"s" => Some(KeyCode::KEY_S),
|
||||||
|
"t" => Some(KeyCode::KEY_T),
|
||||||
|
"u" => Some(KeyCode::KEY_U),
|
||||||
|
"v" => Some(KeyCode::KEY_V),
|
||||||
|
"w" => Some(KeyCode::KEY_W),
|
||||||
|
"x" => Some(KeyCode::KEY_X),
|
||||||
|
"y" => Some(KeyCode::KEY_Y),
|
||||||
|
"z" => Some(KeyCode::KEY_Z),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_quick_toggle_digit(raw: &str) -> Option<KeyCode> {
|
||||||
|
match raw {
|
||||||
|
"1" => Some(KeyCode::KEY_1),
|
||||||
|
"2" => Some(KeyCode::KEY_2),
|
||||||
|
"3" => Some(KeyCode::KEY_3),
|
||||||
|
"4" => Some(KeyCode::KEY_4),
|
||||||
|
"5" => Some(KeyCode::KEY_5),
|
||||||
|
"6" => Some(KeyCode::KEY_6),
|
||||||
|
"7" => Some(KeyCode::KEY_7),
|
||||||
|
"8" => Some(KeyCode::KEY_8),
|
||||||
|
"9" => Some(KeyCode::KEY_9),
|
||||||
|
"0" => Some(KeyCode::KEY_0),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_quick_toggle_function_key(raw: &str) -> Option<KeyCode> {
|
||||||
|
match raw {
|
||||||
|
"f1" => Some(KeyCode::KEY_F1),
|
||||||
|
"f2" => Some(KeyCode::KEY_F2),
|
||||||
|
"f3" => Some(KeyCode::KEY_F3),
|
||||||
|
"f4" => Some(KeyCode::KEY_F4),
|
||||||
|
"f5" => Some(KeyCode::KEY_F5),
|
||||||
|
"f6" => Some(KeyCode::KEY_F6),
|
||||||
|
"f7" => Some(KeyCode::KEY_F7),
|
||||||
|
"f8" => Some(KeyCode::KEY_F8),
|
||||||
|
"f9" => Some(KeyCode::KEY_F9),
|
||||||
|
"f10" => Some(KeyCode::KEY_F10),
|
||||||
|
"f11" => Some(KeyCode::KEY_F11),
|
||||||
|
"f12" => Some(KeyCode::KEY_F12),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Reads debounce window from env, with a safety floor to avoid rapid flapping.
|
/// Reads debounce window from env, with a safety floor to avoid rapid flapping.
|
||||||
fn quick_toggle_debounce_from_env() -> Duration {
|
fn quick_toggle_debounce_from_env() -> Duration {
|
||||||
let millis = std::env::var("LESAVKA_INPUT_TOGGLE_DEBOUNCE_MS")
|
let millis = std::env::var("LESAVKA_INPUT_TOGGLE_DEBOUNCE_MS")
|
||||||
@ -671,3 +765,37 @@ fn path_marker(path: &Path) -> u128 {
|
|||||||
.map(|duration| duration.as_millis())
|
.map(|duration| duration.as_millis())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::parse_quick_toggle_key;
|
||||||
|
use evdev::KeyCode;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_quick_toggle_key_supports_letters_digits_and_function_keys() {
|
||||||
|
assert_eq!(parse_quick_toggle_key("a"), Some(KeyCode::KEY_A));
|
||||||
|
assert_eq!(parse_quick_toggle_key("7"), Some(KeyCode::KEY_7));
|
||||||
|
assert_eq!(parse_quick_toggle_key("f12"), Some(KeyCode::KEY_F12));
|
||||||
|
assert_eq!(parse_quick_toggle_key("F3"), Some(KeyCode::KEY_F3));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_quick_toggle_key_supports_navigation_and_special_aliases() {
|
||||||
|
assert_eq!(parse_quick_toggle_key("page_up"), Some(KeyCode::KEY_PAGEUP));
|
||||||
|
assert_eq!(parse_quick_toggle_key("delete"), Some(KeyCode::KEY_DELETE));
|
||||||
|
assert_eq!(parse_quick_toggle_key("spacebar"), Some(KeyCode::KEY_SPACE));
|
||||||
|
assert_eq!(
|
||||||
|
parse_quick_toggle_key("print-screen"),
|
||||||
|
Some(KeyCode::KEY_SYSRQ)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_quick_toggle_key_can_disable_or_fall_back() {
|
||||||
|
assert_eq!(parse_quick_toggle_key("off"), None);
|
||||||
|
assert_eq!(
|
||||||
|
parse_quick_toggle_key("totally-unknown"),
|
||||||
|
Some(KeyCode::KEY_PAUSE)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -83,6 +83,8 @@ pub struct LauncherState {
|
|||||||
pub view_mode: ViewMode,
|
pub view_mode: ViewMode,
|
||||||
pub displays: [DisplaySurface; 2],
|
pub displays: [DisplaySurface; 2],
|
||||||
pub devices: DeviceSelection,
|
pub devices: DeviceSelection,
|
||||||
|
pub swap_key: String,
|
||||||
|
pub swap_key_binding: bool,
|
||||||
pub capture_power: CapturePowerStatus,
|
pub capture_power: CapturePowerStatus,
|
||||||
pub remote_active: bool,
|
pub remote_active: bool,
|
||||||
pub notes: Vec<String>,
|
pub notes: Vec<String>,
|
||||||
@ -95,6 +97,8 @@ impl Default for LauncherState {
|
|||||||
view_mode: ViewMode::Unified,
|
view_mode: ViewMode::Unified,
|
||||||
displays: [DisplaySurface::Preview, DisplaySurface::Preview],
|
displays: [DisplaySurface::Preview, DisplaySurface::Preview],
|
||||||
devices: DeviceSelection::default(),
|
devices: DeviceSelection::default(),
|
||||||
|
swap_key: "pause".to_string(),
|
||||||
|
swap_key_binding: false,
|
||||||
capture_power: CapturePowerStatus::default(),
|
capture_power: CapturePowerStatus::default(),
|
||||||
remote_active: false,
|
remote_active: false,
|
||||||
notes: Vec::new(),
|
notes: Vec::new(),
|
||||||
@ -172,6 +176,18 @@ impl LauncherState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_swap_key(&mut self, swap_key: impl Into<String>) {
|
||||||
|
self.swap_key = normalize_swap_key(swap_key.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn begin_swap_key_binding(&mut self) {
|
||||||
|
self.swap_key_binding = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish_swap_key_binding(&mut self) {
|
||||||
|
self.swap_key_binding = false;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn start_remote(&mut self) -> bool {
|
pub fn start_remote(&mut self) -> bool {
|
||||||
if self.remote_active {
|
if self.remote_active {
|
||||||
return false;
|
return false;
|
||||||
@ -198,7 +214,7 @@ impl LauncherState {
|
|||||||
|
|
||||||
pub fn status_line(&self) -> String {
|
pub fn status_line(&self) -> String {
|
||||||
format!(
|
format!(
|
||||||
"mode={} view={} active={} power={} d1={} d2={} camera={} mic={} speaker={}",
|
"mode={} view={} active={} power={} d1={} d2={} camera={} mic={} speaker={} swap={}",
|
||||||
match self.routing {
|
match self.routing {
|
||||||
InputRouting::Local => "local",
|
InputRouting::Local => "local",
|
||||||
InputRouting::Remote => "remote",
|
InputRouting::Remote => "remote",
|
||||||
@ -218,6 +234,7 @@ impl LauncherState {
|
|||||||
self.devices.camera.as_deref().unwrap_or("auto"),
|
self.devices.camera.as_deref().unwrap_or("auto"),
|
||||||
self.devices.microphone.as_deref().unwrap_or("auto"),
|
self.devices.microphone.as_deref().unwrap_or("auto"),
|
||||||
self.devices.speaker.as_deref().unwrap_or("auto"),
|
self.devices.speaker.as_deref().unwrap_or("auto"),
|
||||||
|
self.swap_key,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,6 +250,15 @@ fn normalize_selection(value: Option<String>) -> Option<String> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_swap_key(value: String) -> String {
|
||||||
|
let trimmed = value.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
"off".to_string()
|
||||||
|
} else {
|
||||||
|
trimmed.to_ascii_lowercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -357,4 +383,32 @@ mod tests {
|
|||||||
assert_eq!(state.capture_power.active_leases, 2);
|
assert_eq!(state.capture_power.active_leases, 2);
|
||||||
assert!(state.status_line().contains("power=on"));
|
assert!(state.status_line().contains("power=on"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn swap_key_binding_tracks_selected_key_and_binding_mode() {
|
||||||
|
let mut state = LauncherState::new();
|
||||||
|
assert_eq!(state.swap_key, "pause");
|
||||||
|
assert!(!state.swap_key_binding);
|
||||||
|
|
||||||
|
state.begin_swap_key_binding();
|
||||||
|
assert!(state.swap_key_binding);
|
||||||
|
|
||||||
|
state.set_swap_key("F8");
|
||||||
|
assert_eq!(state.swap_key, "f8");
|
||||||
|
|
||||||
|
state.set_swap_key(" ");
|
||||||
|
assert_eq!(state.swap_key, "off");
|
||||||
|
|
||||||
|
state.finish_swap_key_binding();
|
||||||
|
assert!(!state.swap_key_binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn push_note_accumulates_operator_context() {
|
||||||
|
let mut state = LauncherState::new();
|
||||||
|
state.push_note("preview warm");
|
||||||
|
state.push_note("relay linked");
|
||||||
|
|
||||||
|
assert_eq!(state.notes, vec!["preview warm", "relay linked"]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,11 +11,12 @@ use {
|
|||||||
super::state::{CapturePowerStatus, DisplaySurface, InputRouting, LauncherState},
|
super::state::{CapturePowerStatus, DisplaySurface, InputRouting, LauncherState},
|
||||||
super::ui_components::build_launcher_view,
|
super::ui_components::build_launcher_view,
|
||||||
super::ui_runtime::{
|
super::ui_runtime::{
|
||||||
dock_display_to_preview, input_control_path, input_state_path, next_input_routing,
|
RelayChild, capture_swap_key, dock_display_to_preview, input_control_path,
|
||||||
open_popout_window, path_marker, read_input_routing_state, reap_exited_child,
|
input_state_path, next_input_routing, open_popout_window, path_marker,
|
||||||
refresh_launcher_ui, refresh_test_buttons, routing_name, selected_combo_value,
|
read_input_routing_state, reap_exited_child, refresh_launcher_ui, refresh_test_buttons,
|
||||||
selected_server_addr, selected_toggle_key, spawn_client_process, stop_child_process,
|
routing_name, selected_combo_value, selected_server_addr, spawn_client_process,
|
||||||
update_test_action_result, write_input_routing_request, RelayChild,
|
stop_child_process, toggle_key_label, update_test_action_result,
|
||||||
|
write_input_routing_request,
|
||||||
},
|
},
|
||||||
gtk::glib,
|
gtk::glib,
|
||||||
gtk::prelude::*,
|
gtk::prelude::*,
|
||||||
@ -31,6 +32,11 @@ enum PowerMessage {
|
|||||||
Command(std::result::Result<CapturePowerStatus, String>),
|
Command(std::result::Result<CapturePowerStatus, String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(coverage))]
|
||||||
|
enum RelayMessage {
|
||||||
|
Spawned(std::result::Result<RelayChild, String>),
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(coverage))]
|
#[cfg(not(coverage))]
|
||||||
fn request_capture_power_refresh(
|
fn request_capture_power_refresh(
|
||||||
power_tx: std::sync::mpsc::Sender<PowerMessage>,
|
power_tx: std::sync::mpsc::Sender<PowerMessage>,
|
||||||
@ -50,8 +56,12 @@ fn request_capture_power_refresh(
|
|||||||
fn disconnected_capture_note(mode: &str) -> &'static str {
|
fn disconnected_capture_note(mode: &str) -> &'static str {
|
||||||
match mode {
|
match mode {
|
||||||
"forced-on" => "Relay disconnected. Capture is still forced on for staging.",
|
"forced-on" => "Relay disconnected. Capture is still forced on for staging.",
|
||||||
"forced-off" => "Relay disconnected. Capture stays intentionally dark until you return to Auto or Force On.",
|
"forced-off" => {
|
||||||
_ => "Relay disconnected. The server will hold capture briefly, then let it return to standby.",
|
"Relay disconnected. Capture stays intentionally dark until you return to Auto or Force On."
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
"Relay disconnected. The server will hold capture briefly, then let it return to standby."
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +143,8 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
|||||||
|
|
||||||
let (power_tx, power_rx) = std::sync::mpsc::channel::<PowerMessage>();
|
let (power_tx, power_rx) = std::sync::mpsc::channel::<PowerMessage>();
|
||||||
let power_request_in_flight = Rc::new(Cell::new(false));
|
let power_request_in_flight = Rc::new(Cell::new(false));
|
||||||
|
let (relay_tx, relay_rx) = std::sync::mpsc::channel::<RelayMessage>();
|
||||||
|
let relay_request_in_flight = Rc::new(Cell::new(false));
|
||||||
|
|
||||||
{
|
{
|
||||||
let state = Rc::clone(&state);
|
let state = Rc::clone(&state);
|
||||||
@ -228,11 +240,16 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
|||||||
let server_addr_fallback = Rc::clone(&server_addr);
|
let server_addr_fallback = Rc::clone(&server_addr);
|
||||||
let preview = preview.clone();
|
let preview = preview.clone();
|
||||||
let power_tx = power_tx.clone();
|
let power_tx = power_tx.clone();
|
||||||
|
let relay_tx = relay_tx.clone();
|
||||||
|
let relay_request_in_flight = Rc::clone(&relay_request_in_flight);
|
||||||
let start_button = widgets.start_button.clone();
|
let start_button = widgets.start_button.clone();
|
||||||
let widgets_handle = widgets.clone();
|
let widgets_handle = widgets.clone();
|
||||||
start_button.connect_clicked(move |_| {
|
start_button.connect_clicked(move |_| {
|
||||||
let server_addr =
|
let server_addr =
|
||||||
selected_server_addr(&server_entry, server_addr_fallback.as_ref());
|
selected_server_addr(&server_entry, server_addr_fallback.as_ref());
|
||||||
|
if relay_request_in_flight.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if child_proc.borrow().is_some() {
|
if child_proc.borrow().is_some() {
|
||||||
stop_child_process(&child_proc);
|
stop_child_process(&child_proc);
|
||||||
let power_mode = {
|
let power_mode = {
|
||||||
@ -269,63 +286,31 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
|||||||
let _ = std::fs::remove_file(input_control_path.as_path());
|
let _ = std::fs::remove_file(input_control_path.as_path());
|
||||||
let _ = std::fs::remove_file(input_state_path.as_path());
|
let _ = std::fs::remove_file(input_state_path.as_path());
|
||||||
let launch_state = state.borrow().clone();
|
let launch_state = state.borrow().clone();
|
||||||
let input_toggle_key = selected_toggle_key(&widgets.toggle_key_combo);
|
let input_toggle_key = launch_state.swap_key.clone();
|
||||||
match spawn_client_process(
|
let input_control_path = input_control_path.as_ref().clone();
|
||||||
&server_addr,
|
let input_state_path = input_state_path.as_ref().clone();
|
||||||
&launch_state,
|
relay_request_in_flight.set(true);
|
||||||
&input_toggle_key,
|
widgets_handle.status_label.set_text(&format!(
|
||||||
input_control_path.as_path(),
|
"Connecting relay with {} as the swap key...",
|
||||||
input_state_path.as_path(),
|
toggle_key_label(&input_toggle_key)
|
||||||
) {
|
));
|
||||||
Ok(child) => {
|
|
||||||
*child_proc.borrow_mut() = Some(child);
|
|
||||||
let _ = state.borrow_mut().start_remote();
|
|
||||||
if let Some(preview) = preview.as_ref() {
|
|
||||||
preview.set_server_addr(server_addr.clone());
|
|
||||||
preview.set_session_active(true);
|
|
||||||
}
|
|
||||||
let routing = routing_name(state.borrow().routing);
|
|
||||||
let power_mode = state.borrow().capture_power.mode.clone();
|
|
||||||
let message = match power_mode.as_str() {
|
|
||||||
"forced-off" => format!(
|
|
||||||
"Relay connected with inputs routed to {}, but capture is forced off. Return capture to Auto or Force On when you want remote video.",
|
|
||||||
routing
|
|
||||||
),
|
|
||||||
"forced-on" => format!(
|
|
||||||
"Relay connected with inputs routed to {}. Capture is being held awake and the eye previews are coming online.",
|
|
||||||
routing
|
|
||||||
),
|
|
||||||
_ => format!(
|
|
||||||
"Relay connected with inputs routed to {}. The eye previews will come up with the live session.",
|
|
||||||
routing
|
|
||||||
),
|
|
||||||
};
|
|
||||||
widgets_handle.status_label.set_text(&message);
|
|
||||||
request_capture_power_refresh(
|
|
||||||
power_tx.clone(),
|
|
||||||
server_addr.clone(),
|
|
||||||
Duration::from_millis(250),
|
|
||||||
);
|
|
||||||
request_capture_power_refresh(
|
|
||||||
power_tx.clone(),
|
|
||||||
server_addr,
|
|
||||||
Duration::from_millis(1250),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
if let Some(preview) = preview.as_ref() {
|
|
||||||
preview.set_session_active(false);
|
|
||||||
}
|
|
||||||
widgets_handle
|
|
||||||
.status_label
|
|
||||||
.set_text(&format!("Relay start failed: {err}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
refresh_launcher_ui(
|
refresh_launcher_ui(
|
||||||
&widgets_handle,
|
&widgets_handle,
|
||||||
&state.borrow(),
|
&state.borrow(),
|
||||||
child_proc.borrow().is_some(),
|
child_proc.borrow().is_some(),
|
||||||
);
|
);
|
||||||
|
let relay_tx = relay_tx.clone();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let result = spawn_client_process(
|
||||||
|
&server_addr,
|
||||||
|
&launch_state,
|
||||||
|
&input_toggle_key,
|
||||||
|
input_control_path.as_path(),
|
||||||
|
input_state_path.as_path(),
|
||||||
|
)
|
||||||
|
.map_err(|err| err.to_string());
|
||||||
|
let _ = relay_tx.send(RelayMessage::Spawned(result));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -364,6 +349,20 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let state = Rc::clone(&state);
|
||||||
|
let child_proc = Rc::clone(&child_proc);
|
||||||
|
let widgets = widgets.clone();
|
||||||
|
let swap_key_button = widgets.swap_key_button.clone();
|
||||||
|
swap_key_button.connect_clicked(move |_| {
|
||||||
|
state.borrow_mut().begin_swap_key_binding();
|
||||||
|
widgets
|
||||||
|
.status_label
|
||||||
|
.set_text("Press a single key now to make it the swap shortcut.");
|
||||||
|
refresh_launcher_ui(&widgets, &state.borrow(), child_proc.borrow().is_some());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let child_proc = Rc::clone(&child_proc);
|
let child_proc = Rc::clone(&child_proc);
|
||||||
let widgets = widgets.clone();
|
let widgets = widgets.clone();
|
||||||
@ -620,6 +619,52 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let state = Rc::clone(&state);
|
||||||
|
let child_proc = Rc::clone(&child_proc);
|
||||||
|
let widgets = widgets.clone();
|
||||||
|
let key_controller = gtk::EventControllerKey::new();
|
||||||
|
key_controller.connect_key_pressed(move |_, key, _, _| {
|
||||||
|
if !state.borrow().swap_key_binding {
|
||||||
|
return glib::Propagation::Proceed;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(swap_key) = capture_swap_key(key) else {
|
||||||
|
widgets.status_label.set_text(
|
||||||
|
"That key is not a good swap shortcut. Try a letter, digit, function key, or navigation key.",
|
||||||
|
);
|
||||||
|
refresh_launcher_ui(
|
||||||
|
&widgets,
|
||||||
|
&state.borrow(),
|
||||||
|
child_proc.borrow().is_some(),
|
||||||
|
);
|
||||||
|
return glib::Propagation::Stop;
|
||||||
|
};
|
||||||
|
|
||||||
|
let relay_live = child_proc.borrow().is_some() || state.borrow().remote_active;
|
||||||
|
{
|
||||||
|
let mut state = state.borrow_mut();
|
||||||
|
state.set_swap_key(swap_key.clone());
|
||||||
|
state.finish_swap_key_binding();
|
||||||
|
}
|
||||||
|
let status_message = if relay_live {
|
||||||
|
format!(
|
||||||
|
"Swap key set to {}. Disconnect and reconnect the relay to use it live.",
|
||||||
|
toggle_key_label(&swap_key)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"Swap key set to {}. The next relay launch will use it.",
|
||||||
|
toggle_key_label(&swap_key)
|
||||||
|
)
|
||||||
|
};
|
||||||
|
widgets.status_label.set_text(&status_message);
|
||||||
|
refresh_launcher_ui(&widgets, &state.borrow(), child_proc.borrow().is_some());
|
||||||
|
glib::Propagation::Stop
|
||||||
|
});
|
||||||
|
window.add_controller(key_controller);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let window = window.clone();
|
let window = window.clone();
|
||||||
let state = Rc::clone(&state);
|
let state = Rc::clone(&state);
|
||||||
@ -635,6 +680,7 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
|||||||
let last_state_marker =
|
let last_state_marker =
|
||||||
Rc::new(RefCell::new(path_marker(input_state_path.as_path())));
|
Rc::new(RefCell::new(path_marker(input_state_path.as_path())));
|
||||||
let power_request_in_flight = Rc::clone(&power_request_in_flight);
|
let power_request_in_flight = Rc::clone(&power_request_in_flight);
|
||||||
|
let relay_request_in_flight = Rc::clone(&relay_request_in_flight);
|
||||||
let preview = preview.clone();
|
let preview = preview.clone();
|
||||||
let power_tx = power_tx.clone();
|
let power_tx = power_tx.clone();
|
||||||
glib::timeout_add_local(Duration::from_millis(180), move || {
|
glib::timeout_add_local(Duration::from_millis(180), move || {
|
||||||
@ -688,6 +734,57 @@ pub fn run_gui_launcher(server_addr: String) -> Result<()> {
|
|||||||
window.present();
|
window.present();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while let Ok(message) = relay_rx.try_recv() {
|
||||||
|
relay_request_in_flight.set(false);
|
||||||
|
match message {
|
||||||
|
RelayMessage::Spawned(Ok(child)) => {
|
||||||
|
*child_proc.borrow_mut() = Some(child);
|
||||||
|
let _ = state.borrow_mut().start_remote();
|
||||||
|
let server_addr =
|
||||||
|
selected_server_addr(&server_entry, server_addr_fallback.as_ref());
|
||||||
|
if let Some(preview) = preview.as_ref() {
|
||||||
|
preview.set_server_addr(server_addr.clone());
|
||||||
|
preview.set_session_active(true);
|
||||||
|
}
|
||||||
|
let routing = routing_name(state.borrow().routing);
|
||||||
|
let power_mode = state.borrow().capture_power.mode.clone();
|
||||||
|
let message = match power_mode.as_str() {
|
||||||
|
"forced-off" => format!(
|
||||||
|
"Relay connected with inputs routed to {}, but capture is forced off. Return capture to Auto or Force On when you want remote video.",
|
||||||
|
routing
|
||||||
|
),
|
||||||
|
"forced-on" => format!(
|
||||||
|
"Relay connected with inputs routed to {}. Capture is being held awake and the eye previews are coming online.",
|
||||||
|
routing
|
||||||
|
),
|
||||||
|
_ => format!(
|
||||||
|
"Relay connected with inputs routed to {}. The eye previews will come up with the live session.",
|
||||||
|
routing
|
||||||
|
),
|
||||||
|
};
|
||||||
|
widgets.status_label.set_text(&message);
|
||||||
|
request_capture_power_refresh(
|
||||||
|
power_tx.clone(),
|
||||||
|
server_addr.clone(),
|
||||||
|
Duration::from_millis(250),
|
||||||
|
);
|
||||||
|
request_capture_power_refresh(
|
||||||
|
power_tx.clone(),
|
||||||
|
server_addr,
|
||||||
|
Duration::from_millis(1250),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
RelayMessage::Spawned(Err(err)) => {
|
||||||
|
if let Some(preview) = preview.as_ref() {
|
||||||
|
preview.set_session_active(false);
|
||||||
|
}
|
||||||
|
widgets
|
||||||
|
.status_label
|
||||||
|
.set_text(&format!("Relay start failed: {err}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while let Ok(message) = power_rx.try_recv() {
|
while let Ok(message) = power_rx.try_recv() {
|
||||||
power_request_in_flight.set(false);
|
power_request_in_flight.set(false);
|
||||||
match message {
|
match message {
|
||||||
|
|||||||
@ -51,7 +51,7 @@ pub struct LauncherWidgets {
|
|||||||
pub input_toggle_button: gtk::Button,
|
pub input_toggle_button: gtk::Button,
|
||||||
pub clipboard_button: gtk::Button,
|
pub clipboard_button: gtk::Button,
|
||||||
pub probe_button: gtk::Button,
|
pub probe_button: gtk::Button,
|
||||||
pub toggle_key_combo: gtk::ComboBoxText,
|
pub swap_key_button: gtk::Button,
|
||||||
pub camera_test_button: gtk::Button,
|
pub camera_test_button: gtk::Button,
|
||||||
pub microphone_test_button: gtk::Button,
|
pub microphone_test_button: gtk::Button,
|
||||||
pub speaker_test_button: gtk::Button,
|
pub speaker_test_button: gtk::Button,
|
||||||
@ -220,23 +220,15 @@ pub fn build_launcher_view(
|
|||||||
input_toggle_button.set_tooltip_text(Some(
|
input_toggle_button.set_tooltip_text(Some(
|
||||||
"Switch live keyboard and mouse ownership between the local machine and the remote target.",
|
"Switch live keyboard and mouse ownership between the local machine and the remote target.",
|
||||||
));
|
));
|
||||||
let swap_label = gtk::Label::new(Some("Swap key"));
|
let swap_key_button = gtk::Button::with_label(&format!(
|
||||||
swap_label.set_halign(gtk::Align::Start);
|
"Set Swap Key ({})",
|
||||||
let toggle_key_combo = gtk::ComboBoxText::new();
|
super::ui_runtime::toggle_key_label(&state.swap_key)
|
||||||
toggle_key_combo.append(Some("scrolllock"), "Scroll Lock");
|
));
|
||||||
toggle_key_combo.append(Some("sysrq"), "SysRq / PrtSc");
|
swap_key_button.set_tooltip_text(Some(
|
||||||
toggle_key_combo.append(Some("pause"), "Pause");
|
"Press this, then hit one keyboard key to make it the live local/remote input swap shortcut.",
|
||||||
toggle_key_combo.append(Some("f12"), "F12");
|
|
||||||
toggle_key_combo.append(Some("f11"), "F11");
|
|
||||||
toggle_key_combo.append(Some("f10"), "F10");
|
|
||||||
toggle_key_combo.append(Some("off"), "Disabled");
|
|
||||||
let _ = toggle_key_combo.set_active_id(Some("pause"));
|
|
||||||
toggle_key_combo.set_tooltip_text(Some(
|
|
||||||
"Single-key live input swap while the relay is running.",
|
|
||||||
));
|
));
|
||||||
routing_row.append(&input_toggle_button);
|
routing_row.append(&input_toggle_button);
|
||||||
routing_row.append(&swap_label);
|
routing_row.append(&swap_key_button);
|
||||||
routing_row.append(&toggle_key_combo);
|
|
||||||
routing_body.append(&routing_row);
|
routing_body.append(&routing_row);
|
||||||
sidebar.append(&routing_panel);
|
sidebar.append(&routing_panel);
|
||||||
|
|
||||||
@ -442,7 +434,7 @@ pub fn build_launcher_view(
|
|||||||
input_toggle_button: input_toggle_button.clone(),
|
input_toggle_button: input_toggle_button.clone(),
|
||||||
clipboard_button: clipboard_button.clone(),
|
clipboard_button: clipboard_button.clone(),
|
||||||
probe_button: probe_button.clone(),
|
probe_button: probe_button.clone(),
|
||||||
toggle_key_combo: toggle_key_combo.clone(),
|
swap_key_button: swap_key_button.clone(),
|
||||||
camera_test_button: camera_test_button.clone(),
|
camera_test_button: camera_test_button.clone(),
|
||||||
microphone_test_button: microphone_test_button.clone(),
|
microphone_test_button: microphone_test_button.clone(),
|
||||||
speaker_test_button: speaker_test_button.clone(),
|
speaker_test_button: speaker_test_button.clone(),
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gtk::gio;
|
|
||||||
use gtk::{glib, prelude::*};
|
use gtk::{glib, prelude::*};
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
process::{Child, Command},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
LAUNCHER_FOCUS_SIGNAL_ENV,
|
||||||
device_test::{DeviceTestController, DeviceTestKind},
|
device_test::{DeviceTestController, DeviceTestKind},
|
||||||
launcher_focus_signal_path,
|
launcher_focus_signal_path,
|
||||||
preview::LauncherPreview,
|
preview::LauncherPreview,
|
||||||
runtime_env_vars,
|
runtime_env_vars,
|
||||||
state::{CapturePowerStatus, DisplaySurface, InputRouting, LauncherState},
|
state::{CapturePowerStatus, DisplaySurface, InputRouting, LauncherState},
|
||||||
ui_components::{DisplayPaneWidgets, LauncherWidgets, PopoutWindowHandle},
|
ui_components::{DisplayPaneWidgets, LauncherWidgets, PopoutWindowHandle},
|
||||||
LAUNCHER_FOCUS_SIGNAL_ENV,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const INPUT_CONTROL_ENV: &str = "LESAVKA_LAUNCHER_INPUT_CONTROL";
|
pub const INPUT_CONTROL_ENV: &str = "LESAVKA_LAUNCHER_INPUT_CONTROL";
|
||||||
@ -22,7 +22,7 @@ pub const INPUT_STATE_ENV: &str = "LESAVKA_LAUNCHER_INPUT_STATE";
|
|||||||
pub const DEFAULT_INPUT_CONTROL_PATH: &str = "/tmp/lesavka-launcher-input.control";
|
pub const DEFAULT_INPUT_CONTROL_PATH: &str = "/tmp/lesavka-launcher-input.control";
|
||||||
pub const DEFAULT_INPUT_STATE_PATH: &str = "/tmp/lesavka-launcher-input.state";
|
pub const DEFAULT_INPUT_STATE_PATH: &str = "/tmp/lesavka-launcher-input.state";
|
||||||
|
|
||||||
pub type RelayChild = gio::Subprocess;
|
pub type RelayChild = Child;
|
||||||
|
|
||||||
pub fn refresh_launcher_ui(widgets: &LauncherWidgets, state: &LauncherState, child_running: bool) {
|
pub fn refresh_launcher_ui(widgets: &LauncherWidgets, state: &LauncherState, child_running: bool) {
|
||||||
let relay_live = child_running || state.remote_active;
|
let relay_live = child_running || state.remote_active;
|
||||||
@ -46,7 +46,7 @@ pub fn refresh_launcher_ui(widgets: &LauncherWidgets, state: &LauncherState, chi
|
|||||||
widgets
|
widgets
|
||||||
.summary
|
.summary
|
||||||
.shortcut_value
|
.shortcut_value
|
||||||
.set_text(&selected_toggle_key_label(&widgets.toggle_key_combo));
|
.set_text(&toggle_key_label(&state.swap_key));
|
||||||
|
|
||||||
widgets
|
widgets
|
||||||
.power_detail
|
.power_detail
|
||||||
@ -78,6 +78,12 @@ pub fn refresh_launcher_ui(widgets: &LauncherWidgets, state: &LauncherState, chi
|
|||||||
InputRouting::Remote => "Route Inputs To Local",
|
InputRouting::Remote => "Route Inputs To Local",
|
||||||
InputRouting::Local => "Route Inputs To Remote",
|
InputRouting::Local => "Route Inputs To Remote",
|
||||||
});
|
});
|
||||||
|
let swap_key_label = if state.swap_key_binding {
|
||||||
|
"Press Any Key…".to_string()
|
||||||
|
} else {
|
||||||
|
format!("Set Swap Key ({})", toggle_key_label(&state.swap_key))
|
||||||
|
};
|
||||||
|
widgets.swap_key_button.set_label(&swap_key_label);
|
||||||
let power_available = state.capture_power.available;
|
let power_available = state.capture_power.available;
|
||||||
widgets
|
widgets
|
||||||
.power_auto_button
|
.power_auto_button
|
||||||
@ -484,20 +490,6 @@ pub fn selected_combo_value(combo: >k::ComboBoxText) -> Option<String> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn selected_toggle_key(combo: >k::ComboBoxText) -> String {
|
|
||||||
combo
|
|
||||||
.active_id()
|
|
||||||
.map(|value| value.to_string())
|
|
||||||
.unwrap_or_else(|| "pause".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selected_toggle_key_label(combo: >k::ComboBoxText) -> String {
|
|
||||||
combo
|
|
||||||
.active_text()
|
|
||||||
.map(|value| value.to_string())
|
|
||||||
.unwrap_or_else(|| "Pause".to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn selected_server_addr(entry: >k::Entry, fallback: &str) -> String {
|
pub fn selected_server_addr(entry: >k::Entry, fallback: &str) -> String {
|
||||||
let current = entry.text();
|
let current = entry.text();
|
||||||
let trimmed = current.trim();
|
let trimmed = current.trim();
|
||||||
@ -557,6 +549,80 @@ pub fn set_combo_active_text(combo: >k::ComboBoxText, wanted: Option<&str>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn toggle_key_label(raw: &str) -> String {
|
||||||
|
match raw.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"" | "off" | "none" | "disabled" => "Disabled".to_string(),
|
||||||
|
"scrolllock" | "scroll_lock" | "scroll-lock" => "Scroll Lock".to_string(),
|
||||||
|
"sysrq" | "sysreq" | "prtsc" | "printscreen" | "print_screen" | "print-screen" => {
|
||||||
|
"SysRq / PrtSc".to_string()
|
||||||
|
}
|
||||||
|
"pause" | "pausebreak" | "pause_break" | "pause-break" => "Pause".to_string(),
|
||||||
|
"pageup" | "page_up" | "page-up" => "Page Up".to_string(),
|
||||||
|
"pagedown" | "page_down" | "page-down" => "Page Down".to_string(),
|
||||||
|
"capslock" | "caps_lock" | "caps-lock" => "Caps Lock".to_string(),
|
||||||
|
"backspace" | "back_space" | "back-space" => "Backspace".to_string(),
|
||||||
|
"space" | "spacebar" => "Space".to_string(),
|
||||||
|
"escape" | "esc" => "Escape".to_string(),
|
||||||
|
value
|
||||||
|
if value.starts_with('f')
|
||||||
|
&& value.len() <= 3
|
||||||
|
&& value[1..].chars().all(|ch| ch.is_ascii_digit()) =>
|
||||||
|
{
|
||||||
|
value.to_ascii_uppercase()
|
||||||
|
}
|
||||||
|
value if value.len() == 1 => value.to_ascii_uppercase(),
|
||||||
|
value => capitalize(&value.replace('_', " ").replace('-', " ")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capture_swap_key(key: gtk::gdk::Key) -> Option<String> {
|
||||||
|
let normalized_name = key.name()?.to_string().to_ascii_lowercase();
|
||||||
|
match normalized_name.as_str() {
|
||||||
|
"shift_l" | "shift_r" | "control_l" | "control_r" | "alt_l" | "alt_r" | "super_l"
|
||||||
|
| "super_r" | "meta_l" | "meta_r" | "hyper_l" | "hyper_r" | "iso_level3_shift"
|
||||||
|
| "multi_key" => None,
|
||||||
|
"scroll_lock" => Some("scrolllock".to_string()),
|
||||||
|
"sys_req" | "print" => Some("sysrq".to_string()),
|
||||||
|
"pause" | "break" => Some("pause".to_string()),
|
||||||
|
"page_up" => Some("pageup".to_string()),
|
||||||
|
"page_down" => Some("pagedown".to_string()),
|
||||||
|
"caps_lock" => Some("capslock".to_string()),
|
||||||
|
"backspace" => Some("backspace".to_string()),
|
||||||
|
"return" => Some("enter".to_string()),
|
||||||
|
"space" => Some("space".to_string()),
|
||||||
|
"escape" => Some("escape".to_string()),
|
||||||
|
"kp_0" => Some("0".to_string()),
|
||||||
|
"kp_1" => Some("1".to_string()),
|
||||||
|
"kp_2" => Some("2".to_string()),
|
||||||
|
"kp_3" => Some("3".to_string()),
|
||||||
|
"kp_4" => Some("4".to_string()),
|
||||||
|
"kp_5" => Some("5".to_string()),
|
||||||
|
"kp_6" => Some("6".to_string()),
|
||||||
|
"kp_7" => Some("7".to_string()),
|
||||||
|
"kp_8" => Some("8".to_string()),
|
||||||
|
"kp_9" => Some("9".to_string()),
|
||||||
|
other
|
||||||
|
if other.starts_with('f')
|
||||||
|
&& other.len() <= 3
|
||||||
|
&& other[1..].chars().all(|ch| ch.is_ascii_digit()) =>
|
||||||
|
{
|
||||||
|
Some(other.to_string())
|
||||||
|
}
|
||||||
|
other if other.len() == 1 => {
|
||||||
|
let ch = other.chars().next()?;
|
||||||
|
if ch.is_ascii_alphanumeric() {
|
||||||
|
Some(ch.to_ascii_lowercase().to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"insert" | "delete" | "home" | "end" | "left" | "right" | "up" | "down" | "tab" => {
|
||||||
|
Some(normalized_name)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn spawn_client_process(
|
pub fn spawn_client_process(
|
||||||
server_addr: &str,
|
server_addr: &str,
|
||||||
state: &LauncherState,
|
state: &LauncherState,
|
||||||
@ -565,47 +631,41 @@ pub fn spawn_client_process(
|
|||||||
input_state_path: &Path,
|
input_state_path: &Path,
|
||||||
) -> Result<RelayChild> {
|
) -> Result<RelayChild> {
|
||||||
let exe = std::env::current_exe()?;
|
let exe = std::env::current_exe()?;
|
||||||
let launcher = gio::SubprocessLauncher::new(gio::SubprocessFlags::NONE);
|
let mut command = Command::new(exe);
|
||||||
launcher.setenv("LESAVKA_LAUNCHER_CHILD", "1", true);
|
command.arg("--no-launcher");
|
||||||
launcher.setenv("LESAVKA_SERVER_ADDR", server_addr, true);
|
command.env("LESAVKA_LAUNCHER_CHILD", "1");
|
||||||
launcher.setenv("LESAVKA_INPUT_TOGGLE_KEY", input_toggle_key, true);
|
command.env("LESAVKA_SERVER_ADDR", server_addr);
|
||||||
launcher.setenv("LESAVKA_LAUNCHER_WINDOW_TITLE", "Lesavka Launcher", true);
|
command.env("LESAVKA_INPUT_TOGGLE_KEY", input_toggle_key);
|
||||||
launcher.setenv("LESAVKA_FOCUS_LAUNCHER_ON_LOCAL", "1", true);
|
command.env("LESAVKA_LAUNCHER_WINDOW_TITLE", "Lesavka Launcher");
|
||||||
launcher.setenv(
|
command.env("LESAVKA_FOCUS_LAUNCHER_ON_LOCAL", "1");
|
||||||
LAUNCHER_FOCUS_SIGNAL_ENV,
|
command.env(LAUNCHER_FOCUS_SIGNAL_ENV, launcher_focus_signal_path());
|
||||||
launcher_focus_signal_path(),
|
command.env(INPUT_CONTROL_ENV, input_control_path);
|
||||||
true,
|
command.env(INPUT_STATE_ENV, input_state_path);
|
||||||
);
|
command.env("LESAVKA_DISABLE_VIDEO_RENDER", "1");
|
||||||
launcher.setenv(INPUT_CONTROL_ENV, input_control_path, true);
|
command.env("LESAVKA_CLIPBOARD_PASTE", "0");
|
||||||
launcher.setenv(INPUT_STATE_ENV, input_state_path, true);
|
|
||||||
launcher.setenv("LESAVKA_DISABLE_VIDEO_RENDER", "1", true);
|
|
||||||
launcher.setenv("LESAVKA_CLIPBOARD_PASTE", "0", true);
|
|
||||||
for (key, value) in runtime_env_vars(state) {
|
for (key, value) in runtime_env_vars(state) {
|
||||||
launcher.setenv(key, value, true);
|
command.env(key, value);
|
||||||
}
|
}
|
||||||
let argv = [exe.as_os_str(), std::ffi::OsStr::new("--no-launcher")];
|
Ok(command.spawn()?)
|
||||||
Ok(launcher.spawn(&argv)?)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop_child_process(child_proc: &Rc<RefCell<Option<RelayChild>>>) {
|
pub fn stop_child_process(child_proc: &Rc<RefCell<Option<RelayChild>>>) {
|
||||||
if let Some(child) = child_proc.borrow_mut().take()
|
if let Some(mut child) = child_proc.borrow_mut().take() {
|
||||||
&& !child.has_exited()
|
let _ = child.kill();
|
||||||
{
|
let _ = child.wait();
|
||||||
child.force_exit();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reap_exited_child(child_proc: &Rc<RefCell<Option<RelayChild>>>) -> bool {
|
pub fn reap_exited_child(child_proc: &Rc<RefCell<Option<RelayChild>>>) -> bool {
|
||||||
let mut slot = child_proc.borrow_mut();
|
let mut slot = child_proc.borrow_mut();
|
||||||
match slot.as_mut() {
|
match slot.as_mut() {
|
||||||
Some(child) => {
|
Some(child) => match child.try_wait() {
|
||||||
if child.has_exited() {
|
Ok(Some(_)) => {
|
||||||
*slot = None;
|
*slot = None;
|
||||||
false
|
false
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
}
|
||||||
}
|
Ok(None) | Err(_) => true,
|
||||||
|
},
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,8 @@
|
|||||||
},
|
},
|
||||||
"client/src/input/inputs.rs": {
|
"client/src/input/inputs.rs": {
|
||||||
"clippy_warnings": 42,
|
"clippy_warnings": 42,
|
||||||
"doc_debt": 16,
|
"doc_debt": 19,
|
||||||
"loc": 673
|
"loc": 801
|
||||||
},
|
},
|
||||||
"client/src/input/keyboard.rs": {
|
"client/src/input/keyboard.rs": {
|
||||||
"clippy_warnings": 24,
|
"clippy_warnings": 24,
|
||||||
@ -86,14 +86,14 @@
|
|||||||
"loc": 442
|
"loc": 442
|
||||||
},
|
},
|
||||||
"client/src/launcher/state.rs": {
|
"client/src/launcher/state.rs": {
|
||||||
"clippy_warnings": 14,
|
"clippy_warnings": 16,
|
||||||
"doc_debt": 15,
|
"doc_debt": 18,
|
||||||
"loc": 360
|
"loc": 414
|
||||||
},
|
},
|
||||||
"client/src/launcher/ui.rs": {
|
"client/src/launcher/ui.rs": {
|
||||||
"clippy_warnings": 10,
|
"clippy_warnings": 10,
|
||||||
"doc_debt": 3,
|
"doc_debt": 3,
|
||||||
"loc": 752
|
"loc": 848
|
||||||
},
|
},
|
||||||
"client/src/launcher/ui_components.rs": {
|
"client/src/launcher/ui_components.rs": {
|
||||||
"clippy_warnings": 8,
|
"clippy_warnings": 8,
|
||||||
@ -102,8 +102,8 @@
|
|||||||
},
|
},
|
||||||
"client/src/launcher/ui_runtime.rs": {
|
"client/src/launcher/ui_runtime.rs": {
|
||||||
"clippy_warnings": 10,
|
"clippy_warnings": 10,
|
||||||
"doc_debt": 20,
|
"doc_debt": 22,
|
||||||
"loc": 670
|
"loc": 730
|
||||||
},
|
},
|
||||||
"client/src/layout.rs": {
|
"client/src/layout.rs": {
|
||||||
"clippy_warnings": 6,
|
"clippy_warnings": 6,
|
||||||
@ -268,5 +268,21 @@
|
|||||||
"doc_debt": 0,
|
"doc_debt": 0,
|
||||||
"loc": 10
|
"loc": 10
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"client/src/input/inputs.rs": {
|
||||||
|
"loc": 801,
|
||||||
|
"doc_debt": 19
|
||||||
|
},
|
||||||
|
"client/src/launcher/state.rs": {
|
||||||
|
"loc": 414,
|
||||||
|
"clippy_warnings": 16,
|
||||||
|
"doc_debt": 18
|
||||||
|
},
|
||||||
|
"client/src/launcher/ui.rs": {
|
||||||
|
"loc": 848
|
||||||
|
},
|
||||||
|
"client/src/launcher/ui_runtime.rs": {
|
||||||
|
"loc": 730,
|
||||||
|
"doc_debt": 22
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,8 +17,8 @@
|
|||||||
"loc": 372
|
"loc": 372
|
||||||
},
|
},
|
||||||
"client/src/input/inputs.rs": {
|
"client/src/input/inputs.rs": {
|
||||||
"line_percent": 97.55102040816327,
|
"line_percent": 98.27089337175792,
|
||||||
"loc": 673
|
"loc": 801
|
||||||
},
|
},
|
||||||
"client/src/input/keyboard.rs": {
|
"client/src/input/keyboard.rs": {
|
||||||
"line_percent": 95.9409594095941,
|
"line_percent": 95.9409594095941,
|
||||||
@ -53,12 +53,12 @@
|
|||||||
"loc": 195
|
"loc": 195
|
||||||
},
|
},
|
||||||
"client/src/launcher/state.rs": {
|
"client/src/launcher/state.rs": {
|
||||||
"line_percent": 98.29059829059828,
|
"line_percent": 98.51851851851852,
|
||||||
"loc": 360
|
"loc": 414
|
||||||
},
|
},
|
||||||
"client/src/launcher/ui.rs": {
|
"client/src/launcher/ui.rs": {
|
||||||
"line_percent": 100.0,
|
"line_percent": 100.0,
|
||||||
"loc": 752
|
"loc": 848
|
||||||
},
|
},
|
||||||
"client/src/layout.rs": {
|
"client/src/layout.rs": {
|
||||||
"line_percent": 97.72727272727273,
|
"line_percent": 97.72727272727273,
|
||||||
@ -164,5 +164,17 @@
|
|||||||
"line_percent": 96.03174603174604,
|
"line_percent": 96.03174603174604,
|
||||||
"loc": 236
|
"loc": 236
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"client/src/input/inputs.rs": {
|
||||||
|
"loc": 801,
|
||||||
|
"line_percent": 98.27089337175792
|
||||||
|
},
|
||||||
|
"client/src/launcher/state.rs": {
|
||||||
|
"loc": 414,
|
||||||
|
"line_percent": 98.51851851851852
|
||||||
|
},
|
||||||
|
"client/src/launcher/ui.rs": {
|
||||||
|
"loc": 848,
|
||||||
|
"line_percent": 100.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user