mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-05-07 22:58:05 +03:00
macos: re-present window on app re-launch
Closing the menubar window hides it; re-launching Lan Mouse.app via Finder, `open`, or the Dock should bring the window back — but it didn't, because the kAEReopenApplication Apple Event was silently dropped. NSApp's default 'aevt'/'rapp' handler funnels the event into applicationShouldHandleReopen:hasVisibleWindows:, and GtkApplication owns NSApp's delegate without implementing that selector. Register the existing status-item delegate as a direct handler for 'aevt'/'rapp' via NSAppleEventManager. setEventHandler: replaces NSApplication's default, so we receive the event in our code and re-present the window plus call activateIgnoringOtherApps:. Extract a present_window() helper so the menubar's "Open Lan Mouse" item and the new re-open handler share one code path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
committed by
Ferdinand Schober
parent
e5862e10e3
commit
53c668b355
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
ffi::{CStr, CString, c_char, c_double, c_void},
|
||||
ffi::{CStr, CString, c_char, c_double, c_uint, c_void},
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
@@ -45,7 +45,10 @@ pub fn setup(app: &adw::Application, window: &Window) {
|
||||
let hold = app.hold();
|
||||
|
||||
let ns_app = msg_send_id(class(c"NSApplication"), sel(c"sharedApplication"));
|
||||
assert!(!ns_app.is_null(), "NSApplication sharedApplication returned null");
|
||||
assert!(
|
||||
!ns_app.is_null(),
|
||||
"NSApplication sharedApplication returned null"
|
||||
);
|
||||
msg_send_bool_usize(ns_app, sel(c"setActivationPolicy:"), 1);
|
||||
|
||||
let delegate = new_delegate();
|
||||
@@ -56,7 +59,10 @@ pub fn setup(app: &adw::Application, window: &Window) {
|
||||
]);
|
||||
|
||||
let status_bar = msg_send_id(class(c"NSStatusBar"), sel(c"systemStatusBar"));
|
||||
assert!(!status_bar.is_null(), "NSStatusBar systemStatusBar returned null");
|
||||
assert!(
|
||||
!status_bar.is_null(),
|
||||
"NSStatusBar systemStatusBar returned null"
|
||||
);
|
||||
let status_item = msg_send_id_f64(status_bar, sel(c"statusItemWithLength:"), -1.0);
|
||||
assert!(!status_item.is_null(), "statusItemWithLength returned null");
|
||||
// Retain so the status item survives autorelease pool drain.
|
||||
@@ -72,6 +78,8 @@ pub fn setup(app: &adw::Application, window: &Window) {
|
||||
msg_send_void_id(item, sel(c"setTarget:"), delegate);
|
||||
}
|
||||
|
||||
install_reopen_handler(delegate);
|
||||
|
||||
log::debug!("macos_status_item ready at {:p}", status_item);
|
||||
|
||||
item.replace(Some(StatusItem {
|
||||
@@ -194,12 +202,29 @@ fn delegate_class() -> Class {
|
||||
quit_lan_mouse as *const c_void,
|
||||
c"v@:@".as_ptr(),
|
||||
);
|
||||
// kAEReopenApplication handler — fires when the user re-launches
|
||||
// the .app while it's already running (Finder, `open`, Dock).
|
||||
class_addMethod(
|
||||
class,
|
||||
sel(c"handleReopenEvent:withReplyEvent:"),
|
||||
handle_reopen_event as *const c_void,
|
||||
c"v@:@@".as_ptr(),
|
||||
);
|
||||
objc_registerClassPair(class);
|
||||
class as usize
|
||||
}) as Class
|
||||
}
|
||||
|
||||
extern "C" fn show_lan_mouse(_this: Id, _cmd: Sel, _sender: Id) {
|
||||
present_window();
|
||||
}
|
||||
|
||||
extern "C" fn handle_reopen_event(_this: Id, _cmd: Sel, _event: Id, _reply: Id) {
|
||||
log::debug!("kAEReopenApplication received — presenting main window");
|
||||
present_window();
|
||||
}
|
||||
|
||||
fn present_window() {
|
||||
STATUS_ITEM.with(|item| {
|
||||
let item = item.borrow();
|
||||
let Some(item) = item.as_ref() else {
|
||||
@@ -216,6 +241,35 @@ extern "C" fn show_lan_mouse(_this: Id, _cmd: Sel, _sender: Id) {
|
||||
});
|
||||
}
|
||||
|
||||
// Register the status-item delegate as the handler for the
|
||||
// kAEReopenApplication Apple Event ('aevt'/'rapp'). NSApplication
|
||||
// installs a default handler at -finishLaunching that just delegates to
|
||||
// applicationShouldHandleReopen:hasVisibleWindows: — which is a no-op
|
||||
// here because GApplication owns NSApp's delegate. Replacing it lets us
|
||||
// re-present the window when the user double-clicks the .app while
|
||||
// we're already running.
|
||||
unsafe fn install_reopen_handler(delegate: Id) {
|
||||
const K_CORE_EVENT_CLASS: c_uint = 0x6165_7674; // 'aevt'
|
||||
const K_AE_REOPEN_APPLICATION: c_uint = 0x7261_7070; // 'rapp'
|
||||
|
||||
let manager = msg_send_id(
|
||||
class(c"NSAppleEventManager"),
|
||||
sel(c"sharedAppleEventManager"),
|
||||
);
|
||||
if manager.is_null() {
|
||||
log::warn!("NSAppleEventManager unavailable; re-launch will not re-open window");
|
||||
return;
|
||||
}
|
||||
msg_send_void_id_sel_u32_u32(
|
||||
manager,
|
||||
sel(c"setEventHandler:andSelector:forEventClass:andEventID:"),
|
||||
delegate,
|
||||
sel(c"handleReopenEvent:withReplyEvent:"),
|
||||
K_CORE_EVENT_CLASS,
|
||||
K_AE_REOPEN_APPLICATION,
|
||||
);
|
||||
}
|
||||
|
||||
extern "C" fn quit_lan_mouse(_this: Id, _cmd: Sel, _sender: Id) {
|
||||
STATUS_ITEM.with(|item| {
|
||||
if let Some(app) = item.borrow().as_ref().and_then(|item| item.app.upgrade()) {
|
||||
@@ -280,4 +334,13 @@ extern "C" {
|
||||
fn msg_send_void_id(receiver: Id, selector: Sel, value: Id);
|
||||
#[link_name = "objc_msgSend"]
|
||||
fn msg_send_bool_usize(receiver: Id, selector: Sel, value: usize) -> Bool;
|
||||
#[link_name = "objc_msgSend"]
|
||||
fn msg_send_void_id_sel_u32_u32(
|
||||
receiver: Id,
|
||||
selector: Sel,
|
||||
a: Id,
|
||||
b: Sel,
|
||||
c: c_uint,
|
||||
d: c_uint,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user