Add MacOS context menu callback
This commit is contained in:
parent
1261a76bcd
commit
2a60a87f3d
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
/*
|
||||
|
|
|
@ -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];
|
||||
});
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -119,7 +119,7 @@ impl <'a, S: KeyboardSender, C: ClipboardManager,
|
|||
self.ui_manager.show_menu(self.build_menu());
|
||||
},
|
||||
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();
|
||||
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
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, 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); }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user