From 7d357149ff4ad29ab33a665629011c1e3f09cc39 Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Mon, 8 Feb 2021 21:13:33 +0100 Subject: [PATCH] First draft of macOS ui layer --- espanso-detect/build.rs | 5 +- espanso-ui/Cargo.toml | 3 + espanso-ui/build.rs | 19 +- espanso-ui/src/lib.rs | 3 + espanso-ui/src/mac/AppDelegate.h | 39 ++++ espanso-ui/src/mac/AppDelegate.mm | 134 ++++++++++++ espanso-ui/src/mac/mod.rs | 241 ++++++++++++++++++++++ espanso-ui/src/mac/native.h | 73 +++++++ espanso-ui/src/mac/native.mm | 72 +++++++ espanso/src/main.rs | 95 +++++---- espanso/src/{main_old.rs => main_old2.rs} | 2 +- 11 files changed, 638 insertions(+), 48 deletions(-) create mode 100644 espanso-ui/src/mac/AppDelegate.h create mode 100644 espanso-ui/src/mac/AppDelegate.mm create mode 100644 espanso-ui/src/mac/mod.rs create mode 100644 espanso-ui/src/mac/native.h create mode 100644 espanso-ui/src/mac/native.mm rename espanso/src/{main_old.rs => main_old2.rs} (97%) diff --git a/espanso-detect/build.rs b/espanso-detect/build.rs index 9347e70..5ae16b0 100644 --- a/espanso-detect/build.rs +++ b/espanso-detect/build.rs @@ -59,10 +59,7 @@ fn cc_config() { #[cfg(target_os = "macos")] fn cc_config() { - println!("cargo:rustc-link-lib=dylib=c++"); - println!("cargo:rustc-link-lib=static=macbridge"); - println!("cargo:rustc-link-lib=framework=Cocoa"); - println!("cargo:rustc-link-lib=framework=IOKit"); + // TODO } fn main() { diff --git a/espanso-ui/Cargo.toml b/espanso-ui/Cargo.toml index 4130370..3f99bfe 100644 --- a/espanso-ui/Cargo.toml +++ b/espanso-ui/Cargo.toml @@ -16,6 +16,9 @@ thiserror = "1.0.23" widestring = "0.4.3" lazycell = "1.3.0" +[target.'cfg(target_os="macos")'.dependencies] +lazycell = "1.3.0" + [target.'cfg(target_os="linux")'.dependencies] notify-rust = "4.2.2" diff --git a/espanso-ui/build.rs b/espanso-ui/build.rs index e93d4ee..1b36617 100644 --- a/espanso-ui/build.rs +++ b/espanso-ui/build.rs @@ -40,17 +40,24 @@ fn cc_config() { #[cfg(target_os = "linux")] fn cc_config() { - // println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/"); - // println!("cargo:rustc-link-lib=static=linuxbridge"); - // println!("cargo:rustc-link-lib=dylib=X11"); - // println!("cargo:rustc-link-lib=dylib=Xtst"); - // println!("cargo:rustc-link-lib=dylib=xdo"); + // Nothing to link on linux } #[cfg(target_os = "macos")] fn cc_config() { + println!("cargo:rerun-if-changed=src/mac/native.mm"); + println!("cargo:rerun-if-changed=src/mac/native.h"); + println!("cargo:rerun-if-changed=src/mac/AppDelegate.mm"); + println!("cargo:rerun-if-changed=src/mac/AppDelegate.h"); + cc::Build::new() + .cpp(true) + .include("src/mac/native.h") + .include("src/mac/AppDelegate.h") + .file("src/mac/native.mm") + .file("src/mac/AppDelegate.mm") + .compile("espansoui"); println!("cargo:rustc-link-lib=dylib=c++"); - println!("cargo:rustc-link-lib=static=macbridge"); + println!("cargo:rustc-link-lib=static=espansoui"); println!("cargo:rustc-link-lib=framework=Cocoa"); println!("cargo:rustc-link-lib=framework=IOKit"); } diff --git a/espanso-ui/src/lib.rs b/espanso-ui/src/lib.rs index 6b05f82..914c536 100644 --- a/espanso-ui/src/lib.rs +++ b/espanso-ui/src/lib.rs @@ -7,3 +7,6 @@ pub mod win32; #[cfg(target_os = "linux")] pub mod linux; + +#[cfg(target_os = "macos")] +pub mod mac; \ No newline at end of file diff --git a/espanso-ui/src/mac/AppDelegate.h b/espanso-ui/src/mac/AppDelegate.h new file mode 100644 index 0000000..c36ccf3 --- /dev/null +++ b/espanso-ui/src/mac/AppDelegate.h @@ -0,0 +1,39 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#import +#import + +#include "native.h" + +@interface AppDelegate : NSObject { + @public NSStatusItem *statusItem; + @public UIOptions options; + @public void *rust_instance; + @public EventCallback event_callback; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification; +- (void) setIcon: (int32_t) iconIndex; +- (void) popupMenu: (NSString *) payload; +- (void) showNotification: (NSString *) message withDelay:(double) delay; +- (IBAction) statusIconClick: (id) sender; +- (IBAction) contextMenuClick: (id) sender; + +@end \ No newline at end of file diff --git a/espanso-ui/src/mac/AppDelegate.mm b/espanso-ui/src/mac/AppDelegate.mm new file mode 100644 index 0000000..7f70715 --- /dev/null +++ b/espanso-ui/src/mac/AppDelegate.mm @@ -0,0 +1,134 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#import "AppDelegate.h" + +void addSeparatorMenu(NSMenu * parent); +void addSingleMenu(NSMenu * parent, id item); +void addSubMenu(NSMenu * parent, NSArray * items); + +@implementation AppDelegate + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification +{ + if (options.show_icon) { + statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain]; + [self setIcon: 0]; + } + + [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; +} + +- (void) setIcon: (int32_t)iconIndex { + if (options.show_icon) { + char * iconPath = options.icon_paths[iconIndex]; + NSString *nsIconPath = [NSString stringWithUTF8String:iconPath]; + + NSImage *statusImage = [[NSImage alloc] initWithContentsOfFile:nsIconPath]; + [statusImage setTemplate:YES]; + + [statusItem.button setImage:statusImage]; + [statusItem setHighlightMode:YES]; + [statusItem.button setAction:@selector(statusIconClick:)]; + [statusItem.button setTarget:self]; + } +} + +- (IBAction) statusIconClick: (id) sender { + UIEvent event = {}; + event.event_type = UI_EVENT_TYPE_ICON_CLICK; + if (event_callback && rust_instance) { + event_callback(rust_instance, event); + } +} + +- (void) popupMenu: (NSString *) payload { + NSError *jsonError; + NSData *data = [payload dataUsingEncoding:NSUTF8StringEncoding]; + NSArray *jsonMenuItems = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError]; + NSMenu *menu = [[NSMenu alloc] initWithTitle:@"Espanso"]; + addSubMenu(menu, jsonMenuItems); + [statusItem popUpStatusItemMenu: menu]; +} + +- (IBAction) contextMenuClick: (id) sender { + NSInteger itemId = [[sender valueForKey:@"tag"] integerValue]; + + UIEvent event = {}; + event.event_type = UI_EVENT_TYPE_CONTEXT_MENU_CLICK; + event.context_menu_id = (uint32_t) [itemId intValue]; + if (event_callback && rust_instance) { + event_callback(rust_instance, event); + } +} + +- (void) showNotification: (NSString *) message withDelay: (double) delay { + NSUserNotification *notification = [[NSUserNotification alloc] init]; + notification.title = @"Espanso"; + notification.informativeText = message; + notification.soundName = nil; + + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; + [[NSUserNotificationCenter defaultUserNotificationCenter] performSelector:@selector(removeDeliveredNotification:) withObject:notification afterDelay:delay]; +} + +@end + +// Menu utility methods + +void addSeparatorMenu(NSMenu * parent) +{ + [parent addItem: [NSMenuItem separatorItem]]; +} + +void addSingleMenu(NSMenu * parent, id item) +{ + id label = [item objectForKey:@"label"]; + id raw_id = [item objectForKey:@"raw_id"]; + if (label == nil || raw_id == nil) + { + return; + } + NSMenuItem *newMenu = [[NSMenuItem alloc] initWithTitle:label action:@selector(contextMenuClick:) keyEquivalent:@""]; + [newMenu setTag:(NSInteger)raw_id]; + [parent addItem: newMenu]; +} + +void addSubMenu(NSMenu * parent, NSArray * items) +{ + for (id item in items) { + id type = [item objectForKey:@"type"]; + if ([type isEqualToString:@"simple"]) + { + addSingleMenu(parent, item); + } + else if ([type isEqualToString:@"separator"]) + { + addSeparatorMenu(parent); + } + else if ([type isEqualToString:@"sub"]) + { + NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:[item objectForKey:@"label"] action:nil keyEquivalent:@""]; + NSMenu *subMenu = [[NSMenu alloc] init]; + [parent addItem: menuItem]; + addSubMenu(subMenu, [item objectForKey:@"items"]); + [menuItem setSubmenu: subMenu]; + } + } +} \ No newline at end of file diff --git a/espanso-ui/src/mac/mod.rs b/espanso-ui/src/mac/mod.rs new file mode 100644 index 0000000..5142921 --- /dev/null +++ b/espanso-ui/src/mac/mod.rs @@ -0,0 +1,241 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +use std::{cmp::min, collections::HashMap, ffi::{CString}, os::raw::c_char, thread::ThreadId}; + +use lazycell::LazyCell; +use log::{error, trace}; + +use crate::{event::UIEvent, icons::TrayIcon, menu::Menu}; + +// IMPORTANT: if you change these, also edit the native.h file. +const MAX_FILE_PATH: usize = 1024; +const MAX_ICON_COUNT: usize = 3; + +const UI_EVENT_TYPE_ICON_CLICK: i32 = 1; +const UI_EVENT_TYPE_CONTEXT_MENU_CLICK: i32 = 2; + +// Take a look at the native.h header file for an explanation of the fields +#[repr(C)] +pub struct RawUIOptions { + pub show_icon: i32, + + pub icon_paths: [[u8; MAX_FILE_PATH]; MAX_ICON_COUNT], + pub icon_paths_count: i32, +} +// Take a look at the native.h header file for an explanation of the fields +#[repr(C)] +pub struct RawUIEvent { + pub event_type: i32, + pub context_menu_id: u32, +} + +#[allow(improper_ctypes)] +#[link(name = "espansoui", kind = "static")] +extern "C" { + pub fn ui_initialize( + _self: *const MacEventLoop, + options: RawUIOptions, + ); + pub fn ui_eventloop( + event_callback: extern "C" fn(_self: *mut MacEventLoop, event: RawUIEvent), + ) -> i32; + pub fn ui_update_tray_icon(index: i32); + pub fn ui_show_notification(message: *const c_char, delay: f64); + pub fn ui_show_context_menu(payload: *const c_char); +} + +pub struct MacUIOptions<'a> { + pub show_icon: bool, + pub icon_paths: &'a Vec<(TrayIcon, String)>, +} + +pub fn create(options: MacUIOptions) -> (MacRemote, MacEventLoop) { + // Validate icons + if options.icon_paths.len() > MAX_ICON_COUNT { + panic!("MacOS UI received too many icon paths, please increase the MAX_ICON_COUNT constant to support more"); + } + + // Convert the icon paths to the internal representation + let mut icon_indexes: HashMap = HashMap::new(); + let mut icons = Vec::new(); + for (index, (tray_icon, path)) in options.icon_paths.iter().enumerate() { + icon_indexes.insert(tray_icon.clone(), index); + icons.push(path.clone()); + } + + let eventloop = MacEventLoop::new( + icons, + options.show_icon, + ); + let remote = MacRemote::new(icon_indexes); + + (remote, eventloop) +} + +pub type MacUIEventCallback = Box; + +pub struct MacEventLoop { + show_icon: bool, + icons: Vec, + + // Internal + _event_callback: LazyCell, + _init_thread_id: LazyCell, +} + +impl MacEventLoop { + pub(crate) fn new( + icons: Vec, + show_icon: bool, + ) -> Self { + Self { + icons, + show_icon, + _event_callback: LazyCell::new(), + _init_thread_id: LazyCell::new(), + } + } + + pub fn initialize(&mut self) { + // Convert the icon paths to the raw representation + let mut icon_paths: [[u8; MAX_FILE_PATH]; MAX_ICON_COUNT] = + [[0; MAX_FILE_PATH]; MAX_ICON_COUNT]; + for (i, icon_path) in icon_paths.iter_mut().enumerate().take(self.icons.len()) { + let c_path = CString::new(self.icons[i].clone()).expect("unable to create CString for UI tray icon path"); + let len = min(c_path.as_bytes().len(), MAX_FILE_PATH - 1); + icon_path[0..len].clone_from_slice(&c_path.as_bytes()[..len]); + } + + let options = RawUIOptions { + show_icon: if self.show_icon { 1 } else { 0 }, + icon_paths, + icon_paths_count: self.icons.len() as i32, + }; + + unsafe { ui_initialize(self as *const MacEventLoop, options) }; + + // Make sure the run() method is called in the same thread as initialize() + self + ._init_thread_id + .fill(std::thread::current().id()) + .expect("Unable to set initialization thread id"); + } + + pub fn run(&self, event_callback: MacUIEventCallback) { + // Make sure the run() method is called in the same thread as initialize() + if let Some(init_id) = self._init_thread_id.borrow() { + if init_id != &std::thread::current().id() { + panic!("MacEventLoop run() and initialize() methods should be called in the same thread"); + } + } + + if self._event_callback.fill(event_callback).is_err() { + panic!("Unable to set MacEventLoop callback"); + } + + extern "C" fn callback(_self: *mut MacEventLoop, event: RawUIEvent) { + if let Some(callback) = unsafe { (*_self)._event_callback.borrow() } { + let event: Option = event.into(); + if let Some(event) = event { + callback(event) + } else { + trace!("Unable to convert raw event to input event"); + } + } + } + + let error_code = unsafe { ui_eventloop(callback) }; + + if error_code <= 0 { + panic!("MacEventLoop exited with <= 0 code") + } + } +} + +pub struct MacRemote { + // Maps icon name to their index + icon_indexes: HashMap, +} + +impl MacRemote { + pub(crate) fn new( + icon_indexes: HashMap, + ) -> Self { + Self { + icon_indexes, + } + } + + pub fn update_tray_icon(&self, icon: TrayIcon) { + if let Some(index) = self.icon_indexes.get(&icon) { + unsafe { ui_update_tray_icon((*index) as i32) } + } else { + error!("Unable to update tray icon, invalid icon id"); + } + } + + pub fn show_notification(&self, message: &str) { + let c_string = CString::new(message); + match c_string { + Ok(message) => unsafe { ui_show_notification(message.as_ptr(), 3.0) }, + Err(error) => { + error!( + "Unable to show notification {}", + error + ); + } + } + } + + pub fn show_context_menu(&self, menu: &Menu) { + match menu.to_json() { + Ok(payload) => { + let c_string = CString::new(payload); + match c_string { + Ok(c_string) => unsafe { ui_show_context_menu(c_string.as_ptr()) }, + Err(error) => error!( + "Unable to show context menu, impossible to convert payload to c_string: {}", + error + ), + } + } + Err(error) => { + error!("Unable to show context menu, {}", error); + } + } + } +} + +#[allow(clippy::single_match)] // TODO: remove after another match is used +impl From for Option { + fn from(raw: RawUIEvent) -> Option { + match raw.event_type { + UI_EVENT_TYPE_ICON_CLICK => { + return Some(UIEvent::TrayIconClick); + } + UI_EVENT_TYPE_CONTEXT_MENU_CLICK => { + return Some(UIEvent::ContextMenuClick(raw.context_menu_id)); + } + _ => {} + } + + None + } +} \ No newline at end of file diff --git a/espanso-ui/src/mac/native.h b/espanso-ui/src/mac/native.h new file mode 100644 index 0000000..5b3b455 --- /dev/null +++ b/espanso-ui/src/mac/native.h @@ -0,0 +1,73 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#ifndef ESPANSO_UI_H +#define ESPANSO_UI_H + +#include + +// Explicitly define this constant as we need to use it from the Rust side +#define MAX_FILE_PATH 1024 +#define MAX_ICON_COUNT 3 + +#define UI_EVENT_TYPE_ICON_CLICK 1 +#define UI_EVENT_TYPE_CONTEXT_MENU_CLICK 2 + +typedef struct { + int32_t show_icon; + + char icon_paths[MAX_ICON_COUNT][MAX_FILE_PATH]; + int32_t icon_paths_count; +} UIOptions; + +typedef struct { + int32_t event_type; + uint32_t context_menu_id; +} UIEvent; + +typedef void (*EventCallback)(void * self, UIEvent data); + +typedef struct +{ + UIOptions options; + + // Rust interop + void *rust_instance; + EventCallback event_callback; +} UIVariables; + +// Initialize the Application delegate. +extern "C" void ui_initialize(void * self, UIOptions options); + +// Run the event loop. Blocking call. +extern "C" int32_t ui_eventloop(EventCallback callback); + +// Updates the tray icon to the given one. The method accepts an index that refers to +// the icon within the UIOptions.icon_paths array. +extern "C" void ui_update_tray_icon(int32_t index); + +// Show a native notification +extern "C" void ui_show_notification(char * message, double delay); + +// Display the context menu on the tray icon. +// Payload is passed as JSON as given the complex structure, parsing +// this manually would have been complex. +extern "C" void ui_show_context_menu(char * payload); + +#endif //ESPANSO_UI_H \ No newline at end of file diff --git a/espanso-ui/src/mac/native.mm b/espanso-ui/src/mac/native.mm new file mode 100644 index 0000000..9ed2692 --- /dev/null +++ b/espanso-ui/src/mac/native.mm @@ -0,0 +1,72 @@ +/* + * This file is part of espanso. + * + * Copyright (C) 2019-2021 Federico Terzi + * + * espanso is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * espanso is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with espanso. If not, see . + */ + +#include "native.h" +#include "AppDelegate.h" +#import +#include +#include +#include +#include + +void ui_initialize(void *_self, UIOptions _options) +{ + AppDelegate *delegate = [[AppDelegate alloc] init]; + delegate->options = _options; + delegate->rust_instance = _self; + + NSApplication * application = [NSApplication sharedApplication]; + [application setDelegate:delegate]; +} + +int32_t ui_eventloop(EventCallback _callback) +{ + AppDelegate *delegate = [[NSApplication sharedApplication] delegate]; + delegate->event_callback = _callback; + + [NSApp run]; + + return 1; +} + +void ui_update_tray_icon(int32_t index) +{ + dispatch_async(dispatch_get_main_queue(), ^(void) { + AppDelegate *delegate = [[NSApplication sharedApplication] delegate]; + [delegate setIcon: index]; + }); +} + +void ui_show_notification(char *message, double delay) +{ + NSString *nsMessage = [NSString stringWithUTF8String:message]; + dispatch_async(dispatch_get_main_queue(), ^(void) { + AppDelegate *delegate = [[NSApplication sharedApplication] delegate]; + [delegate showNotification: nsMessage withDelay: delay]; + }); +} + +void ui_show_context_menu(char *payload) +{ + NSString *nsPayload = [NSString stringWithUTF8String:payload]; + dispatch_async(dispatch_get_main_queue(), ^(void) { + AppDelegate *delegate = [[NSApplication sharedApplication] delegate]; + [delegate popupMenu: nsPayload]; + }); +} \ No newline at end of file diff --git a/espanso/src/main.rs b/espanso/src/main.rs index e51724e..5cc88bc 100644 --- a/espanso/src/main.rs +++ b/espanso/src/main.rs @@ -1,5 +1,5 @@ use espanso_detect::event::{InputEvent, Status}; -use espanso_ui::{linux::LinuxUIOptions, menu::*}; +use espanso_ui::{icons::TrayIcon, mac::*, menu::*, event::UIEvent::*}; use simplelog::{CombinedLogger, Config, LevelFilter, TermLogger, TerminalMode}; fn main() { @@ -14,14 +14,24 @@ fn main() { ]) .unwrap(); + // let icon_paths = vec![ + // ( + // espanso_ui::icons::TrayIcon::Normal, + // r"C:\Users\Freddy\AppData\Local\espanso\espanso.ico".to_string(), + // ), + // ( + // espanso_ui::icons::TrayIcon::Disabled, + // r"C:\Users\Freddy\AppData\Local\espanso\espansored.ico".to_string(), + // ), + // ]; let icon_paths = vec![ ( espanso_ui::icons::TrayIcon::Normal, - r"C:\Users\Freddy\AppData\Local\espanso\espanso.ico".to_string(), + r"/Users/freddy/Library/Application Support/espanso/icon.png".to_string(), ), ( espanso_ui::icons::TrayIcon::Disabled, - r"C:\Users\Freddy\AppData\Local\espanso\espansored.ico".to_string(), + r"/Users/freddy/Library/Application Support/espanso/icondisabled.png".to_string(), ), ]; @@ -32,44 +42,55 @@ fn main() { // notification_icon_path: r"C:\Users\Freddy\Insync\Development\Espanso\Images\icongreensmall.png" // .to_string(), // }); - let (remote, mut eventloop) = espanso_ui::linux::create(LinuxUIOptions { - notification_icon_path: r"/home/freddy/insync/Development/Espanso/Images/icongreensmall.png".to_owned(), + let (remote, mut eventloop) = espanso_ui::mac::create(MacUIOptions { + show_icon: true, + icon_paths: &icon_paths, }); let handle = std::thread::spawn(move || { + //let mut source = espanso_detect::win32::Win32Source::new(); - let mut source = espanso_detect::evdev::EVDEVSource::new(); - source.initialize(); - source.eventloop(Box::new(move |event: InputEvent| { - println!("ev {:?}", event); - match event { - InputEvent::Mouse(_) => {} - InputEvent::Keyboard(evt) => { - if evt.key == espanso_detect::event::Key::Shift && evt.status == Status::Pressed { - //remote.update_tray_icon(espanso_ui::icons::TrayIcon::Disabled); - remote.show_notification("Espanso is running!"); - } - } - } - })); + //let mut source = espanso_detect::x11::X11Source::new(); + // source.initialize(); + // source.eventloop(Box::new(move |event: InputEvent| { + // println!("ev {:?}", event); + // match event { + // InputEvent::Mouse(_) => {} + // InputEvent::Keyboard(evt) => { + // if evt.key == espanso_detect::event::Key::Shift && evt.status == Status::Pressed { + // //remote.update_tray_icon(espanso_ui::icons::TrayIcon::Disabled); + // remote.show_notification("Espanso is running!"); + // } + // } + // } + // })); }); - // eventloop.initialize(); - // eventloop.run(Box::new(move |event| { - // println!("ui {:?}", event); - // let menu = Menu::from(vec![ - // MenuItem::Simple(SimpleMenuItem::new("open", "Open")), - // MenuItem::Separator, - // MenuItem::Sub(SubMenuItem::new( - // "Sub", - // vec![ - // MenuItem::Simple(SimpleMenuItem::new("sub1", "Sub 1")), - // MenuItem::Simple(SimpleMenuItem::new("sub2", "Sub 2")), - // ], - // )), - // ]) - // .unwrap(); - // remote.show_context_menu(&menu); - // })) - eventloop.run(); + eventloop.initialize(); + eventloop.run(Box::new(move |event| { + println!("ui {:?}", event); + let menu = Menu::from(vec![ + MenuItem::Simple(SimpleMenuItem::new("open", "Open")), + MenuItem::Separator, + MenuItem::Sub(SubMenuItem::new( + "Sub", + vec![ + MenuItem::Simple(SimpleMenuItem::new("sub1", "Sub 1")), + MenuItem::Simple(SimpleMenuItem::new("sub2", "Sub 2")), + ], + )), + ]) + .unwrap(); + match event { + TrayIconClick => { + remote.show_context_menu(&menu); + } + ContextMenuClick(raw_id) => { + //remote.update_tray_icon(TrayIcon::Disabled); + remote.show_notification("Hello there!"); + println!("item {:?}", menu.get_item_id(raw_id)); + } + } + + })); } diff --git a/espanso/src/main_old.rs b/espanso/src/main_old2.rs similarity index 97% rename from espanso/src/main_old.rs rename to espanso/src/main_old2.rs index 8b2025f..e51724e 100644 --- a/espanso/src/main_old.rs +++ b/espanso/src/main_old2.rs @@ -38,7 +38,7 @@ fn main() { let handle = std::thread::spawn(move || { //let mut source = espanso_detect::win32::Win32Source::new(); - let mut source = espanso_detect::x11::X11Source::new(); + let mut source = espanso_detect::evdev::EVDEVSource::new(); source.initialize(); source.eventloop(Box::new(move |event: InputEvent| { println!("ev {:?}", event);