Add MacOS context menu callback

This commit is contained in:
Federico Terzi 2019-09-13 14:35:46 +02:00
parent 1261a76bcd
commit 2a60a87f3d
10 changed files with 194 additions and 13 deletions

View File

@ -3,8 +3,12 @@
#include "bridge.h" #include "bridge.h"
@interface AppDelegate : NSObject <NSApplicationDelegate> @interface AppDelegate : NSObject <NSApplicationDelegate> {
@public NSStatusItem *myStatusItem;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification; - (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
- (IBAction) statusIconClick: (id) sender;
- (IBAction) contextMenuClick: (id) sender;
@end @end

View File

@ -18,6 +18,19 @@ BOOL checkAccessibility()
NSLog(@"Accessibility Disabled"); 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"); NSLog(@"registering keydown mask");
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSEventMaskKeyDown | NSEventMaskFlagsChanged) [NSEvent addGlobalMonitorForEventsMatchingMask:(NSEventMaskKeyDown | NSEventMaskFlagsChanged)
handler:^(NSEvent *event){ 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<int32_t>(item_id));
}
@end @end

View File

@ -6,11 +6,12 @@
extern "C" { extern "C" {
extern void * context_instance; extern void * context_instance;
extern char * icon_path;
/* /*
* Initialize the AppDelegate and check for accessibility permissions * 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. * Start the event loop indefinitely. Blocking call.
@ -50,6 +51,32 @@ void delete_string(int32_t count);
*/ */
void trigger_paste(); 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 // SYSTEM
/* /*

View File

@ -2,6 +2,7 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#include "AppDelegate.h" #include "AppDelegate.h"
#include <stdio.h>
#include <string.h> #include <string.h>
extern "C" { extern "C" {
@ -9,13 +10,20 @@ extern "C" {
#include <vector> #include <vector>
KeypressCallback keypress_callback;
void * context_instance; 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; context_instance = context;
icon_path = strdup(_icon_path);
AppDelegate *delegate = [[AppDelegate alloc] init]; AppDelegate *delegate = [[AppDelegate alloc] init];
delegate_ptr = delegate;
NSApplication * application = [NSApplication sharedApplication]; NSApplication * application = [NSApplication sharedApplication];
[application setDelegate:delegate]; [application setDelegate:delegate];
} }
@ -24,6 +32,15 @@ void register_keypress_callback(KeypressCallback callback) {
keypress_callback = 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() { int32_t eventloop() {
[NSApp run]; [NSApp run];
} }
@ -183,3 +200,31 @@ int32_t set_clipboard(char * text) {
NSString *nsText = [NSString stringWithUTF8String:text]; NSString *nsText = [NSString stringWithUTF8String:text];
[pasteboard setString:nsText forType:NSPasteboardTypeString]; [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; i<count_copy; i++) {
if (item_copy[i].type == 1) {
NSString *title = [NSString stringWithUTF8String:item_copy[i].name];
NSMenuItem *newMenu = [[NSMenuItem alloc] initWithTitle:title action:@selector(contextMenuClick:) keyEquivalent:@""];
[newMenu setTag:(NSInteger)item_copy[i].id];
[espansoMenu addItem: newMenu];
}else{
[espansoMenu addItem: [NSMenuItem separatorItem]];
}
}
free(item_copy);
[delegate_ptr->myStatusItem popUpStatusItemMenu:espansoMenu];
});
}

View File

@ -1,9 +1,16 @@
use std::os::raw::{c_void, c_char}; 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)] #[allow(improper_ctypes)]
#[link(name="macbridge", kind="static")] #[link(name="macbridge", kind="static")]
extern { extern {
pub fn initialize(s: *const c_void); pub fn initialize(s: *const c_void, icon_path: *const c_char);
pub fn eventloop(); pub fn eventloop();
// System // System
@ -14,6 +21,11 @@ extern {
pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32; pub fn get_clipboard(buffer: *mut c_char, size: i32) -> i32;
pub fn set_clipboard(text: *const c_char) -> 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 // Keyboard
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8, pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const u8,
i32, i32, i32)); i32, i32, i32));

View File

@ -1,8 +1,14 @@
use std::sync::mpsc::Sender; use std::sync::mpsc::Sender;
use std::os::raw::c_void; use std::os::raw::c_void;
use crate::bridge::macos::*; use crate::bridge::macos::*;
use crate::event::{Event, KeyEvent, KeyModifier}; use crate::event::{Event, KeyEvent, KeyModifier, ActionEvent, ActionType};
use crate::event::KeyModifier::*; 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 struct MacContext {
pub send_channel: Sender<Event> pub send_channel: Sender<Event>
@ -14,12 +20,27 @@ impl MacContext {
send_channel 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 { unsafe {
let context_ptr = &*context as *const MacContext as *const c_void; let context_ptr = &*context as *const MacContext as *const c_void;
register_keypress_callback(keypress_callback); 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 context
@ -37,7 +58,7 @@ impl super::Context for MacContext {
// Native bridge code // Native bridge code
extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const u8, len: i32, 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 { unsafe {
let _self = _self as *mut MacContext; let _self = _self as *mut MacContext;
@ -68,3 +89,21 @@ 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();
}
}

View File

@ -119,7 +119,7 @@ impl <'a, S: KeyboardSender, C: ClipboardManager,
self.ui_manager.show_menu(self.build_menu()); self.ui_manager.show_menu(self.build_menu());
}, },
ActionEvent::ContextMenuClick(id) => { ActionEvent::ContextMenuClick(id) => {
println!("{:?}",id);
} }
} }
} }

View File

@ -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(); let mut toggle_press_time = self.toggle_press_time.borrow_mut();
if let Ok(elapsed) = toggle_press_time.elapsed() { if let Ok(elapsed) = toggle_press_time.elapsed() {
if elapsed.as_millis() < config.toggle_interval as u128 { if elapsed.as_millis() < config.toggle_interval as u128 {
self.toggle();
let is_enabled = self.is_enabled.borrow(); let is_enabled = self.is_enabled.borrow();
if !*is_enabled { if !*is_enabled {
self.current_set_queue.borrow_mut().clear(); self.current_set_queue.borrow_mut().clear();
} }
self.toggle();
} }
} }

BIN
src/res/mac/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 B

View File

@ -1,10 +1,13 @@
use std::fs::create_dir_all; use std::fs::create_dir_all;
use std::{fs, io}; use std::{fs, io};
use std::io::{Cursor}; use std::io::{Cursor};
use std::ffi::CString;
use log::{info, debug}; use log::{info, debug};
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; 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 NOTIFY_HELPER_BINARY : &'static [u8] = include_bytes!("../res/mac/EspansoNotifyHelper.zip");
const DEFAULT_NOTIFICATION_DELAY : f64 = 1.5; const DEFAULT_NOTIFICATION_DELAY : f64 = 1.5;
@ -25,7 +28,30 @@ impl super::UIManager for MacUIManager {
} }
fn show_menu(&self, menu: Vec<MenuItem>) { fn show_menu(&self, menu: Vec<MenuItem>) {
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); }
} }
} }