Skip to content

Commit b3c2a88

Browse files
committed
Various fixes
1 parent 87f8b7f commit b3c2a88

13 files changed

Lines changed: 136 additions & 149 deletions

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ tokio = { version = "1", features = ["full"] }
5656
objc2 = "0.6"
5757
objc2-app-kit = "0.3"
5858

59+
[lints.rust]
60+
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(feature, values("cargo-clippy"))'] }
61+
5962
[profile.release]
6063
opt-level = 2
6164
lto = true
@@ -73,4 +76,3 @@ long_description = "Pixie is a macOS window focus tool with a leader key system
7376
osx_minimum_system_version = "10.15"
7477
osx_info_plist_exts = ["Info.plist.ext"]
7578

76-

src/accessibility.rs

Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -241,29 +241,14 @@ pub fn get_focused_window_with_retry(
241241
pub struct WindowInfo {
242242
pub pid: i32,
243243
pub title: String,
244-
pub role: String,
245-
}
246-
247-
impl WindowInfo {
248-
pub fn display_string(&self) -> String {
249-
let app_name = get_app_name(self.pid).unwrap_or_else(|_| "Unknown".to_string());
250-
if self.title.is_empty() {
251-
format!("{} (PID: {})", app_name, self.pid)
252-
} else {
253-
format!("{} - \"{}\" (PID: {})", app_name, self.title, self.pid)
254-
}
255-
}
256244
}
257245

258246
/// Get information about a window element
259247
pub fn get_window_info(element: &AXUIElement) -> Result<WindowInfo, PixieError> {
260248
let title = element.title().map(|s| s.to_string()).unwrap_or_default();
261-
262-
let role = element.role().map(|s| s.to_string()).unwrap_or_default();
263-
264249
let pid = get_pid(element)?;
265250

266-
Ok(WindowInfo { pid, title, role })
251+
Ok(WindowInfo { pid, title })
267252
}
268253

269254
/// Get the PID from an AXUIElement
@@ -435,7 +420,11 @@ fn read_bundle_icon_name(bundle_path: &str) -> Option<String> {
435420
use std::process::Command;
436421

437422
let output = Command::new("defaults")
438-
.args(["read", &format!("{bundle_path}/Contents/Info"), "CFBundleIconFile"])
423+
.args([
424+
"read",
425+
&format!("{bundle_path}/Contents/Info"),
426+
"CFBundleIconFile",
427+
])
439428
.output()
440429
.ok()?;
441430
if !output.status.success() {
@@ -589,7 +578,6 @@ pub struct WindowRect {
589578
pub y: f64,
590579
pub width: f64,
591580
pub height: f64,
592-
pub element: AXUIElement,
593581
pub pid: i32,
594582
pub window_id: Option<u32>,
595583
}
@@ -627,7 +615,6 @@ pub fn get_window_rect(element: &AXUIElement) -> Result<WindowRect, PixieError>
627615
y: rect.origin.y,
628616
width: rect.size.width,
629617
height: rect.size.height,
630-
element: element.clone(),
631618
pid,
632619
window_id,
633620
})
@@ -850,7 +837,7 @@ fn get_dict_f64(dict: &CFDictionary, key: &str) -> f64 {
850837
let key = CFString::new(key);
851838
unsafe {
852839
let mut value: *const std::ffi::c_void = std::ptr::null();
853-
let key_ptr = key.as_CFTypeRef() as *const std::ffi::c_void;
840+
let key_ptr = key.as_CFTypeRef();
854841
if core_foundation::dictionary::CFDictionaryGetValueIfPresent(
855842
dict.as_concrete_TypeRef(),
856843
key_ptr,
@@ -920,11 +907,6 @@ pub fn get_screens() -> Result<Vec<Screen>, PixieError> {
920907
Ok(screens)
921908
}
922909

923-
/// Get all visible windows on a specific monitor
924-
pub fn get_windows_on_monitor(screen: &Screen) -> Result<Vec<WindowEntry>, PixieError> {
925-
get_picker_windows(Some(screen))
926-
}
927-
928910
/// Get all windows used by the picker (including off-screen/minimized)
929911
pub fn get_all_windows() -> Result<Vec<WindowEntry>, PixieError> {
930912
get_picker_windows(None)
@@ -1085,7 +1067,9 @@ fn get_picker_windows(screen: Option<&Screen>) -> Result<Vec<WindowEntry>, Pixie
10851067
Ok(windows)
10861068
}
10871069

1088-
fn get_window_titles_for_pid(pid: i32) -> Result<std::collections::HashMap<u32, String>, PixieError> {
1070+
fn get_window_titles_for_pid(
1071+
pid: i32,
1072+
) -> Result<std::collections::HashMap<u32, String>, PixieError> {
10891073
let app_element = AXUIElement::application(pid);
10901074

10911075
let windows = app_element
@@ -1097,7 +1081,10 @@ fn get_window_titles_for_pid(pid: i32) -> Result<std::collections::HashMap<u32,
10971081
if let Some(win) = windows.get(i) {
10981082
let win = win.clone();
10991083
if let Ok(win_id) = get_window_id(&win) {
1100-
titles.insert(win_id, win.title().map(|s| s.to_string()).unwrap_or_default());
1084+
titles.insert(
1085+
win_id,
1086+
win.title().map(|s| s.to_string()).unwrap_or_default(),
1087+
);
11011088
}
11021089
}
11031090
}
@@ -1320,23 +1307,6 @@ pub fn toggle_fullscreen(element: &AXUIElement) -> Result<(), PixieError> {
13201307
Ok(())
13211308
}
13221309

1323-
pub fn center_window(element: &AXUIElement) -> Result<(), PixieError> {
1324-
let window_rect = get_window_rect(element)?;
1325-
let screen = get_screen_for_window(&window_rect)?;
1326-
1327-
let menu_bar_height = if screen.is_main { 25.0 } else { 0.0 };
1328-
1329-
let available_x = screen.x;
1330-
let available_y = screen.y + menu_bar_height;
1331-
let available_width = screen.width;
1332-
let available_height = screen.height - menu_bar_height;
1333-
1334-
let new_x = available_x + (available_width - window_rect.width) / 2.0;
1335-
let new_y = available_y + (available_height - window_rect.height) / 2.0;
1336-
1337-
set_window_rect(element, new_x, new_y, window_rect.width, window_rect.height)
1338-
}
1339-
13401310
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13411311
pub enum MonitorDirection {
13421312
Left,

src/config.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,19 @@ fn parse_key_code(s: &str) -> Result<KeyCode> {
495495
if c.is_ascii_digit() {
496496
return digit_to_code(c);
497497
}
498+
return match c {
499+
'=' => Ok(KeyCode::Equal),
500+
'-' => Ok(KeyCode::Minus),
501+
'[' => Ok(KeyCode::BracketLeft),
502+
']' => Ok(KeyCode::BracketRight),
503+
'\\' => Ok(KeyCode::Backslash),
504+
';' => Ok(KeyCode::Semicolon),
505+
'\'' => Ok(KeyCode::Quote),
506+
',' => Ok(KeyCode::Comma),
507+
'.' => Ok(KeyCode::Period),
508+
'/' => Ok(KeyCode::Slash),
509+
_ => Err(PixieError::Config(format!("Unknown key: {}", s))),
510+
};
498511
}
499512

500513
if s.starts_with('f') || s.starts_with('F') {
@@ -586,7 +599,7 @@ const LAUNCH_AGENT_PLIST: &str = r#"<?xml version="1.0" encoding="UTF-8"?>
586599
<string>com.pixie</string>
587600
<key>ProgramArguments</key>
588601
<array>
589-
<string>/Applications/Pixie.app/Contents/MacOS/Pixie</string>
602+
<string>/Applications/Pixie.app/Contents/MacOS/pixie</string>
590603
<string>--headless</string>
591604
</array>
592605
<key>RunAtLoad</key>

src/error.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use thiserror::Error;
44

5+
#[allow(dead_code)]
56
#[derive(Error, Debug)]
67
pub enum PixieError {
78
#[error("Accessibility API error: {0}")]

src/event_tap.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
use core_foundation::runloop::{CFRunLoop, kCFRunLoopCommonModes};
21
use core_foundation::base::TCFType;
2+
use core_foundation::runloop::{CFRunLoop, kCFRunLoopCommonModes};
33
use core_graphics::event::{
44
CGEventFlags, CGEventTap, CGEventTapLocation, CGEventTapOptions, CGEventTapPlacement,
55
CGEventType, EventField,
@@ -17,7 +17,6 @@ static PICKER_REPEAT_COUNTER: AtomicU8 = AtomicU8::new(0);
1717
#[derive(Debug, Clone)]
1818
pub enum EventTapAction {
1919
LeaderPressed,
20-
LeaderReleased,
2120
KeyPressed(i64, bool),
2221
ActionTriggered(Action),
2322
ArrowPressed(crate::accessibility::Direction),
@@ -159,8 +158,9 @@ impl EventHandler {
159158
| PickerInput::SelectUp
160159
| PickerInput::SearchChar('j')
161160
| PickerInput::SearchChar('k') => {
162-
let repeat = PICKER_REPEAT_COUNTER.fetch_add(1, Ordering::Relaxed);
163-
if repeat % 2 != 0 {
161+
let repeat =
162+
PICKER_REPEAT_COUNTER.fetch_add(1, Ordering::Relaxed);
163+
if !repeat.is_multiple_of(2) {
164164
event.set_type(CGEventType::Null);
165165
return;
166166
}

src/leader_mode.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub struct LeaderModeController {
2525
}
2626

2727
impl LeaderModeController {
28+
#[allow(dead_code)]
2829
pub fn new() -> Result<Self> {
2930
Self::with_timeout(Duration::from_secs(2))
3031
}
@@ -74,6 +75,7 @@ impl LeaderModeController {
7475
let _ = self.event_sender.send(event);
7576
}
7677

78+
#[allow(dead_code)]
7779
pub fn cancel(&self) {
7880
if self.is_listening.swap(false, Ordering::SeqCst) {
7981
let _ = self.event_sender.send(LeaderModeEvent::Cancelled);
@@ -94,6 +96,7 @@ impl LeaderModeController {
9496
.send(LeaderModeEvent::FocusDirection(direction));
9597
}
9698

99+
#[allow(dead_code)]
97100
pub fn send_action(&self, action: Action) {
98101
let _ = self
99102
.event_sender
@@ -104,6 +107,7 @@ impl LeaderModeController {
104107
self.event_receiver.clone()
105108
}
106109

110+
#[allow(dead_code)]
107111
pub fn is_listening(&self) -> bool {
108112
self.is_listening.load(Ordering::SeqCst)
109113
}

src/main.rs

Lines changed: 44 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ use event_tap::EventTapAction;
3434
use leader_mode::{LeaderModeController, LeaderModeEvent};
3535
use window::WindowManager;
3636

37-
struct WindowManagerState(pub Arc<WindowManager>);
37+
struct WindowManagerState;
3838
impl gpui::Global for WindowManagerState {}
3939

4040
/// Pixie - macOS Window Focusing Tool
@@ -75,45 +75,57 @@ static RUNNING: AtomicBool = AtomicBool::new(true);
7575

7676
#[tokio::main]
7777
async fn main() -> Result<()> {
78-
let args = Args::parse();
78+
let args = Args::parse_from(std::env::args().filter(|arg| !arg.starts_with("-psn_")));
7979

8080
let is_from_terminal = std::env::var("TERM_PROGRAM").is_ok();
8181
if is_from_terminal {
8282
println!("Note: Running from Terminal. If permissions don't work,");
8383
println!(" try running as: open /Applications/Pixie.app\n");
8484
}
8585

86-
let mut attempts = 0;
87-
loop {
88-
match accessibility::test_api_access() {
89-
Ok(()) => {
90-
tracing::info!("Accessibility API working");
91-
break;
92-
}
93-
Err(e) => {
94-
if attempts == 0 {
95-
println!("\n⚠️ Accessibility API not available: {}", e);
96-
println!("\nSteps to fix:");
97-
println!("1. System Preferences → Privacy & Security → Accessibility");
98-
println!("2. Make sure Pixie.app is in the list AND CHECKED");
99-
println!("3. If running from Terminal, also add Terminal.app");
100-
println!("\nOpening System Preferences...\n");
101-
102-
accessibility::request_accessibility_permissions();
103-
104-
let _ = std::process::Command::new("open")
105-
.arg("x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")
106-
.spawn();
86+
if is_from_terminal {
87+
let mut attempts = 0;
88+
loop {
89+
match accessibility::test_api_access() {
90+
Ok(()) => {
91+
tracing::info!("Accessibility API working");
92+
break;
10793
}
94+
Err(e) => {
95+
if attempts == 0 {
96+
println!("\n⚠️ Accessibility API not available: {}", e);
97+
println!("\nSteps to fix:");
98+
println!("1. System Preferences → Privacy & Security → Accessibility");
99+
println!("2. Make sure Pixie.app is in the list AND CHECKED");
100+
println!("3. If running from Terminal, also add Terminal.app");
101+
println!("\nOpening System Preferences...\n");
102+
103+
accessibility::request_accessibility_permissions();
104+
105+
let _ = std::process::Command::new("open")
106+
.arg("x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")
107+
.spawn();
108+
}
108109

109-
attempts += 1;
110-
if attempts % 5 == 0 {
111-
println!("Still waiting for permissions... (attempt {})", attempts);
112-
}
110+
attempts += 1;
111+
if attempts % 5 == 0 {
112+
println!("Still waiting for permissions... (attempt {})", attempts);
113+
}
113114

114-
std::thread::sleep(std::time::Duration::from_secs(1));
115+
std::thread::sleep(std::time::Duration::from_secs(1));
116+
}
115117
}
116118
}
119+
} else if let Err(e) = accessibility::test_api_access() {
120+
tracing::warn!(
121+
"Accessibility API not ready at app launch: {}. Prompting and exiting for relaunch.",
122+
e
123+
);
124+
accessibility::request_accessibility_permissions();
125+
let _ = std::process::Command::new("open")
126+
.arg("x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility")
127+
.spawn();
128+
return Ok(());
117129
}
118130

119131
let window_manager = Arc::new(WindowManager::new()?);
@@ -300,7 +312,9 @@ fn apply_autostart_setting(enabled: bool) {
300312
}
301313
}
302314

303-
fn runtime_bindings(cfg: &config::Config) -> (
315+
fn runtime_bindings(
316+
cfg: &config::Config,
317+
) -> (
304318
config::Modifiers,
305319
config::KeyCode,
306320
Vec<config::KeybindEntry>,
@@ -439,7 +453,7 @@ fn run_daemon(window_manager: Arc<WindowManager>, headless: bool) -> Result<()>
439453

440454
ui::init(cx);
441455

442-
cx.set_global(WindowManagerState(wm_for_events.clone()));
456+
cx.set_global(WindowManagerState);
443457

444458
let menu_bar_controller = if menubar_enabled {
445459
match menu_bar::MenuBarController::new(
@@ -484,7 +498,6 @@ fn run_daemon(window_manager: Arc<WindowManager>, headless: bool) -> Result<()>
484498
notification::notify("Pixie", "Listening...");
485499
let _ = ui_sender.send(UiAction::MenuBarSetActive(true));
486500
}
487-
EventTapAction::LeaderReleased => {}
488501
EventTapAction::KeyPressed(keycode, has_shift) => {
489502
if let Some(letter) = keycode_to_letter(keycode) {
490503
controller.handle_key(letter, has_shift);
@@ -775,7 +788,6 @@ fn run_headless_only(
775788
notification::notify("Pixie", "Listening...");
776789
println!("Listening...");
777790
}
778-
EventTapAction::LeaderReleased => {}
779791
EventTapAction::KeyPressed(keycode, has_shift) => {
780792
if let Some(letter) = keycode_to_letter(keycode) {
781793
controller_for_event.handle_key(letter, has_shift);

0 commit comments

Comments
 (0)