First steps in macos support
This commit is contained in:
parent
cafc3c158f
commit
ab93bb6879
11
build.rs
11
build.rs
|
@ -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();
|
||||||
|
|
8
native/libmacbridge/AppDelegate.h
Normal file
8
native/libmacbridge/AppDelegate.h
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#import <AppKit/AppKit.h>
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
@interface AppDelegate : NSObject <NSApplicationDelegate>
|
||||||
|
|
||||||
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
|
||||||
|
|
||||||
|
@end
|
32
native/libmacbridge/AppDelegate.m
Normal file
32
native/libmacbridge/AppDelegate.m
Normal 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
|
9
native/libmacbridge/CMakeLists.txt
Normal file
9
native/libmacbridge/CMakeLists.txt
Normal 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 .)
|
16
native/libmacbridge/bridge.h
Normal file
16
native/libmacbridge/bridge.h
Normal 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
|
17
native/libmacbridge/bridge.mm
Normal file
17
native/libmacbridge/bridge.mm
Normal 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];
|
||||||
|
}
|
|
@ -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());
|
||||||
|
|
|
@ -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
46
src/keyboard/macos.rs
Normal 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();
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
@ -38,4 +41,15 @@ pub fn get_interceptor(sender: mpsc::Sender<char>) -> impl KeyboardInterceptor {
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
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{}
|
||||||
}
|
}
|
19
src/main.rs
19
src/main.rs
|
@ -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);
|
|
||||||
}
|
}
|
|
@ -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) => {
|
||||||
|
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user