ltk/event_loop/
text_scale.rsuse std::io::{ BufRead, BufReader };
use std::os::unix::process::CommandExt;
use std::process::{ Command, Stdio };
const SCHEMA: &str = "org.gnome.desktop.interface";
const KEY: &str = "text-scaling-factor";
fn unblock_signals( cmd: &mut Command ) -> &mut Command
{
unsafe
{
cmd.pre_exec( ||
{
let mut set: libc::sigset_t = std::mem::zeroed();
libc::sigemptyset( &mut set );
libc::sigprocmask( libc::SIG_SETMASK, &set, std::ptr::null_mut() );
Ok( () )
} )
}
}
pub( super ) fn spawn_watcher( tx: calloop::channel::Sender<f32> )
{
let _ = std::thread::Builder::new()
.name( "ltk-text-scale".into() )
.spawn( move ||
{
if let Some( v ) = read_current()
{
let _ = tx.send( v );
}
let Ok( mut child ) = unblock_signals(
Command::new( "gsettings" )
.args( [ "monitor", SCHEMA, KEY ] )
.stdout( Stdio::piped() )
.stderr( Stdio::null() ) )
.spawn()
else { return };
let Some( stdout ) = child.stdout.take() else { return };
for line in BufReader::new( stdout ).lines().map_while( Result::ok )
{
if let Some( v ) = parse_factor( &line )
{
if tx.send( v ).is_err() { break; }
}
}
let _ = child.kill();
} );
}
fn read_current() -> Option<f32>
{
let out = unblock_signals(
Command::new( "gsettings" )
.args( [ "get", SCHEMA, KEY ] )
.stderr( Stdio::null() ) )
.output()
.ok()?;
parse_factor( std::str::from_utf8( &out.stdout ).ok()? )
}
fn parse_factor( s: &str ) -> Option<f32>
{
s.rsplit( [ ' ', ':' ] )
.next()?
.trim()
.parse::<f32>()
.ok()
.filter( |v| *v > 0.0 )
}
#[ cfg( test ) ]
mod tests
{
use super::{ parse_factor, unblock_signals };
#[ test ]
fn parses_monitor_line_and_bare_value()
{
assert_eq!( parse_factor( "text-scaling-factor: 1.25" ), Some( 1.25 ) );
assert_eq!( parse_factor( "1.0\n" ), Some( 1.0 ) );
assert_eq!( parse_factor( "" ), None );
assert_eq!( parse_factor( "text-scaling-factor: nope" ), None );
}
fn spawn_and_read_sigblk( clear: bool ) -> u64
{
let mut cmd = std::process::Command::new( "sleep" );
cmd.arg( "30" );
if clear
{
unblock_signals( &mut cmd );
}
let mut child = cmd.spawn().unwrap();
let status = std::fs::read_to_string( format!( "/proc/{}/status", child.id() ) ).unwrap();
let _ = child.kill();
let _ = child.wait();
status.lines()
.find_map( |l| l.strip_prefix( "SigBlk:" ) )
.and_then( |v| u64::from_str_radix( v.trim(), 16 ).ok() )
.unwrap()
}
#[ test ]
fn child_starts_with_clear_signal_mask()
{
unsafe
{
let mut set: libc::sigset_t = std::mem::zeroed();
libc::sigemptyset( &mut set );
libc::sigaddset( &mut set, libc::SIGTERM );
libc::pthread_sigmask( libc::SIG_BLOCK, &set, std::ptr::null_mut() );
}
let sigterm_bit = 1u64 << ( libc::SIGTERM - 1 );
assert_ne!( spawn_and_read_sigblk( false ) & sigterm_bit, 0 );
assert_eq!( spawn_and_read_sigblk( true ) & sigterm_bit, 0 );
}
}