Skip to content

Commit a7b4e5f

Browse files
RMANOVclaude
andcommitted
fix: click-outside polling, adaptive height, section reorder, Wayland crash fix
Python (launcher.pyw): - Replace unreliable <FocusOut> with click-outside polling (150ms, atomic tuple) - Adaptive window height from content (no hardcoded 400) - Add Linux recent files support (recently-used.xbel) - Move Clipboard section below Shortcuts - Fix keyboard shortcut order to match visual layout - Fix bare except: → except Exception: (known bug pattern) Rust (app.rs): - Move viewport resize outside CentralPanel closure (fixes SIGABRT on Wayland) - Add 5-frame resize cooldown to prevent oscillation - Replace frame-count focus threshold with has_been_focused flag - Move Clipboard section below Shortcuts - Apply dark_theme() only on first frame (was allocating every frame) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 091ecc0 commit a7b4e5f

4 files changed

Lines changed: 457 additions & 326 deletions

File tree

crates/bin/src/input.rs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,33 @@
22
33
use evdev::{Device, InputEventKind, Key};
44
use std::os::unix::io::AsRawFd;
5+
use std::process::Command as ProcCommand;
56
use std::sync::mpsc::{channel, Receiver, Sender};
67
use std::sync::{Arc, Mutex};
78
use std::thread;
89
use std::time::{Duration, Instant};
910

11+
/// Query the current cursor position via X11 (works on Wayland via XWayland).
12+
fn query_cursor_position() -> (f64, f64) {
13+
if let Ok(output) = ProcCommand::new("python3")
14+
.args([
15+
"-c",
16+
"from Xlib import display;d=display.Display();p=d.screen().root.query_pointer()._data;print(p['root_x'],p['root_y'])",
17+
])
18+
.output()
19+
{
20+
if let Ok(s) = std::string::String::from_utf8(output.stdout) {
21+
let parts: Vec<&str> = s.trim().split_whitespace().collect();
22+
if parts.len() == 2 {
23+
if let (Ok(x), Ok(y)) = (parts[0].parse(), parts[1].parse()) {
24+
return (x, y);
25+
}
26+
}
27+
}
28+
}
29+
(400.0, 300.0) // fallback: visible on screen
30+
}
31+
1032
/// Trigger event sent when L+R click is detected
1133
#[derive(Debug, Clone)]
1234
pub struct TriggerEvent {
@@ -91,7 +113,11 @@ impl InputListener {
91113
right_time.duration_since(left_time)
92114
};
93115

94-
log::debug!("check_trigger: diff={:?} threshold={:?}", diff, self.simultaneous_threshold);
116+
log::debug!(
117+
"check_trigger: diff={:?} threshold={:?}",
118+
diff,
119+
self.simultaneous_threshold
120+
);
95121
if diff > self.simultaneous_threshold {
96122
log::debug!("check_trigger: REJECTED - diff too large");
97123
return None;
@@ -114,7 +140,7 @@ impl InputListener {
114140
state.right_pressed = None;
115141

116142
Some(TriggerEvent {
117-
position: (0.0, 0.0), // evdev doesn't provide absolute position
143+
position: query_cursor_position(),
118144
timestamp: now,
119145
})
120146
}
@@ -182,9 +208,8 @@ impl InputListener {
182208

183209
loop {
184210
// Wait for events on any device (100ms timeout)
185-
let ret = unsafe {
186-
libc::poll(pollfds.as_mut_ptr(), pollfds.len() as libc::nfds_t, 100)
187-
};
211+
let ret =
212+
unsafe { libc::poll(pollfds.as_mut_ptr(), pollfds.len() as libc::nfds_t, 100) };
188213

189214
if ret <= 0 {
190215
continue; // Timeout or error, try again

crates/bin/src/main.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,15 @@ use std::sync::{Arc, Mutex};
1212

1313
fn main() -> Result<()> {
1414
// Initialize logging
15-
env_logger::Builder::from_env(
16-
env_logger::Env::default().default_filter_or("info"),
17-
)
18-
.format_timestamp_secs()
19-
.init();
15+
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info"))
16+
.format_timestamp_secs()
17+
.init();
2018

2119
log::info!("Starting Simple Program Launcher");
2220

2321
// Load configuration
24-
let config_manager = Arc::new(
25-
ConfigManager::new().context("Failed to initialize config manager")?,
26-
);
22+
let config_manager =
23+
Arc::new(ConfigManager::new().context("Failed to initialize config manager")?);
2724

2825
// Initialize usage tracker
2926
let usage_tracker = Arc::new(Mutex::new(
@@ -63,7 +60,11 @@ fn main() -> Result<()> {
6360
);
6461

6562
// Show the popup window on main thread (required by winit)
66-
if let Err(e) = run_popup(trigger.position, config_manager.clone(), usage_tracker.clone()) {
63+
if let Err(e) = run_popup(
64+
trigger.position,
65+
config_manager.clone(),
66+
usage_tracker.clone(),
67+
) {
6768
log::error!("Popup error: {}", e);
6869
}
6970
}

0 commit comments

Comments
 (0)