Add MacOS context menu callback
This commit is contained in:
parent
1261a76bcd
commit
2a60a87f3d
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -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];
|
||||||
|
});
|
||||||
|
}
|
|
@ -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));
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
BIN
src/res/mac/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 239 B |
|
@ -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); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user