diff --git a/native/libmacbridge/AppDelegate.h b/native/libmacbridge/AppDelegate.h index f35f5d1..e37bb71 100644 --- a/native/libmacbridge/AppDelegate.h +++ b/native/libmacbridge/AppDelegate.h @@ -29,5 +29,7 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification; - (IBAction) statusIconClick: (id) sender; - (IBAction) contextMenuClick: (id) sender; +- (void) updateIcon: (char *)iconPath; +- (void) setIcon: (char *)iconPath; @end \ No newline at end of file diff --git a/native/libmacbridge/AppDelegate.mm b/native/libmacbridge/AppDelegate.mm index cd24e8b..5bfb256 100644 --- a/native/libmacbridge/AppDelegate.mm +++ b/native/libmacbridge/AppDelegate.mm @@ -26,15 +26,8 @@ // Setup status icon if (show_icon) { myStatusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; - - NSString *nsIconPath = [NSString stringWithUTF8String:icon_path]; - NSImage *statusImage = [[NSImage alloc] initWithContentsOfFile:nsIconPath]; - [statusImage setTemplate:YES]; - - [myStatusItem.button setImage:statusImage]; - [myStatusItem setHighlightMode:YES]; - [myStatusItem.button setAction:@selector(statusIconClick:)]; - [myStatusItem.button setTarget:self]; + + [self setIcon: icon_path]; } // Setup key listener @@ -66,6 +59,29 @@ }]; } +- (void) updateIcon: (char *)iconPath { + if (show_icon) { + [myStatusItem release]; + + [self setIcon: iconPath]; + } +} + +- (void) setIcon: (char *)iconPath { + if (show_icon) { + myStatusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; + + NSString *nsIconPath = [NSString stringWithUTF8String:iconPath]; + NSImage *statusImage = [[NSImage alloc] initWithContentsOfFile:nsIconPath]; + [statusImage setTemplate:YES]; + + [myStatusItem.button setImage:statusImage]; + [myStatusItem setHighlightMode:YES]; + [myStatusItem.button setAction:@selector(statusIconClick:)]; + [myStatusItem.button setTarget:self]; + } +} + - (IBAction) statusIconClick: (id) sender { icon_click_callback(context_instance); } diff --git a/native/libmacbridge/bridge.h b/native/libmacbridge/bridge.h index 169c3cf..a50212c 100644 --- a/native/libmacbridge/bridge.h +++ b/native/libmacbridge/bridge.h @@ -26,12 +26,13 @@ extern "C" { extern void * context_instance; extern char * icon_path; +extern char * disabled_icon_path; extern int32_t show_icon; /* * Initialize the AppDelegate and check for accessibility permissions */ -int32_t initialize(void * context, const char * icon_path, int32_t show_icon); +int32_t initialize(void * context, const char * icon_path, const char * disabled_icon_path, int32_t show_icon); /* * Start the event loop indefinitely. Blocking call. @@ -117,6 +118,11 @@ typedef void (*ContextMenuClickCallback)(void * self, int32_t id); extern ContextMenuClickCallback context_menu_click_callback; extern "C" void register_context_menu_click_callback(ContextMenuClickCallback callback); +/* + * Update the tray icon status + */ +extern "C" void update_tray_icon(int32_t enabled); + // SYSTEM /* diff --git a/native/libmacbridge/bridge.mm b/native/libmacbridge/bridge.mm index bafe1ae..27cd5b1 100644 --- a/native/libmacbridge/bridge.mm +++ b/native/libmacbridge/bridge.mm @@ -33,6 +33,7 @@ extern "C" { void * context_instance; char * icon_path; +char * disabled_icon_path; int32_t show_icon; AppDelegate * delegate_ptr; @@ -40,9 +41,10 @@ KeypressCallback keypress_callback; IconClickCallback icon_click_callback; ContextMenuClickCallback context_menu_click_callback; -int32_t initialize(void * context, const char * _icon_path, int32_t _show_icon) { +int32_t initialize(void * context, const char * _icon_path, const char * _disabled_icon_path, int32_t _show_icon) { context_instance = context; icon_path = strdup(_icon_path); + disabled_icon_path = strdup(_disabled_icon_path); show_icon = _show_icon; AppDelegate *delegate = [[AppDelegate alloc] init]; @@ -74,6 +76,18 @@ int32_t headless_eventloop() { return 0; } +void update_tray_icon(int32_t enabled) { + dispatch_async(dispatch_get_main_queue(), ^(void) { + NSApplication * application = [NSApplication sharedApplication]; + char * iconPath = icon_path; + if (!enabled) { + iconPath = disabled_icon_path; + } + + [[application delegate] updateIcon: iconPath]; + }); +} + void send_string(const char * string) { char * stringCopy = strdup(string); dispatch_async(dispatch_get_main_queue(), ^(void) { diff --git a/src/bridge/macos.rs b/src/bridge/macos.rs index c9f8c9f..01846d7 100644 --- a/src/bridge/macos.rs +++ b/src/bridge/macos.rs @@ -29,7 +29,12 @@ pub struct MacMenuItem { #[allow(improper_ctypes)] #[link(name = "macbridge", kind = "static")] extern "C" { - pub fn initialize(s: *const c_void, icon_path: *const c_char, show_icon: i32); + pub fn initialize( + s: *const c_void, + icon_path: *const c_char, + disabled_icon_path: *const c_char, + show_icon: i32, + ); pub fn eventloop(); pub fn headless_eventloop(); @@ -51,6 +56,7 @@ extern "C" { pub fn register_icon_click_callback(cb: extern "C" fn(_self: *mut c_void)); pub fn show_context_menu(items: *const MacMenuItem, count: i32) -> i32; pub fn register_context_menu_click_callback(cb: extern "C" fn(_self: *mut c_void, id: i32)); + pub fn update_tray_icon(enabled: i32); // Keyboard pub fn register_keypress_callback( diff --git a/src/context/macos.rs b/src/context/macos.rs index a5c9b54..284e0c7 100644 --- a/src/context/macos.rs +++ b/src/context/macos.rs @@ -34,6 +34,7 @@ use std::sync::Arc; use std::{fs, thread}; const STATUS_ICON_BINARY: &[u8] = include_bytes!("../res/mac/icon.png"); +const DISABLED_STATUS_ICON_BINARY: &[u8] = include_bytes!("../res/mac/icondisabled.png"); pub struct MacContext { pub send_channel: Sender, @@ -72,6 +73,7 @@ impl MacContext { // Initialize the status icon path let espanso_dir = super::get_data_dir(); let status_icon_target = espanso_dir.join("icon.png"); + let disabled_status_icon_target = espanso_dir.join("icondisabled.png"); if status_icon_target.exists() { info!("Status icon already initialized, skipping."); @@ -84,6 +86,19 @@ impl MacContext { }); } + if disabled_status_icon_target.exists() { + info!("Status icon (disabled) already initialized, skipping."); + } else { + fs::write(&disabled_status_icon_target, DISABLED_STATUS_ICON_BINARY).unwrap_or_else( + |e| { + error!( + "Error copying the Status Icon (disabled) to the espanso data directory: {}", + e + ); + }, + ); + } + unsafe { let context_ptr = &*context as *const MacContext as *const c_void; @@ -93,9 +108,17 @@ impl MacContext { let status_icon_path = CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default(); + let disabled_status_icon_path = + CString::new(disabled_status_icon_target.to_str().unwrap_or_default()) + .unwrap_or_default(); let show_icon = if config.show_icon { 1 } else { 0 }; - initialize(context_ptr, status_icon_path.as_ptr(), show_icon); + initialize( + context_ptr, + status_icon_path.as_ptr(), + disabled_status_icon_path.as_ptr(), + show_icon, + ); } context @@ -146,6 +169,12 @@ impl MacContext { } } +pub fn update_icon(enabled: bool) { + unsafe { + crate::bridge::macos::update_tray_icon(if enabled { 1 } else { 0 }); + } +} + impl super::Context for MacContext { fn eventloop(&self) { // Start the SecureInput watcher thread diff --git a/src/context/mod.rs b/src/context/mod.rs index ea656fe..0f95483 100644 --- a/src/context/mod.rs +++ b/src/context/mod.rs @@ -50,7 +50,7 @@ pub fn new( #[cfg(target_os = "macos")] pub fn update_icon(enabled: bool) { - // TODO: add update icon on macOS + macos::update_icon(enabled); } #[cfg(target_os = "macos")] diff --git a/src/engine.rs b/src/engine.rs index 3406181..7bd7ce9 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -498,9 +498,14 @@ impl< if config.secure_input_notification && config.show_notifications { self.ui_manager.notify_delay(&format!("{} has activated SecureInput. Espanso won't work until you disable it.", app_name), 5000); } + + crate::context::update_icon(false); } SystemEvent::SecureInputDisabled => { info!("SecureInput has been disabled."); + + let is_enabled = self.enabled.borrow(); + crate::context::update_icon(*is_enabled); } SystemEvent::NotifyRequest(message) => { let config = self.config_manager.default_config(); diff --git a/src/res/mac/icondisabled.png b/src/res/mac/icondisabled.png new file mode 100644 index 0000000..a289156 Binary files /dev/null and b/src/res/mac/icondisabled.png differ