From ab93bb6879fadfc6d5f5729d20d0e4625241fe8b Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Thu, 5 Sep 2019 17:20:52 +0200 Subject: [PATCH] First steps in macos support --- build.rs | 11 +++++++ native/libmacbridge/AppDelegate.h | 8 ++++++ native/libmacbridge/AppDelegate.m | 32 +++++++++++++++++++++ native/libmacbridge/CMakeLists.txt | 9 ++++++ native/libmacbridge/bridge.h | 16 +++++++++++ native/libmacbridge/bridge.mm | 17 +++++++++++ src/engine.rs | 10 +++---- src/keyboard/linux.rs | 2 +- src/keyboard/macos.rs | 46 ++++++++++++++++++++++++++++++ src/keyboard/mod.rs | 14 +++++++++ src/main.rs | 19 ++++++------ src/matcher/mod.rs | 8 +++--- src/matcher/scrolling.rs | 36 ++++++++++++----------- 13 files changed, 193 insertions(+), 35 deletions(-) create mode 100644 native/libmacbridge/AppDelegate.h create mode 100644 native/libmacbridge/AppDelegate.m create mode 100644 native/libmacbridge/CMakeLists.txt create mode 100644 native/libmacbridge/bridge.h create mode 100644 native/libmacbridge/bridge.mm create mode 100644 src/keyboard/macos.rs diff --git a/build.rs b/build.rs index c5f79f0..fe985d1 100644 --- a/build.rs +++ b/build.rs @@ -14,6 +14,11 @@ fn get_config() -> PathBuf { Config::new("native/liblinuxbridge").build() } +#[cfg(target_os = "macos")] +fn get_config() -> PathBuf { + Config::new("native/libmacbridge").build() +} + /* OS CUSTOM CARGO CONFIG LINES Note: this is where linked libraries should be specified. @@ -34,6 +39,12 @@ fn print_config() { println!("cargo:rustc-link-lib=dylib=xdo"); } +#[cfg(target_os = "macos")] +fn print_config() { + println!("cargo:rustc-link-lib=static=macbridge"); + println!("cargo:rustc-link-lib=framework=Cocoa"); +} + fn main() { let dst = get_config(); diff --git a/native/libmacbridge/AppDelegate.h b/native/libmacbridge/AppDelegate.h new file mode 100644 index 0000000..1a0714c --- /dev/null +++ b/native/libmacbridge/AppDelegate.h @@ -0,0 +1,8 @@ +#import +#import + +@interface AppDelegate : NSObject + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification; + +@end \ No newline at end of file diff --git a/native/libmacbridge/AppDelegate.m b/native/libmacbridge/AppDelegate.m new file mode 100644 index 0000000..9b6e3fd --- /dev/null +++ b/native/libmacbridge/AppDelegate.m @@ -0,0 +1,32 @@ +#import "AppDelegate.h" + +@implementation AppDelegate + +// 10.9+ only, see this url for compatibility: +// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9 +BOOL checkAccessibility() +{ + NSDictionary* opts = @{(__bridge id)kAXTrustedCheckOptionPrompt: @YES}; + return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts); +} + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification +{ + if (checkAccessibility()) { + NSLog(@"Accessibility Enabled"); + }else { + NSLog(@"Accessibility Disabled"); + } + + NSLog(@"registering keydown mask"); + [NSEvent addGlobalMonitorForEventsMatchingMask:(NSEventMaskKeyDown | NSEventMaskFlagsChanged) + handler:^(NSEvent *event){ + if (event.type == NSEventTypeKeyDown) { + NSLog(@"keydown: %@, %d", event.characters, event.keyCode); + }else{ + NSLog(@"keydown: %d", event.keyCode); + } + }]; +} + +@end \ No newline at end of file diff --git a/native/libmacbridge/CMakeLists.txt b/native/libmacbridge/CMakeLists.txt new file mode 100644 index 0000000..b944d7e --- /dev/null +++ b/native/libmacbridge/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.0) +project(libmacbridge) + +set (CMAKE_CXX_STANDARD 14) +set(CMAKE_C_FLAGS "-x objective-c") + +add_library(macbridge STATIC bridge.mm bridge.h AppDelegate.h AppDelegate.m) + +install(TARGETS macbridge DESTINATION .) \ No newline at end of file diff --git a/native/libmacbridge/bridge.h b/native/libmacbridge/bridge.h new file mode 100644 index 0000000..52150cb --- /dev/null +++ b/native/libmacbridge/bridge.h @@ -0,0 +1,16 @@ +#ifndef ESPANSO_BRIDGE_H +#define ESPANSO_BRIDGE_H + +#include + +/* + * Initialize the X11 context and parameters + */ +extern "C" int32_t initialize(); + +/* + * Start the event loop indefinitely. Blocking call. + */ +extern "C" int32_t eventloop(); + +#endif //ESPANSO_BRIDGE_H diff --git a/native/libmacbridge/bridge.mm b/native/libmacbridge/bridge.mm new file mode 100644 index 0000000..a302c68 --- /dev/null +++ b/native/libmacbridge/bridge.mm @@ -0,0 +1,17 @@ +#include "bridge.h" + +#import + +extern "C" { + #include "AppDelegate.h" +} + +int32_t initialize() { + AppDelegate *delegate = [[AppDelegate alloc] init]; + NSApplication * application = [NSApplication sharedApplication]; + [application setDelegate:delegate]; +} + +int32_t eventloop() { + [NSApp run]; +} \ No newline at end of file diff --git a/src/engine.rs b/src/engine.rs index 2d437da..824b34f 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,17 +1,17 @@ use crate::matcher::{Match, MatchReceiver}; use crate::keyboard::KeyboardSender; -pub struct Engine<'a>{ - sender: &'a dyn KeyboardSender +pub struct Engine where S: KeyboardSender { + sender: S } -impl <'a> Engine<'a> { - pub fn new(sender: &'a dyn KeyboardSender) -> Engine<'a> { +impl Engine where S: KeyboardSender{ + pub fn new(sender: S) -> Engine where S: KeyboardSender { Engine{sender} } } -impl <'a> MatchReceiver for Engine<'a>{ +impl MatchReceiver for Engine where S: KeyboardSender{ fn on_match(&self, m: &Match) { self.sender.delete_string(m.trigger.len() as i32); self.sender.send_string(m.replace.as_str()); diff --git a/src/keyboard/linux.rs b/src/keyboard/linux.rs index db14d3c..a8c6350 100644 --- a/src/keyboard/linux.rs +++ b/src/keyboard/linux.rs @@ -18,7 +18,7 @@ impl super::KeyboardInterceptor for LinuxKeyboardInterceptor { fn start(&self) { thread::spawn(|| { unsafe { - initialize(); + initialize(); // TODO: check initialization return codes eventloop(); } }); diff --git a/src/keyboard/macos.rs b/src/keyboard/macos.rs new file mode 100644 index 0000000..e185094 --- /dev/null +++ b/src/keyboard/macos.rs @@ -0,0 +1,46 @@ +use std::sync::mpsc; +use std::os::raw::c_char; +use std::ffi::CString; + +#[repr(C)] +pub struct MacKeyboardInterceptor { + pub sender: mpsc::Sender +} + +impl super::KeyboardInterceptor for MacKeyboardInterceptor { + fn initialize(&self) { + unsafe { initialize(); } // TODO: check initialization return codes + } + + fn start(&self) { + unsafe { eventloop(); } + } +} + +pub struct MacKeyboardSender { +} + +impl super::KeyboardSender for MacKeyboardSender { + fn send_string(&self, s: &str) { + + } + + fn delete_string(&self, count: i32) { + + } +} + +// Native bridge code + +extern fn keypress_callback(_self: *mut MacKeyboardInterceptor, raw_buffer: *const u8, len: i32) { + unsafe { + + } +} + +#[allow(improper_ctypes)] +#[link(name="macbridge", kind="static")] +extern { + fn initialize(); + fn eventloop(); +} \ No newline at end of file diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index 113e810..71a587c 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -4,6 +4,9 @@ mod windows; #[cfg(target_os = "linux")] mod linux; +#[cfg(target_os = "macos")] +mod macos; + use std::sync::mpsc; pub trait KeyboardInterceptor { @@ -38,4 +41,15 @@ pub fn get_interceptor(sender: mpsc::Sender) -> impl KeyboardInterceptor { #[cfg(target_os = "linux")] pub fn get_sender() -> impl KeyboardSender { linux::LinuxKeyboardSender{} +} + +// MAC IMPLEMENTATION +#[cfg(target_os = "macos")] +pub fn get_interceptor(sender: mpsc::Sender) -> impl KeyboardInterceptor { + macos::MacKeyboardInterceptor {sender} +} + +#[cfg(target_os = "macos")] +pub fn get_sender() -> impl KeyboardSender { + macos::MacKeyboardSender{} } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index a36bb34..0b876b2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ use crate::matcher::Matcher; use crate::matcher::scrolling::ScrollingMatcher; use crate::engine::Engine; use crate::config::Configs; +use std::thread; mod keyboard; mod matcher; @@ -15,16 +16,16 @@ fn main() { let (txc, rxc) = mpsc::channel(); + let sender = keyboard::get_sender(); + + let engine = Engine::new(sender); + + thread::spawn(move || { + let matcher = ScrollingMatcher::new(configs.matches.to_vec(), engine); + matcher.watch(rxc); + }); + let interceptor = keyboard::get_interceptor(txc); interceptor.initialize(); interceptor.start(); - - let sender = keyboard::get_sender(); - - let engine = Engine::new(&sender); - - println!("espanso is running!"); - - let mut matcher = ScrollingMatcher::new(&configs.matches, &engine); - matcher.watch(&rxc); } \ No newline at end of file diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index 9d673bc..f14c22a 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -3,7 +3,7 @@ use serde::{Serialize, Deserialize}; pub(crate) mod scrolling; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct Match { pub trigger: String, pub replace: String @@ -13,9 +13,9 @@ pub trait MatchReceiver { fn on_match(&self, m: &Match); } -pub trait Matcher { - fn handle_char(&mut self, c: char); - fn watch(&mut self, receiver: &Receiver) { +pub trait Matcher<'a>: Send { + fn handle_char(&'a self, c: char); + fn watch(&'a self, receiver: Receiver) { loop { match receiver.recv() { Ok(c) => { diff --git a/src/matcher/scrolling.rs b/src/matcher/scrolling.rs index 5e3d983..7ab9ad3 100644 --- a/src/matcher/scrolling.rs +++ b/src/matcher/scrolling.rs @@ -1,9 +1,11 @@ use crate::matcher::{Match, MatchReceiver}; +use std::cell::RefCell; +use std::thread::current; -pub struct ScrollingMatcher<'a>{ - matches: &'a Vec, - receiver: &'a dyn MatchReceiver, - current_set: Vec> +pub struct ScrollingMatcher<'a, R> where R: MatchReceiver{ + matches: Vec, + receiver: R, + current_set: RefCell>> } struct MatchEntry<'a> { @@ -11,25 +13,27 @@ struct MatchEntry<'a> { _match: &'a Match } -impl <'a> super::Matcher for ScrollingMatcher<'a> { - fn handle_char(&mut self, c: char) { - let mut new_matches: Vec = self.matches.iter() +impl <'a, R> super::Matcher<'a> for ScrollingMatcher<'a, R> where R: MatchReceiver+Send{ + fn handle_char(&'a self, c: char) { + let mut current_set = self.current_set.borrow_mut(); + + let new_matches: Vec = self.matches.iter() .filter(|&x| x.trigger.chars().nth(0).unwrap() == c) .map(|x | MatchEntry{remaining: &x.trigger[1..], _match: &x}) .collect(); // TODO: use an associative structure to improve the efficiency of this first "new_matches" lookup. - let old_matches = self.current_set.iter() + let old_matches: Vec = (*current_set).iter() .filter(|&x| x.remaining.chars().nth(0).unwrap() == c) .map(|x | MatchEntry{remaining: &x.remaining[1..], _match: &x._match}) .collect(); - - self.current_set = old_matches; - self.current_set.append(&mut new_matches); + + (*current_set) = old_matches; + (*current_set).extend(new_matches); let mut found_match = None; - for entry in self.current_set.iter_mut() { + for entry in (*current_set).iter() { if entry.remaining.len() == 0 { found_match = Some(entry._match); break; @@ -37,15 +41,15 @@ impl <'a> super::Matcher for ScrollingMatcher<'a> { } if let Some(_match) = found_match { - self.current_set.clear(); + (*current_set).clear(); self.receiver.on_match(_match); } } } -impl <'a> ScrollingMatcher<'a> { - pub fn new(matches:&'a Vec, receiver: &'a dyn MatchReceiver) -> ScrollingMatcher<'a> { - let current_set = Vec::new(); +impl <'a, R> ScrollingMatcher<'a, R> where R: MatchReceiver { + pub fn new(matches:Vec, receiver: R) -> ScrollingMatcher<'a, R> { + let current_set = RefCell::new(Vec::new()); ScrollingMatcher{ matches, receiver, current_set } } } \ No newline at end of file