First draft of macOS ui layer
This commit is contained in:
parent
bee2001e28
commit
7d357149ff
|
@ -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() {
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -7,3 +7,6 @@ pub mod win32;
|
|||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub mod linux;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub mod mac;
|
39
espanso-ui/src/mac/AppDelegate.h
Normal file
39
espanso-ui/src/mac/AppDelegate.h
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#import <AppKit/AppKit.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#include "native.h"
|
||||
|
||||
@interface AppDelegate : NSObject <NSApplicationDelegate, NSUserNotificationCenterDelegate> {
|
||||
@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
|
134
espanso-ui/src/mac/AppDelegate.mm
Normal file
134
espanso-ui/src/mac/AppDelegate.mm
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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];
|
||||
}
|
||||
}
|
||||
}
|
241
espanso-ui/src/mac/mod.rs
Normal file
241
espanso-ui/src/mac/mod.rs
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<TrayIcon, usize> = 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<dyn Fn(UIEvent)>;
|
||||
|
||||
pub struct MacEventLoop {
|
||||
show_icon: bool,
|
||||
icons: Vec<String>,
|
||||
|
||||
// Internal
|
||||
_event_callback: LazyCell<MacUIEventCallback>,
|
||||
_init_thread_id: LazyCell<ThreadId>,
|
||||
}
|
||||
|
||||
impl MacEventLoop {
|
||||
pub(crate) fn new(
|
||||
icons: Vec<String>,
|
||||
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<UIEvent> = 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<TrayIcon, usize>,
|
||||
}
|
||||
|
||||
impl MacRemote {
|
||||
pub(crate) fn new(
|
||||
icon_indexes: HashMap<TrayIcon, usize>,
|
||||
) -> 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<RawUIEvent> for Option<UIEvent> {
|
||||
fn from(raw: RawUIEvent) -> Option<UIEvent> {
|
||||
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
|
||||
}
|
||||
}
|
73
espanso-ui/src/mac/native.h
Normal file
73
espanso-ui/src/mac/native.h
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef ESPANSO_UI_H
|
||||
#define ESPANSO_UI_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// 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
|
72
espanso-ui/src/mac/native.mm
Normal file
72
espanso-ui/src/mac/native.mm
Normal file
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "native.h"
|
||||
#include "AppDelegate.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <libproc.h>
|
||||
|
||||
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];
|
||||
});
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -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);
|
Loading…
Reference in New Issue
Block a user