First steps in macos support

This commit is contained in:
Federico Terzi 2019-09-05 17:20:52 +02:00
parent cafc3c158f
commit ab93bb6879
13 changed files with 193 additions and 35 deletions

View File

@ -14,6 +14,11 @@ fn get_config() -> PathBuf {
Config::new("native/liblinuxbridge").build() Config::new("native/liblinuxbridge").build()
} }
#[cfg(target_os = "macos")]
fn get_config() -> PathBuf {
Config::new("native/libmacbridge").build()
}
/* /*
OS CUSTOM CARGO CONFIG LINES OS CUSTOM CARGO CONFIG LINES
Note: this is where linked libraries should be specified. Note: this is where linked libraries should be specified.
@ -34,6 +39,12 @@ fn print_config() {
println!("cargo:rustc-link-lib=dylib=xdo"); 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() fn main()
{ {
let dst = get_config(); let dst = get_config();

View File

@ -0,0 +1,8 @@
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end

View File

@ -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

View File

@ -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 .)

View File

@ -0,0 +1,16 @@
#ifndef ESPANSO_BRIDGE_H
#define ESPANSO_BRIDGE_H
#include <stdint.h>
/*
* 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

View File

@ -0,0 +1,17 @@
#include "bridge.h"
#import <Foundation/Foundation.h>
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];
}

View File

@ -1,17 +1,17 @@
use crate::matcher::{Match, MatchReceiver}; use crate::matcher::{Match, MatchReceiver};
use crate::keyboard::KeyboardSender; use crate::keyboard::KeyboardSender;
pub struct Engine<'a>{ pub struct Engine<S> where S: KeyboardSender {
sender: &'a dyn KeyboardSender sender: S
} }
impl <'a> Engine<'a> { impl <S> Engine<S> where S: KeyboardSender{
pub fn new(sender: &'a dyn KeyboardSender) -> Engine<'a> { pub fn new(sender: S) -> Engine<S> where S: KeyboardSender {
Engine{sender} Engine{sender}
} }
} }
impl <'a> MatchReceiver for Engine<'a>{ impl <S> MatchReceiver for Engine<S> where S: KeyboardSender{
fn on_match(&self, m: &Match) { fn on_match(&self, m: &Match) {
self.sender.delete_string(m.trigger.len() as i32); self.sender.delete_string(m.trigger.len() as i32);
self.sender.send_string(m.replace.as_str()); self.sender.send_string(m.replace.as_str());

View File

@ -18,7 +18,7 @@ impl super::KeyboardInterceptor for LinuxKeyboardInterceptor {
fn start(&self) { fn start(&self) {
thread::spawn(|| { thread::spawn(|| {
unsafe { unsafe {
initialize(); initialize(); // TODO: check initialization return codes
eventloop(); eventloop();
} }
}); });

46
src/keyboard/macos.rs Normal file
View File

@ -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<char>
}
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();
}

View File

@ -4,6 +4,9 @@ mod windows;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
mod linux; mod linux;
#[cfg(target_os = "macos")]
mod macos;
use std::sync::mpsc; use std::sync::mpsc;
pub trait KeyboardInterceptor { pub trait KeyboardInterceptor {
@ -39,3 +42,14 @@ pub fn get_interceptor(sender: mpsc::Sender<char>) -> impl KeyboardInterceptor {
pub fn get_sender() -> impl KeyboardSender { pub fn get_sender() -> impl KeyboardSender {
linux::LinuxKeyboardSender{} linux::LinuxKeyboardSender{}
} }
// MAC IMPLEMENTATION
#[cfg(target_os = "macos")]
pub fn get_interceptor(sender: mpsc::Sender<char>) -> impl KeyboardInterceptor {
macos::MacKeyboardInterceptor {sender}
}
#[cfg(target_os = "macos")]
pub fn get_sender() -> impl KeyboardSender {
macos::MacKeyboardSender{}
}

View File

@ -4,6 +4,7 @@ use crate::matcher::Matcher;
use crate::matcher::scrolling::ScrollingMatcher; use crate::matcher::scrolling::ScrollingMatcher;
use crate::engine::Engine; use crate::engine::Engine;
use crate::config::Configs; use crate::config::Configs;
use std::thread;
mod keyboard; mod keyboard;
mod matcher; mod matcher;
@ -15,16 +16,16 @@ fn main() {
let (txc, rxc) = mpsc::channel(); 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); let interceptor = keyboard::get_interceptor(txc);
interceptor.initialize(); interceptor.initialize();
interceptor.start(); 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);
} }

View File

@ -3,7 +3,7 @@ use serde::{Serialize, Deserialize};
pub(crate) mod scrolling; pub(crate) mod scrolling;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Match { pub struct Match {
pub trigger: String, pub trigger: String,
pub replace: String pub replace: String
@ -13,9 +13,9 @@ pub trait MatchReceiver {
fn on_match(&self, m: &Match); fn on_match(&self, m: &Match);
} }
pub trait Matcher { pub trait Matcher<'a>: Send {
fn handle_char(&mut self, c: char); fn handle_char(&'a self, c: char);
fn watch(&mut self, receiver: &Receiver<char>) { fn watch(&'a self, receiver: Receiver<char>) {
loop { loop {
match receiver.recv() { match receiver.recv() {
Ok(c) => { Ok(c) => {

View File

@ -1,9 +1,11 @@
use crate::matcher::{Match, MatchReceiver}; use crate::matcher::{Match, MatchReceiver};
use std::cell::RefCell;
use std::thread::current;
pub struct ScrollingMatcher<'a>{ pub struct ScrollingMatcher<'a, R> where R: MatchReceiver{
matches: &'a Vec<Match>, matches: Vec<Match>,
receiver: &'a dyn MatchReceiver, receiver: R,
current_set: Vec<MatchEntry<'a>> current_set: RefCell<Vec<MatchEntry<'a>>>
} }
struct MatchEntry<'a> { struct MatchEntry<'a> {
@ -11,25 +13,27 @@ struct MatchEntry<'a> {
_match: &'a Match _match: &'a Match
} }
impl <'a> super::Matcher for ScrollingMatcher<'a> { impl <'a, R> super::Matcher<'a> for ScrollingMatcher<'a, R> where R: MatchReceiver+Send{
fn handle_char(&mut self, c: char) { fn handle_char(&'a self, c: char) {
let mut new_matches: Vec<MatchEntry> = self.matches.iter() let mut current_set = self.current_set.borrow_mut();
let new_matches: Vec<MatchEntry> = self.matches.iter()
.filter(|&x| x.trigger.chars().nth(0).unwrap() == c) .filter(|&x| x.trigger.chars().nth(0).unwrap() == c)
.map(|x | MatchEntry{remaining: &x.trigger[1..], _match: &x}) .map(|x | MatchEntry{remaining: &x.trigger[1..], _match: &x})
.collect(); .collect();
// TODO: use an associative structure to improve the efficiency of this first "new_matches" lookup. // 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<MatchEntry> = (*current_set).iter()
.filter(|&x| x.remaining.chars().nth(0).unwrap() == c) .filter(|&x| x.remaining.chars().nth(0).unwrap() == c)
.map(|x | MatchEntry{remaining: &x.remaining[1..], _match: &x._match}) .map(|x | MatchEntry{remaining: &x.remaining[1..], _match: &x._match})
.collect(); .collect();
self.current_set = old_matches; (*current_set) = old_matches;
self.current_set.append(&mut new_matches); (*current_set).extend(new_matches);
let mut found_match = None; let mut found_match = None;
for entry in self.current_set.iter_mut() { for entry in (*current_set).iter() {
if entry.remaining.len() == 0 { if entry.remaining.len() == 0 {
found_match = Some(entry._match); found_match = Some(entry._match);
break; break;
@ -37,15 +41,15 @@ impl <'a> super::Matcher for ScrollingMatcher<'a> {
} }
if let Some(_match) = found_match { if let Some(_match) = found_match {
self.current_set.clear(); (*current_set).clear();
self.receiver.on_match(_match); self.receiver.on_match(_match);
} }
} }
} }
impl <'a> ScrollingMatcher<'a> { impl <'a, R> ScrollingMatcher<'a, R> where R: MatchReceiver {
pub fn new(matches:&'a Vec<Match>, receiver: &'a dyn MatchReceiver) -> ScrollingMatcher<'a> { pub fn new(matches:Vec<Match>, receiver: R) -> ScrollingMatcher<'a, R> {
let current_set = Vec::new(); let current_set = RefCell::new(Vec::new());
ScrollingMatcher{ matches, receiver, current_set } ScrollingMatcher{ matches, receiver, current_set }
} }
} }