mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-05-09 15:48:04 +03:00
macos: run as LSUIElement menubar app with NSStatusItem
Ship Lan Mouse on macOS as an accessory app (no Dock icon, no main window on launch) with a status-bar item for show/quit. Closing the window hides it instead of quitting so the menu bar stays the primary surface. - build-aux/macos-lsui-element.plist: LSUIElement=true plus NSInputMonitoringUsageDescription and NSAppleEventsUsageDescription (merged into Info.plist via cargo-bundle's osx_info_plist_exts). - lan-mouse-gtk/src/macos_status_item.rs: NSStatusItem setup via raw objc_msgSend FFI. Loads a bundled 22pt PNG as a template image so it auto-tints for light/dark menu bars. - scripts/makeicns.sh: emit Contents/Resources/menubar-template.png from the existing SVG. - scripts/copy-macos-dylib.sh: flatten cargo-bundle's preserved target/ subdir under Resources so NSBundle pathForResource: finds the template image. - lan-mouse-gtk/src/lib.rs: register the new modules, set up a Cmd+Q-wired quit action, configure bundle env vars (schemas, XDG_DATA_DIRS, GTK_DATA_PREFIX) when running from inside the .app, and filter the known upstream Gtk theme-parser warning spam. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,10 @@ mod client_row;
|
||||
mod fingerprint_window;
|
||||
mod key_object;
|
||||
mod key_row;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos_privacy;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod macos_status_item;
|
||||
mod window;
|
||||
|
||||
use std::{env, process, str};
|
||||
@@ -47,6 +51,12 @@ pub fn run() -> Result<(), GtkError> {
|
||||
}
|
||||
|
||||
fn gtk_main() -> glib::ExitCode {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
configure_macos_bundle_environment();
|
||||
install_macos_gtk_log_filter();
|
||||
}
|
||||
|
||||
gio::resources_register_include!("lan-mouse.gresource").expect("Failed to register resources.");
|
||||
|
||||
let app = Application::builder()
|
||||
@@ -64,6 +74,64 @@ fn gtk_main() -> glib::ExitCode {
|
||||
app.run_with_args(&args)
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn install_macos_gtk_log_filter() {
|
||||
glib::log_set_writer_func(|level, fields| {
|
||||
if level == glib::LogLevel::Warning && is_gtk_theme_parser_warning(fields) {
|
||||
return glib::LogWriterOutput::Handled;
|
||||
}
|
||||
|
||||
glib::log_writer_default(level, fields)
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn is_gtk_theme_parser_warning(fields: &[glib::LogField<'_>]) -> bool {
|
||||
let mut domain = None;
|
||||
let mut message = None;
|
||||
|
||||
for field in fields {
|
||||
match field.key() {
|
||||
"GLIB_DOMAIN" => domain = field.value_str(),
|
||||
"MESSAGE" => message = field.value_str(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
domain == Some("Gtk")
|
||||
&& message.is_some_and(|message| message.starts_with("Theme parser warning: gtk.css:"))
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn configure_macos_bundle_environment() {
|
||||
let Ok(exe) = env::current_exe() else {
|
||||
return;
|
||||
};
|
||||
let Some(contents) = exe
|
||||
.parent()
|
||||
.and_then(|dir| dir.parent())
|
||||
.map(std::path::Path::to_owned)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
let share = contents.join("Resources").join("share");
|
||||
if !share.exists() {
|
||||
return;
|
||||
}
|
||||
|
||||
let schemas = share.join("glib-2.0").join("schemas");
|
||||
if schemas.exists() {
|
||||
env::set_var("GSETTINGS_SCHEMA_DIR", schemas);
|
||||
}
|
||||
|
||||
env::set_var("XDG_DATA_DIRS", &share);
|
||||
env::set_var(
|
||||
"GTK_DATA_PREFIX",
|
||||
contents.join("Resources").to_string_lossy().as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
fn load_icons() {
|
||||
let display = &Display::default().expect("Could not connect to a display.");
|
||||
let icon_theme = IconTheme::for_display(display);
|
||||
@@ -123,6 +191,16 @@ fn build_ui(app: &Application) {
|
||||
});
|
||||
|
||||
let window = Window::new(app, frontend_tx);
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
window.connect_close_request(|window| {
|
||||
window.set_visible(false);
|
||||
glib::Propagation::Stop
|
||||
});
|
||||
macos_status_item::setup(app, &window);
|
||||
// First-launch TCC prompts. No-op when already granted.
|
||||
macos_privacy::fire_initial_prompts();
|
||||
}
|
||||
|
||||
glib::spawn_future_local(clone!(
|
||||
#[weak]
|
||||
@@ -171,5 +249,6 @@ fn build_ui(app: &Application) {
|
||||
}
|
||||
));
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
window.present();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user