diff --git a/native/libmacbridge/AppDelegate.h b/native/libmacbridge/AppDelegate.h index 86b0dc3..7fac0e7 100644 --- a/native/libmacbridge/AppDelegate.h +++ b/native/libmacbridge/AppDelegate.h @@ -3,8 +3,12 @@ #include "bridge.h" -@interface AppDelegate : NSObject +@interface AppDelegate : NSObject { + @public NSStatusItem *myStatusItem; +} - (void)applicationDidFinishLaunching:(NSNotification *)aNotification; +- (IBAction) statusIconClick: (id) sender; +- (IBAction) contextMenuClick: (id) sender; @end \ No newline at end of file diff --git a/native/libmacbridge/AppDelegate.mm b/native/libmacbridge/AppDelegate.mm index 6d1cdd1..b077954 100644 --- a/native/libmacbridge/AppDelegate.mm +++ b/native/libmacbridge/AppDelegate.mm @@ -18,6 +18,19 @@ BOOL checkAccessibility() NSLog(@"Accessibility Disabled"); } + // Setup status 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]; + + // Setup key listener NSLog(@"registering keydown mask"); [NSEvent addGlobalMonitorForEventsMatchingMask:(NSEventMaskKeyDown | NSEventMaskFlagsChanged) handler:^(NSEvent *event){ @@ -43,4 +56,19 @@ BOOL checkAccessibility() }]; } +- (IBAction) statusIconClick: (id) sender { + NSLog(@"Hello friends!"); + + icon_click_callback(context_instance); + +} + +- (IBAction) contextMenuClick: (id) sender { + NSLog(@"Click me up!"); + + NSInteger item_id = [[sender valueForKey:@"tag"] integerValue]; + + context_menu_click_callback(context_instance, static_cast(item_id)); +} + @end \ No newline at end of file diff --git a/native/libmacbridge/bridge.h b/native/libmacbridge/bridge.h index be939a0..bc9813d 100644 --- a/native/libmacbridge/bridge.h +++ b/native/libmacbridge/bridge.h @@ -6,11 +6,12 @@ extern "C" { extern void * context_instance; +extern char * icon_path; /* * Initialize the AppDelegate and check for accessibility permissions */ -int32_t initialize(void * context); +int32_t initialize(void * context, const char * icon_path); /* * Start the event loop indefinitely. Blocking call. @@ -50,6 +51,32 @@ void delete_string(int32_t count); */ void trigger_paste(); +// UI + +/* + * Called when the tray icon is clicked + */ +typedef void (*IconClickCallback)(void * self); +extern IconClickCallback icon_click_callback; +void register_icon_click_callback(IconClickCallback callback); + +// CONTEXT MENU + +typedef struct { + int32_t id; + int32_t type; + char name[100]; +} MenuItem; + +int32_t show_context_menu(MenuItem * items, int32_t count); + +/* + * Called when the context menu is clicked + */ +typedef void (*ContextMenuClickCallback)(void * self, int32_t id); +extern ContextMenuClickCallback context_menu_click_callback; +extern "C" void register_context_menu_click_callback(ContextMenuClickCallback callback); + // SYSTEM /* diff --git a/native/libmacbridge/bridge.mm b/native/libmacbridge/bridge.mm index acc594a..b21eda7 100644 --- a/native/libmacbridge/bridge.mm +++ b/native/libmacbridge/bridge.mm @@ -2,6 +2,7 @@ #import #include "AppDelegate.h" +#include #include extern "C" { @@ -9,13 +10,20 @@ extern "C" { #include -KeypressCallback keypress_callback; void * context_instance; +char * icon_path; +AppDelegate * delegate_ptr; -int32_t initialize(void * context) { +KeypressCallback keypress_callback; +IconClickCallback icon_click_callback; +ContextMenuClickCallback context_menu_click_callback; + +int32_t initialize(void * context, const char * _icon_path) { context_instance = context; + icon_path = strdup(_icon_path); AppDelegate *delegate = [[AppDelegate alloc] init]; + delegate_ptr = delegate; NSApplication * application = [NSApplication sharedApplication]; [application setDelegate:delegate]; } @@ -24,6 +32,15 @@ void register_keypress_callback(KeypressCallback callback) { keypress_callback = callback; } +void register_icon_click_callback(IconClickCallback callback) { + icon_click_callback = callback; +} + +void register_context_menu_click_callback(ContextMenuClickCallback callback) { + context_menu_click_callback = callback; +} + + int32_t eventloop() { [NSApp run]; } @@ -182,4 +199,32 @@ int32_t set_clipboard(char * text) { NSString *nsText = [NSString stringWithUTF8String:text]; [pasteboard setString:nsText forType:NSPasteboardTypeString]; +} + +// CONTEXT MENU + +int32_t show_context_menu(MenuItem * items, int32_t count) { + MenuItem * item_copy = (MenuItem*)malloc(sizeof(MenuItem)*count); + memcpy(item_copy, items, sizeof(MenuItem)*count); + int32_t count_copy = count; + + dispatch_async(dispatch_get_main_queue(), ^(void) { + + NSMenu *espansoMenu = [[NSMenu alloc] initWithTitle:@"Espanso"]; + + for (int i = 0; imyStatusItem popUpStatusItemMenu:espansoMenu]; + }); } \ No newline at end of file diff --git a/src/bridge/macos.rs b/src/bridge/macos.rs index 6bd6bc2..25299a8 100644 --- a/src/bridge/macos.rs +++ b/src/bridge/macos.rs @@ -1,9 +1,16 @@ use std::os::raw::{c_void, c_char}; +#[repr(C)] +pub struct MacMenuItem { + pub item_id: i32, + pub item_type: i32, + pub item_name: [c_char; 100], +} + #[allow(improper_ctypes)] #[link(name="macbridge", kind="static")] extern { - pub fn initialize(s: *const c_void); + pub fn initialize(s: *const c_void, icon_path: *const c_char); pub fn eventloop(); // System @@ -14,6 +21,11 @@ extern { pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32; pub fn set_clipboard(text: *const c_char) -> i32; + // UI + pub fn register_icon_click_callback(cb: extern 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 fn(_self: *mut c_void, id: i32)); + // Keyboard pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8, i32, i32, i32)); diff --git a/src/context/macos.rs b/src/context/macos.rs index 6cec64f..2d28be0 100644 --- a/src/context/macos.rs +++ b/src/context/macos.rs @@ -1,8 +1,14 @@ use std::sync::mpsc::Sender; use std::os::raw::c_void; use crate::bridge::macos::*; -use crate::event::{Event, KeyEvent, KeyModifier}; +use crate::event::{Event, KeyEvent, KeyModifier, ActionEvent, ActionType}; use crate::event::KeyModifier::*; +use std::fs::create_dir_all; +use std::ffi::CString; +use std::fs; +use log::{info}; + +const STATUS_ICON_BINARY : &'static [u8] = include_bytes!("../res/mac/icon.png"); pub struct MacContext { pub send_channel: Sender @@ -14,12 +20,27 @@ impl MacContext { send_channel }); + // Initialize the status icon path + let data_dir = dirs::data_dir().expect("Can't obtain data_dir(), terminating."); + let espanso_dir = data_dir.join("espanso"); + let res = create_dir_all(&espanso_dir); + let status_icon_target = espanso_dir.join("icon.png"); + + if status_icon_target.exists() { + info!("Status icon already initialized, skipping."); + }else { + fs::write(&status_icon_target, STATUS_ICON_BINARY); + } + unsafe { let context_ptr = &*context as *const MacContext as *const c_void; register_keypress_callback(keypress_callback); + register_icon_click_callback(icon_click_callback); + register_context_menu_click_callback(context_menu_click_callback); - initialize(context_ptr); + let status_icon_path = CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default(); + initialize(context_ptr, status_icon_path.as_ptr()); } context @@ -37,7 +58,7 @@ impl super::Context for MacContext { // Native bridge code extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, - is_modifier: i32, key_code: i32) { + is_modifier: i32, key_code: i32) { unsafe { let _self = _self as *mut MacContext; @@ -67,4 +88,22 @@ extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, } } } +} + +extern fn icon_click_callback(_self: *mut c_void) { + unsafe { + let _self = _self as *mut MacContext; + + let event = Event::Action(ActionEvent::IconClick); + (*_self).send_channel.send(event).unwrap(); + } +} + +extern fn context_menu_click_callback(_self: *mut c_void, id: i32) { + unsafe { + let _self = _self as *mut MacContext; + + let event = Event::Action(ActionEvent::ContextMenuClick(ActionType::from(id))); + (*_self).send_channel.send(event).unwrap(); + } } \ No newline at end of file diff --git a/src/engine.rs b/src/engine.rs index e7a4538..d9ad72c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -119,7 +119,7 @@ impl <'a, S: KeyboardSender, C: ClipboardManager, self.ui_manager.show_menu(self.build_menu()); }, ActionEvent::ContextMenuClick(id) => { - + println!("{:?}",id); } } } diff --git a/src/matcher/scrolling.rs b/src/matcher/scrolling.rs index fa74211..ddc34eb 100644 --- a/src/matcher/scrolling.rs +++ b/src/matcher/scrolling.rs @@ -101,13 +101,13 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa let mut toggle_press_time = self.toggle_press_time.borrow_mut(); if let Ok(elapsed) = toggle_press_time.elapsed() { if elapsed.as_millis() < config.toggle_interval as u128 { + self.toggle(); + let is_enabled = self.is_enabled.borrow(); if !*is_enabled { self.current_set_queue.borrow_mut().clear(); } - - self.toggle(); } } diff --git a/src/res/mac/icon.png b/src/res/mac/icon.png new file mode 100644 index 0000000..04d9593 Binary files /dev/null and b/src/res/mac/icon.png differ diff --git a/src/ui/macos.rs b/src/ui/macos.rs index 6605d1c..f718972 100644 --- a/src/ui/macos.rs +++ b/src/ui/macos.rs @@ -1,10 +1,13 @@ use std::fs::create_dir_all; use std::{fs, io}; use std::io::{Cursor}; +use std::ffi::CString; use log::{info, debug}; use std::path::PathBuf; use std::process::Command; -use crate::ui::MenuItem; +use crate::ui::{MenuItem, MenuItemType}; +use crate::bridge::macos::{MacMenuItem, show_context_menu}; +use std::os::raw::c_char; const NOTIFY_HELPER_BINARY : &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip"); const DEFAULT_NOTIFICATION_DELAY : f64 = 1.5; @@ -25,7 +28,30 @@ impl super::UIManager for MacUIManager { } fn show_menu(&self, menu: Vec) { - unimplemented!() + let mut raw_menu = Vec::new(); + + for item in menu.iter() { + let text = CString::new(item.item_name.clone()).unwrap_or_default(); + let mut str_buff : [c_char; 100] = [0; 100]; + unsafe { + std::ptr::copy(text.as_ptr(), str_buff.as_mut_ptr(), item.item_name.len()); + } + + let menu_type = match item.item_type { + MenuItemType::Button => {1}, + MenuItemType::Separator => {2}, + }; + + let raw_item = MacMenuItem { + item_id: item.item_id, + item_type: menu_type, + item_name: str_buff, + }; + + raw_menu.push(raw_item); + } + + unsafe { show_context_menu(raw_menu.as_ptr(), raw_menu.len() as i32); } } }