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"
@interface AppDelegate : NSObject <NSApplicationDelegate>
@interface AppDelegate : NSObject <NSApplicationDelegate> {
@public NSStatusItem *myStatusItem;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
- (IBAction) statusIconClick: (id) sender;
- (IBAction) contextMenuClick: (id) sender;
@end

View File

@ -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<int32_t>(item_id));
}
@end

View File

@ -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
/*

View File

@ -2,6 +2,7 @@
#import <Foundation/Foundation.h>
#include "AppDelegate.h"
#include <stdio.h>
#include <string.h>
extern "C" {
@ -9,13 +10,20 @@ extern "C" {
#include <vector>
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; 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};
#[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));

View File

@ -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<Event>
@ -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();
}
}

View File

@ -119,7 +119,7 @@ impl <'a, S: KeyboardSender, C: ClipboardManager,
self.ui_manager.show_menu(self.build_menu());
},
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();
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();
}
}

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, 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<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); }
}
}