mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-05-09 23: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::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
ffi::{CStr, CString, c_char, c_double, c_void},
|
ffi::{CStr, CString, c_char, c_double, c_uint, c_void},
|
||||||
sync::OnceLock,
|
sync::OnceLock,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -45,7 +45,10 @@ pub fn setup(app: &adw::Application, window: &Window) {
|
|||||||
let hold = app.hold();
|
let hold = app.hold();
|
||||||
|
|
||||||
let ns_app = msg_send_id(class(c"NSApplication"), sel(c"sharedApplication"));
|
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);
|
msg_send_bool_usize(ns_app, sel(c"setActivationPolicy:"), 1);
|
||||||
|
|
||||||
let delegate = new_delegate();
|
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"));
|
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);
|
let status_item = msg_send_id_f64(status_bar, sel(c"statusItemWithLength:"), -1.0);
|
||||||
assert!(!status_item.is_null(), "statusItemWithLength returned null");
|
assert!(!status_item.is_null(), "statusItemWithLength returned null");
|
||||||
// Retain so the status item survives autorelease pool drain.
|
// 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);
|
msg_send_void_id(item, sel(c"setTarget:"), delegate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
install_reopen_handler(delegate);
|
||||||
|
|
||||||
log::debug!("macos_status_item ready at {:p}", status_item);
|
log::debug!("macos_status_item ready at {:p}", status_item);
|
||||||
|
|
||||||
item.replace(Some(StatusItem {
|
item.replace(Some(StatusItem {
|
||||||
@@ -194,12 +202,29 @@ fn delegate_class() -> Class {
|
|||||||
quit_lan_mouse as *const c_void,
|
quit_lan_mouse as *const c_void,
|
||||||
c"v@:@".as_ptr(),
|
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);
|
objc_registerClassPair(class);
|
||||||
class as usize
|
class as usize
|
||||||
}) as Class
|
}) as Class
|
||||||
}
|
}
|
||||||
|
|
||||||
extern "C" fn show_lan_mouse(_this: Id, _cmd: Sel, _sender: Id) {
|
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| {
|
STATUS_ITEM.with(|item| {
|
||||||
let item = item.borrow();
|
let item = item.borrow();
|
||||||
let Some(item) = item.as_ref() else {
|
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) {
|
extern "C" fn quit_lan_mouse(_this: Id, _cmd: Sel, _sender: Id) {
|
||||||
STATUS_ITEM.with(|item| {
|
STATUS_ITEM.with(|item| {
|
||||||
if let Some(app) = item.borrow().as_ref().and_then(|item| item.app.upgrade()) {
|
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);
|
fn msg_send_void_id(receiver: Id, selector: Sel, value: Id);
|
||||||
#[link_name = "objc_msgSend"]
|
#[link_name = "objc_msgSend"]
|
||||||
fn msg_send_bool_usize(receiver: Id, selector: Sel, value: usize) -> Bool;
|
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