2026-04-23 07:00:06 -03:00
#[ cfg(test) ]
/// Prefer the basename for `/dev/...` entries while keeping Pulse names intact.
fn compact_device_name ( value : & str ) -> String {
let trimmed = value . trim ( ) ;
if trimmed . is_empty ( ) {
return " auto " . to_string ( ) ;
}
trimmed . rsplit ( '/' ) . next ( ) . unwrap_or ( trimmed ) . to_string ( )
}
2026-05-06 05:50:59 -03:00
/// Keeps `capitalize` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
/// Inputs are the typed parameters; output is the return value or side effect.
2026-04-23 07:00:06 -03:00
pub fn capitalize ( value : & str ) -> String {
let mut chars = value . chars ( ) ;
match chars . next ( ) {
Some ( first ) = > format! ( " {} {} " , first . to_ascii_uppercase ( ) , chars . as_str ( ) ) ,
None = > String ::new ( ) ,
}
}
2026-05-06 05:50:59 -03:00
/// Keeps `selected_combo_value` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
/// Inputs are the typed parameters; output is the return value or side effect.
2026-04-23 07:00:06 -03:00
pub fn selected_combo_value ( combo : & gtk ::ComboBoxText ) -> Option < String > {
combo
. active_id ( )
. map ( | value | value . to_string ( ) )
. or_else ( | | combo . active_text ( ) . map ( | value | value . to_string ( ) ) )
. and_then ( | value | {
let value = value . to_string ( ) ;
let trimmed = value . trim ( ) ;
if trimmed . is_empty ( )
| | trimmed . eq_ignore_ascii_case ( " auto " )
| | trimmed . eq_ignore_ascii_case ( " all " )
{
None
} else {
Some ( trimmed . to_string ( ) )
}
} )
}
2026-05-06 05:50:59 -03:00
/// Keeps `selected_server_addr` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
/// Inputs are the typed parameters; output is the return value or side effect.
2026-04-23 07:00:06 -03:00
pub fn selected_server_addr ( entry : & gtk ::Entry , fallback : & str ) -> String {
let current = entry . text ( ) ;
let trimmed = current . trim ( ) ;
if trimmed . is_empty ( ) {
2026-04-30 11:38:16 -03:00
normalize_server_addr ( fallback )
2026-04-23 07:00:06 -03:00
} else {
2026-04-30 11:38:16 -03:00
let normalized = normalize_server_addr ( trimmed ) ;
if normalized ! = trimmed {
entry . set_text ( & normalized ) ;
}
normalized
}
}
2026-05-06 05:50:59 -03:00
/// Keeps `normalize_server_addr` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
/// Inputs are the typed parameters; output is the return value or side effect.
2026-04-30 11:38:16 -03:00
pub fn normalize_server_addr ( raw : & str ) -> String {
let trimmed = raw . trim ( ) ;
if trimmed . is_empty ( ) | | trimmed . contains ( " :// " ) {
2026-04-23 07:00:06 -03:00
trimmed . to_string ( )
2026-04-30 11:38:16 -03:00
} else {
format! ( " https:// {trimmed} " )
2026-04-23 07:00:06 -03:00
}
}
pub fn input_control_path ( ) -> PathBuf {
std ::env ::var ( INPUT_CONTROL_ENV )
. map ( PathBuf ::from )
. unwrap_or_else ( | _ | PathBuf ::from ( DEFAULT_INPUT_CONTROL_PATH ) )
}
pub fn input_state_path ( ) -> PathBuf {
std ::env ::var ( INPUT_STATE_ENV )
. map ( PathBuf ::from )
. unwrap_or_else ( | _ | PathBuf ::from ( DEFAULT_INPUT_STATE_PATH ) )
}
pub fn input_toggle_control_path ( ) -> PathBuf {
std ::env ::var ( TOGGLE_KEY_CONTROL_ENV )
. map ( PathBuf ::from )
. unwrap_or_else ( | _ | PathBuf ::from ( DEFAULT_TOGGLE_KEY_CONTROL_PATH ) )
}
pub fn audio_gain_control_path ( ) -> PathBuf {
std ::env ::var ( AUDIO_GAIN_CONTROL_ENV )
. map ( PathBuf ::from )
. unwrap_or_else ( | _ | PathBuf ::from ( DEFAULT_AUDIO_GAIN_CONTROL_PATH ) )
}
pub fn mic_gain_control_path ( ) -> PathBuf {
std ::env ::var ( MIC_GAIN_CONTROL_ENV )
. map ( PathBuf ::from )
. unwrap_or_else ( | _ | PathBuf ::from ( DEFAULT_MIC_GAIN_CONTROL_PATH ) )
}
2026-04-30 15:04:00 -03:00
pub fn media_control_path ( ) -> PathBuf {
std ::env ::var ( MEDIA_CONTROL_ENV )
. map ( PathBuf ::from )
. unwrap_or_else ( | _ | PathBuf ::from ( DEFAULT_MEDIA_CONTROL_PATH ) )
}
2026-04-23 07:00:06 -03:00
pub fn uplink_camera_preview_path ( ) -> PathBuf {
std ::env ::var ( UPLINK_CAMERA_PREVIEW_ENV )
. map ( PathBuf ::from )
. unwrap_or_else ( | _ | PathBuf ::from ( DEFAULT_UPLINK_CAMERA_PREVIEW_PATH ) )
}
pub fn uplink_mic_level_path ( ) -> PathBuf {
std ::env ::var ( UPLINK_MIC_LEVEL_ENV )
. map ( PathBuf ::from )
. unwrap_or_else ( | _ | PathBuf ::from ( DEFAULT_UPLINK_MIC_LEVEL_PATH ) )
}
2026-04-24 00:30:07 -03:00
pub fn uplink_telemetry_path ( ) -> PathBuf {
std ::env ::var ( UPLINK_TELEMETRY_ENV )
. map ( PathBuf ::from )
. unwrap_or_else ( | _ | PathBuf ::from ( DEFAULT_UPLINK_TELEMETRY_PATH ) )
}
2026-04-23 07:00:06 -03:00
pub fn write_input_routing_request ( path : & Path , routing : InputRouting ) -> Result < ( ) > {
std ::fs ::write (
path ,
format! ( " {} {} \n " , routing_name ( routing ) , control_request_nonce ( ) ) ,
) ? ;
Ok ( ( ) )
}
pub fn write_audio_gain_request ( path : & Path , gain_percent : u32 ) -> Result < ( ) > {
let gain = gain_percent . min ( super ::state ::MAX_AUDIO_GAIN_PERCENT ) as f64 / 100.0 ;
std ::fs ::write ( path , format! ( " {gain:.3} {} \n " , control_request_nonce ( ) ) ) ? ;
Ok ( ( ) )
}
pub fn write_mic_gain_request ( path : & Path , gain_percent : u32 ) -> Result < ( ) > {
let gain = gain_percent . min ( super ::state ::MAX_MIC_GAIN_PERCENT ) as f64 / 100.0 ;
std ::fs ::write ( path , format! ( " {gain:.3} {} \n " , control_request_nonce ( ) ) ) ? ;
Ok ( ( ) )
}
2026-05-06 05:50:59 -03:00
/// Keeps `write_media_control_request` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
/// Inputs are the typed parameters; output is the return value or side effect.
2026-04-30 15:04:00 -03:00
pub fn write_media_control_request ( path : & Path , state : & LauncherState ) -> Result < ( ) > {
crate ::live_media_control ::write_media_control_request (
path ,
2026-05-10 23:14:15 -03:00
crate ::live_media_control ::MediaControlState ::with_devices_and_audio (
2026-04-30 15:04:00 -03:00
state . channels . camera ,
state . channels . microphone ,
state . channels . audio ,
2026-05-02 10:31:22 -03:00
state . devices . camera . clone ( ) ,
state . camera_quality . map ( | mode | mode . id ( ) ) ,
state . devices . microphone . clone ( ) ,
state . devices . speaker . clone ( ) ,
2026-05-10 23:14:15 -03:00
state . upstream_audio_transport . as_common_codec ( ) ,
state . mic_noise_suppression ,
2026-04-30 15:04:00 -03:00
) ,
) ? ;
Ok ( ( ) )
}
2026-04-23 07:00:06 -03:00
pub fn write_input_toggle_key_request ( path : & Path , swap_key : & str ) -> Result < ( ) > {
std ::fs ::write (
path ,
format! ( " {} {} \n " , swap_key . trim ( ) , control_request_nonce ( ) ) ,
) ? ;
Ok ( ( ) )
}
2026-05-06 05:50:59 -03:00
/// Keeps `read_input_routing_state` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
/// Inputs are the typed parameters; output is the return value or side effect.
2026-04-23 07:00:06 -03:00
pub fn read_input_routing_state ( path : & Path ) -> Option < InputRouting > {
let raw = std ::fs ::read_to_string ( path ) . ok ( ) ? ;
match raw
. split_ascii_whitespace ( )
. next ( ) ?
. to_ascii_lowercase ( )
. as_str ( )
{
" local " = > Some ( InputRouting ::Local ) ,
" remote " = > Some ( InputRouting ::Remote ) ,
_ = > None ,
}
}
fn control_request_nonce ( ) -> u128 {
SystemTime ::now ( )
. duration_since ( UNIX_EPOCH )
. map ( | duration | duration . as_nanos ( ) )
. unwrap_or_default ( )
}
2026-05-06 05:50:59 -03:00
/// Keeps `routing_name` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
/// Inputs are the typed parameters; output is the return value or side effect.
2026-04-23 07:00:06 -03:00
pub fn routing_name ( routing : InputRouting ) -> & 'static str {
match routing {
InputRouting ::Local = > " local " ,
InputRouting ::Remote = > " remote " ,
}
}
pub fn path_marker ( path : & Path ) -> u128 {
std ::fs ::metadata ( path )
. ok ( )
. and_then ( | meta | meta . modified ( ) . ok ( ) )
. and_then ( | stamp | stamp . duration_since ( std ::time ::UNIX_EPOCH ) . ok ( ) )
. map ( | duration | duration . as_millis ( ) )
. unwrap_or_default ( )
}
2026-05-06 05:50:59 -03:00
/// Keeps `set_combo_active_text` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
/// Inputs are the typed parameters; output is the return value or side effect.
2026-04-23 07:00:06 -03:00
pub fn set_combo_active_text ( combo : & gtk ::ComboBoxText , wanted : Option < & str > ) {
let wanted = wanted . unwrap_or ( " auto " ) ;
if combo . set_active_id ( Some ( wanted ) ) {
return ;
}
if combo . set_active_id ( Some ( " auto " ) ) {
return ;
}
let _ = combo . set_active_id ( Some ( " all " ) ) ;
}
2026-05-06 05:50:59 -03:00
/// Keeps `toggle_key_label` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
/// Inputs are the typed parameters; output is the return value or side effect.
2026-04-23 07:00:06 -03:00
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 ( [ '_' , '-' ] , " " ) ) ,
}
}
2026-05-06 05:50:59 -03:00
/// Keeps `capture_swap_key` explicit because it sits on launcher state/UI wiring, where device choices should remain explainable across refreshes.
/// Inputs are the typed parameters; output is the return value or side effect.
2026-04-23 07:00:06 -03:00
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 ,
}
}