From 714dffe6c142c3f5f8f719e118f7affaed350a5f Mon Sep 17 00:00:00 2001 From: Federico Terzi Date: Thu, 12 Sep 2019 22:14:41 +0200 Subject: [PATCH] First draft of new event architecture --- build.rs | 2 +- native/libwinbridge/bridge.cpp | 425 +++++++++++++++++---------------- native/libwinbridge/bridge.h | 30 ++- src/bridge/windows.rs | 11 +- src/config/mod.rs | 2 +- src/context/mod.rs | 45 ++++ src/context/windows.rs | 143 +++++++++++ src/engine.rs | 9 + src/event/manager.rs | 43 ++++ src/event/mod.rs | 40 ++++ src/keyboard/mod.rs | 52 +--- src/keyboard/windows.rs | 56 ----- src/main.rs | 34 ++- src/matcher/mod.rs | 31 ++- src/matcher/scrolling.rs | 8 +- src/ui/mod.rs | 9 + src/ui/windows.rs | 47 +--- 17 files changed, 568 insertions(+), 419 deletions(-) create mode 100644 src/context/mod.rs create mode 100644 src/context/windows.rs create mode 100644 src/event/manager.rs create mode 100644 src/event/mod.rs diff --git a/build.rs b/build.rs index d49eab8..3d6ae2c 100644 --- a/build.rs +++ b/build.rs @@ -27,7 +27,7 @@ fn get_config() -> PathBuf { #[cfg(target_os = "windows")] fn print_config() { println!("cargo:rustc-link-lib=static=winbridge"); - println!("cargo:rustc-link-lib=static=user32"); + println!("cargo:rustc-link-lib=static=user32"); // TODO: maybe dylib is better } #[cfg(target_os = "linux")] diff --git a/native/libwinbridge/bridge.cpp b/native/libwinbridge/bridge.cpp index 154e927..c58d346 100644 --- a/native/libwinbridge/bridge.cpp +++ b/native/libwinbridge/bridge.cpp @@ -1,7 +1,9 @@ #include "bridge.h" +#include #include #include #include +#include #include #define UNICODE @@ -13,31 +15,141 @@ // How many milliseconds must pass between keystrokes to refresh the keyboard layout const long refreshKeyboardLayoutInterval = 2000; +void * manager_instance; + +// Keyboard listening + DWORD lastKeyboardPressTick = 0; HKL currentKeyboardLayout; HWND window; - const wchar_t* const winclass = L"Espanso"; KeypressCallback keypress_callback; -void * interceptor_instance; -void register_keypress_callback(void * self, KeypressCallback callback) { +// UI + +#define APPWM_ICON_CLICK (WM_APP + 1) +#define APPWM_NOTIFICATION_POPUP (WM_APP + 2) +#define APPWM_NOTIFICATION_CLOSE (WM_APP + 3) + +const wchar_t* const notification_winclass = L"EspansoNotification"; +HWND nw = NULL; +HWND hwnd_st_u = NULL; +HBITMAP g_espanso_bmp = NULL; +HICON g_espanso_ico = NULL; + +MenuItemCallback menu_item_callback = NULL; + +// Callback registration + +void register_menu_item_callback(MenuItemCallback callback) { + menu_item_callback = callback; +} + +void register_keypress_callback(KeypressCallback callback) { keypress_callback = callback; - interceptor_instance = self; } /* - * Message handler procedure for the Worker window + * Message handler procedure for the windows */ -LRESULT CALLBACK window_worker_procedure(HWND window, unsigned int msg, WPARAM wp, LPARAM lp) +LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPARAM lp) { + HDC hdcStatic = NULL; + switch (msg) { case WM_DESTROY: std::cout << "\ndestroying window\n"; PostQuitMessage(0); + DeleteObject(g_espanso_bmp); + DeleteObject(g_espanso_ico); return 0L; + case WM_MENUSELECT: // Click on the tray icon context menu + { + HMENU hmenu = (HMENU)lp; + UINT idItem = (UINT)LOWORD(wp); + UINT flags = (UINT)HIWORD(wp); + + if (flags & MF_CHECKED) { + // TODO: callback + } + + break; + } + case APPWM_NOTIFICATION_POPUP: // Request to show a notification + { + std::unique_ptr ptr(reinterpret_cast(wp)); + + SetWindowText(hwnd_st_u, L" "); // Clear the previous text + SetWindowText(hwnd_st_u, ptr.get()); + + // Show the window + ShowWindow(nw, SW_SHOW); + break; + } + case APPWM_NOTIFICATION_CLOSE: // Request to close a notification + { + // Hide the window + ShowWindow(nw, SW_HIDE); + break; + } + case APPWM_ICON_CLICK: // Click on the tray icon + { + switch (lp) + { + case WM_LBUTTONUP: + case WM_RBUTTONUP: + HMENU hPopupMenu = CreatePopupMenu(); + + // Create the menu + + int32_t count; + MenuItem items[100]; + + // Load the items + menu_item_callback(manager_instance, items, &count); + + for (int i = 0; i 0 is the BACKSPACE, so we have to consider it. if (result >= 1 && raw->data.keyboard.VKey != VK_BACK) { - keypress_callback(interceptor_instance, reinterpret_cast(buffer.data()), buffer.size(), 0, raw->data.keyboard.VKey); + keypress_callback(manager_instance, reinterpret_cast(buffer.data()), buffer.size(), 0, raw->data.keyboard.VKey); }else{ - keypress_callback(interceptor_instance, nullptr, 0, 1, raw->data.keyboard.VKey); + keypress_callback(manager_instance, nullptr, 0, 1, raw->data.keyboard.VKey); } } } @@ -113,7 +225,16 @@ LRESULT CALLBACK window_worker_procedure(HWND window, unsigned int msg, WPARAM w } } -int32_t initialize_window() { +int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path) { + manager_instance = self; + + // Load the images + g_espanso_bmp = (HBITMAP)LoadImage(NULL, bmp_path, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); + g_espanso_ico = (HICON)LoadImage(NULL, ico_path, IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR | LR_SHARED | LR_DEFAULTSIZE | LR_LOADFROMFILE); + + // Make the notification capable of handling different screen definitions + SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE); + // Initialize the default keyboard layout currentKeyboardLayout = GetKeyboardLayout(0); @@ -123,7 +244,7 @@ int32_t initialize_window() { WNDCLASSEX wndclass = { sizeof(WNDCLASSEX), // cbSize: Size of this structure 0, // style: Class styles - window_worker_procedure, // lpfnWndProc: Pointer to the window procedure + window_procedure, // lpfnWndProc: Pointer to the window procedure 0, // cbClsExtra: Number of extra bytes to allocate following the window-class structure 0, // cbWndExtra: The number of extra bytes to allocate following the window instance. GetModuleHandle(0), // hInstance: A handle to the instance that contains the window procedure for the class. @@ -135,7 +256,25 @@ int32_t initialize_window() { NULL // hIconSm: A handle to a small icon that is associated with the window class. }; - if (RegisterClassEx(&wndclass)) + // Notification Window + + // Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa + WNDCLASSEX notificationwndclass = { + sizeof(WNDCLASSEX), // cbSize: Size of this structure + 0, // style: Class styles + window_procedure, // lpfnWndProc: Pointer to the window procedure + 0, // cbClsExtra: Number of extra bytes to allocate following the window-class structure + 0, // cbWndExtra: The number of extra bytes to allocate following the window instance. + GetModuleHandle(0), // hInstance: A handle to the instance that contains the window procedure for the class. + NULL, // hIcon: A handle to the class icon. + LoadCursor(0,IDC_ARROW), // hCursor: A handle to the class cursor. + NULL, // hbrBackground: A handle to the class background brush. + NULL, // lpszMenuName: Pointer to a null-terminated character string that specifies the resource name of the class menu + notification_winclass, // lpszClassName: A pointer to a null-terminated string or is an atom. + NULL // hIconSm: A handle to a small icon that is associated with the window class. + }; + + if (RegisterClassEx(&wndclass) && RegisterClassEx(¬ificationwndclass)) { // Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw window = CreateWindowEx( @@ -164,6 +303,62 @@ int32_t initialize_window() { if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) { // Something went wrong, error. return -1; } + + // Initialize the notification window + nw = CreateWindowEx( + WS_EX_TOOLWINDOW | WS_EX_TOPMOST, // dwExStyle: The extended window style of the window being created. + notification_winclass, // lpClassName: A null-terminated string or a class atom created by a previous call to the RegisterClass + L"Espanso Notification", // lpWindowName: The window name. + WS_POPUPWINDOW, // dwStyle: The style of the window being created. + CW_USEDEFAULT, // X: The initial horizontal position of the window. + CW_USEDEFAULT, // Y: The initial vertical position of the window. + 300, // nWidth: The width, in device units, of the window. + 100, // nHeight: The height, in device units, of the window. + NULL, // hWndParent: handle to the parent or owner window of the window being created. + NULL, // hMenu: A handle to a menu, or specifies a child-window identifier, depending on the window style. + GetModuleHandle(0), // hInstance: A handle to the instance of the module to be associated with the window. + NULL // lpParam: Pointer to a value to be passed to the window + ); + + if (nw) + { + int x, w, y, h; + y = 40; h = 30; + x = 100; w = 180; + hwnd_st_u = CreateWindowEx(0, L"static", L"ST_U", + WS_CHILD | WS_VISIBLE | WS_TABSTOP | SS_CENTER, + x, y, w, h, + nw, (HMENU)(501), + (HINSTANCE)GetWindowLong(nw, GWLP_HINSTANCE), NULL); + + SetWindowText(hwnd_st_u, L"Loading..."); + + int posX = GetSystemMetrics(SM_CXSCREEN) - 350; + int posY = GetSystemMetrics(SM_CYSCREEN) - 200; + + SetWindowPos(nw, HWND_TOP, posX, posY, 0, 0, SWP_NOSIZE); + + // Hide the window + ShowWindow(nw, SW_HIDE); + + // Setup the icon in the notification space + + SendMessage(nw, WM_SETICON, ICON_BIG, (LPARAM)g_espanso_ico); + SendMessage(nw, WM_SETICON, ICON_SMALL, (LPARAM)g_espanso_ico); + + //Notification + NOTIFYICONDATA nid = {}; + nid.cbSize = sizeof(nid); + nid.hWnd = nw; + nid.uID = 1; + nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE; + nid.uCallbackMessage = APPWM_ICON_CLICK; + nid.hIcon = g_espanso_ico; + StringCchCopy(nid.szTip, ARRAYSIZE(nid.szTip), L"espanso"); + + // Show the notification. + Shell_NotifyIcon(NIM_ADD, &nid); + } }else{ // Something went wrong, error. return -1; @@ -276,209 +471,14 @@ int32_t get_active_window_executable(wchar_t * buffer, int32_t size) { return res; } -// UI -#define APPWM_ICONNOTIFY (WM_APP + 1) - -const wchar_t* const notification_winclass = L"EspansoNotification"; -HWND nw = NULL; -HWND hwnd_st_u = NULL; -HBITMAP g_espanso_bmp = NULL; -HICON g_espanso_ico = NULL; - -MenuItemCallback menu_item_callback = NULL; -void * ui_manager_instance; - -void register_menu_item_callback(void *self, MenuItemCallback callback) { - menu_item_callback = callback; - ui_manager_instance = self; -} - -/* - * Message handler procedure for the notification window - */ -LRESULT CALLBACK notification_worker_procedure(HWND window, unsigned int msg, WPARAM wp, LPARAM lp) -{ - HDC hdcStatic = NULL; - - switch (msg) - { - case WM_DESTROY: - std::cout << "\ndestroying window\n"; - PostQuitMessage(0); - DeleteObject(g_espanso_bmp); - return 0L; - case WM_MENUSELECT: - { - HMENU hmenu = (HMENU)lp; - UINT idItem = (UINT)LOWORD(wp); - UINT flags = (UINT)HIWORD(wp); - - if (flags & MF_CHECKED) { - // TODO: callback - } - - break; - } - case APPWM_ICONNOTIFY: - { - switch (lp) - { - case WM_LBUTTONUP: - case WM_RBUTTONUP: - HMENU hPopupMenu = CreatePopupMenu(); - - // Create the menu - - int32_t count; - MenuItem items[100]; - - // Load the items - menu_item_callback(ui_manager_instance, items, &count); - - for (int i = 0; i(buffer), 0); return 1; } @@ -486,6 +486,7 @@ int32_t show_notification(wchar_t * message) { } void close_notification() { - // Hide the window - ShowWindow(nw, SW_HIDE); + if (nw != NULL) { + PostMessage(nw, APPWM_NOTIFICATION_CLOSE, 0, 0); + } } \ No newline at end of file diff --git a/native/libwinbridge/bridge.h b/native/libwinbridge/bridge.h index a971e4b..871eba3 100644 --- a/native/libwinbridge/bridge.h +++ b/native/libwinbridge/bridge.h @@ -4,31 +4,33 @@ #include #include +extern void * manager_instance; + +/* + * Initialize the Windows parameters + * return: 1 if OK, -1 otherwise. + */ +extern "C" int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path); + /* * Called when a new keypress is made, the first argument is an int array, * while the second is the size of the array. */ typedef void (*KeypressCallback)(void * self, int32_t *buffer, int32_t len, int32_t is_modifier, int32_t key_code); - extern KeypressCallback keypress_callback; -extern void * interceptor_instance; /* * Register the callback that will be called when a keypress was made */ -extern "C" void register_keypress_callback(void *self, KeypressCallback callback); - -/* - * Initialize the Windows worker's parameters - * return: 1 if OK, -1 otherwise. - */ -extern "C" int32_t initialize_window(); +extern "C" void register_keypress_callback(KeypressCallback callback); /* * Start the event loop indefinitely. Blocking call. */ extern "C" void eventloop(); +// Keyboard Manager + /* * Type the given string by simulating Key Presses */ @@ -44,6 +46,8 @@ extern "C" void send_vkey(int32_t vk); */ extern "C" void delete_string(int32_t count); +// Detect current application commands + /* * Return the active windows's title */ @@ -56,11 +60,6 @@ extern "C" int32_t get_active_window_executable(wchar_t * buffer, int32_t size); // UI -/* - * Initialize the notification window. - */ -extern "C" int32_t initialize_ui(wchar_t * ico_path, wchar_t * bmp_path); - // CONTEXT MENU typedef struct { @@ -75,8 +74,7 @@ typedef struct { typedef void (*MenuItemCallback)(void * self, MenuItem * items, int32_t * item_count); extern MenuItemCallback menu_item_callback; -extern void * ui_manager_instance; -extern "C" void register_menu_item_callback(void *self, MenuItemCallback callback); +extern "C" void register_menu_item_callback(MenuItemCallback callback); // NOTIFICATION diff --git a/src/bridge/windows.rs b/src/bridge/windows.rs index 4a0e4c7..2d86124 100644 --- a/src/bridge/windows.rs +++ b/src/bridge/windows.rs @@ -10,23 +10,22 @@ pub struct WindowsMenuItem { #[allow(improper_ctypes)] #[link(name="winbridge", kind="static")] extern { + pub fn initialize(s: *const c_void, ico_path: *const u16, bmp_path: *const u16) -> i32; + // SYSTEM pub fn get_active_window_name(buffer: *mut u16, size: i32) -> i32; pub fn get_active_window_executable(buffer: *mut u16, size: i32) -> i32; // UI - pub fn initialize_ui(ico_path: *const u16, bmp_path: *const u16) -> i32; pub fn show_notification(message: *const u16) -> i32; pub fn close_notification(); - pub fn register_menu_item_callback(s: *const c_void, - cb: extern fn(_self: *mut c_void, *mut WindowsMenuItem, + pub fn register_menu_item_callback(cb: extern fn(_self: *mut c_void, *mut WindowsMenuItem, *mut i32)); // KEYBOARD - pub fn register_keypress_callback(s: *const c_void, - cb: extern fn(_self: *mut c_void, *const i32, + pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const i32, i32, i32, i32)); - pub fn initialize_window(); + pub fn eventloop(); pub fn send_string(string: *const u16); pub fn send_vkey(vk: i32); diff --git a/src/config/mod.rs b/src/config/mod.rs index ab79c75..492d843 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -6,7 +6,7 @@ use crate::matcher::Match; use std::fs::{File, create_dir_all}; use std::io::Read; use serde::{Serialize, Deserialize}; -use crate::keyboard::KeyModifier; +use crate::event::KeyModifier; use crate::system::SystemManager; use std::collections::HashSet; use std::process::exit; diff --git a/src/context/mod.rs b/src/context/mod.rs new file mode 100644 index 0000000..b8e6b02 --- /dev/null +++ b/src/context/mod.rs @@ -0,0 +1,45 @@ +#[cfg(target_os = "windows")] +mod windows; + +#[cfg(target_os = "linux")] +mod linux; + +#[cfg(target_os = "macos")] +mod macos; + +use std::sync::mpsc::Sender; +use crate::event::Event; +use std::sync::Arc; + +pub trait Context { + fn eventloop(&self); +} + +pub enum MenuItemType { + Button, + Separator +} + +pub struct MenuItem { + +} + +// MAC IMPLEMENTATION +#[cfg(target_os = "macos")] +pub fn new(send_channel: Sender) -> Box { // TODO + macos::MacUIManager::new() +} + +// LINUX IMPLEMENTATION +#[cfg(target_os = "linux")] +pub fn new(send_channel: Sender) -> Box { // TODO + let manager = linux::LinuxUIManager{}; + manager.initialize(); + manager +} + +// WINDOWS IMPLEMENTATION +#[cfg(target_os = "windows")] +pub fn new(send_channel: Sender) -> Box { + windows::WindowsContext::new(send_channel) +} \ No newline at end of file diff --git a/src/context/windows.rs b/src/context/windows.rs new file mode 100644 index 0000000..2936ba1 --- /dev/null +++ b/src/context/windows.rs @@ -0,0 +1,143 @@ +use std::sync::mpsc::Sender; +use crate::bridge::windows::*; +use crate::event::{Event, KeyEvent, KeyModifier}; +use crate::event::KeyModifier::*; +use std::ffi::c_void; +use std::fs::create_dir_all; +use std::{fs, thread, time}; +use std::sync::{Arc, Mutex}; +use widestring::U16CString; +use log::{info, debug, error}; + +const BMP_BINARY : &'static [u8] = include_bytes!("../res/win/espanso.bmp"); +const ICO_BINARY : &'static [u8] = include_bytes!("../res/win/espanso.ico"); + +pub struct WindowsContext { + send_channel: Sender, + id: Arc> +} + +impl WindowsContext { + pub fn new(send_channel: Sender) -> Box { + // Initialize image resources + + let data_dir = dirs::data_dir().expect("Can't obtain data_dir(), terminating."); + let espanso_dir = data_dir.join("espanso"); + let res = create_dir_all(&espanso_dir); + + info!("Initializing Espanso resources in {}", espanso_dir.as_path().display()); + + let espanso_bmp_image = espanso_dir.join("espansoicon.bmp"); + if espanso_bmp_image.exists() { + info!("BMP already initialized, skipping."); + }else { + fs::write(&espanso_bmp_image, BMP_BINARY) + .expect("Unable to write windows bmp file"); + + info!("Extracted bmp icon to: {}", espanso_bmp_image.to_str().unwrap_or("error")); + } + + let espanso_ico_image = espanso_dir.join("espanso.ico"); + if espanso_ico_image.exists() { + info!("ICO already initialized, skipping."); + }else { + fs::write(&espanso_ico_image, ICO_BINARY) + .expect("Unable to write windows ico file"); + + info!("Extracted 'ico' icon to: {}", espanso_ico_image.to_str().unwrap_or("error")); + } + + let bmp_icon = espanso_bmp_image.to_str().unwrap_or_default(); + let ico_icon = espanso_ico_image.to_str().unwrap_or_default(); + + let id = Arc::new(Mutex::new(0)); + let send_channel = send_channel; + + let manager = Box::new(WindowsContext{ + send_channel, + id + }); + + unsafe { + let manager_ptr = &*manager as *const WindowsContext as *const c_void; + + // Register callbacks + register_keypress_callback(keypress_callback); + register_menu_item_callback(menu_item_callback); + + let ico_file_c = U16CString::from_str(ico_icon).unwrap(); + let bmp_file_c = U16CString::from_str(bmp_icon).unwrap(); + + // Initialize the windows + let res = initialize(manager_ptr, ico_file_c.as_ptr(), bmp_file_c.as_ptr()); + if res != 1 { + panic!("Can't initialize Windows context") + } + } + + manager + } +} + +impl super::Context for WindowsContext { + fn eventloop(&self) { + unsafe { + eventloop(); + } + } +} + +// Native bridge code + +extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const i32, len: i32, + is_modifier: i32, key_code: i32) { + unsafe { + let _self = _self as *mut WindowsContext; + + if is_modifier == 0 { // Char event + // Convert the received buffer to a character + let buffer = std::slice::from_raw_parts(raw_buffer, len as usize); + let r = std::char::from_u32(buffer[0] as u32); + + // Send the char through the channel + if let Some(c) = r { + let event = Event::Key(KeyEvent::Char(c)); + (*_self).send_channel.send(event).unwrap(); + } + }else{ // Modifier event + let modifier: Option = match key_code { + 0x5B | 0x5C => Some(META), + 0x10 => Some(SHIFT), + 0x12 => Some(ALT), + 0x11 => Some(CTRL), + 0x08 => Some(BACKSPACE), + _ => None, + }; + + if let Some(modifier) = modifier { + let event = Event::Key(KeyEvent::Modifier(modifier)); + (*_self).send_channel.send(event).unwrap(); + } + } + } +} + +extern fn menu_item_callback(_self: *mut c_void, items: *mut WindowsMenuItem, count: *mut i32) { + unsafe { + let _self = _self as *mut WindowsContext; + + let str = U16CString::from_str("Test").unwrap_or_default(); + let mut str_buff : [u16; 100] = [0; 100]; + std::ptr::copy(str.as_ptr(), str_buff.as_mut_ptr(), str.len()); + let item = WindowsMenuItem { + item_id: 1, + item_type: 1, + item_name: str_buff, + }; + + let items = unsafe { std::slice::from_raw_parts_mut(items, 100) }; + + items[0] = item; + *count = 1; + } +} \ No newline at end of file diff --git a/src/engine.rs b/src/engine.rs index 7c411cf..edf22b0 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -5,6 +5,7 @@ use crate::config::BackendType; use crate::clipboard::ClipboardManager; use log::{info}; use crate::ui::UIManager; +use crate::event::{ActionEventReceiver, Event}; pub struct Engine<'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>, U: UIManager> { @@ -71,4 +72,12 @@ impl <'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>, U: UIMan self.ui_manager.notify(message); } +} + +impl <'a, S: KeyboardSender, C: ClipboardManager, + M: ConfigManager<'a>, U: UIManager> ActionEventReceiver for Engine<'a, S, C, M, U>{ + + fn on_action_event(&self, e: Event) { + unimplemented!() + } } \ No newline at end of file diff --git a/src/event/manager.rs b/src/event/manager.rs new file mode 100644 index 0000000..f9f5e0a --- /dev/null +++ b/src/event/manager.rs @@ -0,0 +1,43 @@ +use crate::event::{KeyEventReceiver, ActionEventReceiver, Event}; +use std::sync::mpsc::Receiver; + +pub trait EventManager { + fn eventloop(&self); +} + +pub struct DefaultEventManager<'a, K: KeyEventReceiver, A: ActionEventReceiver> { + receive_channel: Receiver, + key_receiver: &'a K, + action_receiver: &'a A, +} + +impl<'a, K: KeyEventReceiver, A: ActionEventReceiver> DefaultEventManager<'a, K, A> { + pub fn new(receive_channel: Receiver, key_receiver: &'a K, + action_receiver: &'a A) -> DefaultEventManager<'a, K, A> { + DefaultEventManager { + receive_channel, + key_receiver, + action_receiver, + } + } +} + +impl <'a, K: KeyEventReceiver, A: ActionEventReceiver> EventManager for DefaultEventManager<'a, K, A> { + fn eventloop(&self) { + loop { + match self.receive_channel.recv() { + Ok(event) => { + match event { + Event::Key(key_event) => { + self.key_receiver.on_key_event(key_event); + }, + Event::Action => { + self.action_receiver.on_action_event(event); // TODO: action event + } + } + }, + Err(_) => panic!("Broken event channel"), + } + } + } +} \ No newline at end of file diff --git a/src/event/mod.rs b/src/event/mod.rs new file mode 100644 index 0000000..a2c7617 --- /dev/null +++ b/src/event/mod.rs @@ -0,0 +1,40 @@ +pub(crate) mod manager; + +use serde::{Serialize, Deserialize}; + +#[derive(Debug)] +pub enum Event { + Action, + Key(KeyEvent) +} + +#[derive(Debug)] +pub enum KeyEvent { + Char(char), + Modifier(KeyModifier) +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum KeyModifier { + CTRL, + SHIFT, + ALT, + META, + BACKSPACE, +} + +impl Default for KeyModifier { + fn default() -> Self { + KeyModifier::ALT + } +} + +// Receivers + +pub trait KeyEventReceiver { + fn on_key_event(&self, e: KeyEvent); +} + +pub trait ActionEventReceiver { + fn on_action_event(&self, e: Event); // TODO: Action event +} \ No newline at end of file diff --git a/src/keyboard/mod.rs b/src/keyboard/mod.rs index 8f3bd96..f3d477b 100644 --- a/src/keyboard/mod.rs +++ b/src/keyboard/mod.rs @@ -7,72 +7,26 @@ mod linux; #[cfg(target_os = "macos")] mod macos; -use std::sync::mpsc; -use serde::{Serialize, Deserialize}; - -pub trait KeyboardInterceptor { - fn initialize(&self); - fn start(&self); -} - -pub trait KeyboardSender { +pub trait KeyboardSender { // TODO: rename KeyboardManager fn send_string(&self, s: &str); fn send_enter(&self); fn trigger_paste(&self); fn delete_string(&self, count: i32); } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -pub enum KeyModifier { - CTRL, - SHIFT, - ALT, - META, - BACKSPACE, -} - -impl Default for KeyModifier { - fn default() -> Self { - KeyModifier::ALT - } -} - -#[derive(Debug)] -pub enum KeyEvent { - Char(char), - Modifier(KeyModifier) -} - -// WINDOWS IMPLEMENTATIONS - -#[cfg(target_os = "windows")] -pub fn get_interceptor(sender: mpsc::Sender) -> impl KeyboardInterceptor { - windows::WindowsKeyboardInterceptor {sender} -} - +// WINDOWS IMPLEMENTATION #[cfg(target_os = "windows")] pub fn get_sender() -> impl KeyboardSender { windows::WindowsKeyboardSender{} } -// LINUX IMPLEMENTATIONS - -#[cfg(target_os = "linux")] -pub fn get_interceptor(sender: mpsc::Sender) -> impl KeyboardInterceptor { - linux::LinuxKeyboardInterceptor {sender} -} - +// LINUX IMPLEMENTATION #[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{} diff --git a/src/keyboard/windows.rs b/src/keyboard/windows.rs index ec187d3..921ea58 100644 --- a/src/keyboard/windows.rs +++ b/src/keyboard/windows.rs @@ -1,31 +1,8 @@ use std::sync::mpsc; use std::os::raw::{c_void}; use widestring::{U16CString}; -use crate::keyboard::{KeyEvent, KeyModifier}; -use crate::keyboard::KeyModifier::*; use crate::bridge::windows::*; -#[repr(C)] -pub struct WindowsKeyboardInterceptor { - pub sender: mpsc::Sender -} - -impl super::KeyboardInterceptor for WindowsKeyboardInterceptor { - fn initialize(&self) { - unsafe { - let self_ptr = self as *const WindowsKeyboardInterceptor as *const c_void; - register_keypress_callback(self_ptr,keypress_callback); - initialize_window(); - } - } - - fn start(&self) { - unsafe { - eventloop(); - } - } -} - pub struct WindowsKeyboardSender { } @@ -59,37 +36,4 @@ impl super::KeyboardSender for WindowsKeyboardSender { delete_string(count) } } -} - -// Native bridge code - -extern fn keypress_callback(_self: *mut c_void, raw_buffer: *const i32, len: i32, - is_modifier: i32, key_code: i32) { - unsafe { - let _self = _self as *mut WindowsKeyboardInterceptor; - - if is_modifier == 0 { // Char event - // Convert the received buffer to a character - let buffer = std::slice::from_raw_parts(raw_buffer, len as usize); - let r = std::char::from_u32(buffer[0] as u32); - - // Send the char through the channel - if let Some(c) = r { - (*_self).sender.send(KeyEvent::Char(c)).unwrap(); - } - }else{ // Modifier event - let modifier: Option = match key_code { - 0x5B | 0x5C => Some(META), - 0x10 => Some(SHIFT), - 0x12 => Some(ALT), - 0x11 => Some(CTRL), - 0x08 => Some(BACKSPACE), - _ => None, - }; - - if let Some(modifier) = modifier { - (*_self).sender.send(KeyEvent::Modifier(modifier)).unwrap(); - } - } - } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index d1073d4..22654ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,4 @@ -use std::sync::{mpsc}; -use crate::keyboard::{KeyboardInterceptor, KeyEvent}; +use std::sync::{mpsc, Arc}; use crate::matcher::Matcher; use crate::matcher::scrolling::ScrollingMatcher; use crate::engine::Engine; @@ -7,6 +6,9 @@ use crate::clipboard::ClipboardManager; use crate::config::ConfigSet; use crate::config::runtime::RuntimeConfigManager; use crate::ui::UIManager; +use crate::context::Context; +use crate::event::*; +use crate::event::manager::{EventManager, DefaultEventManager}; use std::{thread, time}; use clap::{App, Arg}; use std::path::Path; @@ -16,10 +18,12 @@ use simplelog::{CombinedLogger, TermLogger, TerminalMode}; use std::process::exit; mod ui; +mod event; mod bridge; mod engine; mod config; mod system; +mod context; mod matcher; mod keyboard; mod clipboard; @@ -85,18 +89,18 @@ fn main() { } fn espanso_main(config_set: ConfigSet) { - let (txc, rxc) = mpsc::channel(); + let (send_channel, receive_channel) = mpsc::channel(); + + let context = context::new(send_channel); thread::spawn(move || { - espanso_background(rxc, config_set); + espanso_background(receive_channel, config_set); }); - let interceptor = keyboard::get_interceptor(txc); - interceptor.initialize(); - interceptor.start(); + context.eventloop(); } -fn espanso_background(rxc: Receiver, config_set: ConfigSet) { +fn espanso_background(receive_channel: Receiver, config_set: ConfigSet) { let system_manager = system::get_manager(); let config_manager = RuntimeConfigManager::new(config_set, system_manager); @@ -105,14 +109,22 @@ fn espanso_background(rxc: Receiver, config_set: ConfigSet) { let clipboard_manager = clipboard::get_manager(); - let sender = keyboard::get_sender(); + let sender = keyboard::get_sender(); // TODO: rename manager + // TODO: change sender to move to reference let engine = Engine::new(sender, &clipboard_manager, &config_manager, &ui_manager ); - let matcher = ScrollingMatcher::new(&config_manager, engine); - matcher.watch(rxc); + let matcher = ScrollingMatcher::new(&config_manager, &engine); + + let event_manager = DefaultEventManager::new( + receive_channel, + &matcher, + &engine, + ); + + event_manager.eventloop(); } \ No newline at end of file diff --git a/src/matcher/mod.rs b/src/matcher/mod.rs index 55a1c04..670fac0 100644 --- a/src/matcher/mod.rs +++ b/src/matcher/mod.rs @@ -1,6 +1,7 @@ use std::sync::mpsc::Receiver; use serde::{Serialize, Deserialize}; -use crate::keyboard::{KeyEvent, KeyModifier}; +use crate::event::{KeyEvent, KeyModifier}; +use crate::event::KeyEventReceiver; pub(crate) mod scrolling; @@ -15,24 +16,20 @@ pub trait MatchReceiver { fn on_toggle(&self, status: bool); } -pub trait Matcher { +pub trait Matcher : KeyEventReceiver { fn handle_char(&self, c: char); fn handle_modifier(&self, m: KeyModifier); - fn watch(&self, receiver: Receiver) { - loop { - match receiver.recv() { - Ok(event) => { - match event { - KeyEvent::Char(c) => { - self.handle_char(c); - }, - KeyEvent::Modifier(m) => { - self.handle_modifier(m); - }, - } - }, - Err(_) => panic!("Keyboard interceptor broke receiver stream."), - } +} + +impl KeyEventReceiver for M { + fn on_key_event(&self, e: KeyEvent) { + match e { + KeyEvent::Char(c) => { + self.handle_char(c); + }, + KeyEvent::Modifier(m) => { + self.handle_modifier(m); + }, } } } \ No newline at end of file diff --git a/src/matcher/scrolling.rs b/src/matcher/scrolling.rs index 94b68cb..f3e93de 100644 --- a/src/matcher/scrolling.rs +++ b/src/matcher/scrolling.rs @@ -1,14 +1,14 @@ use crate::matcher::{Match, MatchReceiver}; use std::cell::RefCell; -use crate::keyboard::KeyModifier; +use crate::event::KeyModifier; use crate::config::ConfigManager; -use crate::keyboard::KeyModifier::BACKSPACE; +use crate::event::KeyModifier::BACKSPACE; use std::time::SystemTime; use std::collections::VecDeque; pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> { config_manager: &'a M, - receiver: R, + receiver: &'a R, current_set_queue: RefCell>>>, toggle_press_time: RefCell, is_enabled: RefCell, @@ -101,7 +101,7 @@ impl <'a, R: MatchReceiver, M: ConfigManager<'a>> super::Matcher for ScrollingMa } } impl <'a, R: MatchReceiver, M: ConfigManager<'a>> ScrollingMatcher<'a, R, M> { - pub fn new(config_manager: &'a M, receiver: R) -> ScrollingMatcher<'a, R, M> { + pub fn new(config_manager: &'a M, receiver: &'a R) -> ScrollingMatcher<'a, R, M> { let current_set_queue = RefCell::new(VecDeque::new()); let toggle_press_time = RefCell::new(SystemTime::now()); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 07a0c71..f12e2b9 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -11,6 +11,15 @@ pub trait UIManager { fn notify(&self, message: &str); } +pub enum MenuItemType { + Button, + Separator +} + +pub struct MenuItem { + +} + // MAC IMPLEMENTATION #[cfg(target_os = "macos")] pub fn get_uimanager() -> impl UIManager { diff --git a/src/ui/windows.rs b/src/ui/windows.rs index 1f56edf..2943bc4 100644 --- a/src/ui/windows.rs +++ b/src/ui/windows.rs @@ -1,5 +1,5 @@ use std::process::Command; -use crate::bridge::windows::{show_notification, close_notification, initialize_ui, WindowsMenuItem, register_menu_item_callback}; +use crate::bridge::windows::{show_notification, close_notification, WindowsMenuItem}; use widestring::U16CString; use std::{fs, thread, time}; use log::{info, debug}; @@ -53,57 +53,12 @@ impl super::UIManager for WindowsUIManager { impl WindowsUIManager { pub fn new() -> WindowsUIManager { - let data_dir = dirs::data_dir().expect("Can't obtain data_dir(), terminating."); - - let espanso_dir = data_dir.join("espanso"); - - let res = create_dir_all(&espanso_dir); - - info!("Initializing Espanso resources in {}", espanso_dir.as_path().display()); - - let espanso_bmp_image = espanso_dir.join("espansoicon.bmp"); - if espanso_bmp_image.exists() { - info!("BMP already initialized, skipping."); - }else { - fs::write(&espanso_bmp_image, BMP_BINARY) - .expect("Unable to write windows bmp file"); - - info!("Extracted bmp icon to: {}", espanso_bmp_image.to_str().unwrap_or("error")); - } - - let espanso_ico_image = espanso_dir.join("espanso.ico"); - if espanso_ico_image.exists() { - info!("ICO already initialized, skipping."); - }else { - fs::write(&espanso_ico_image, ICO_BINARY) - .expect("Unable to write windows ico file"); - - info!("Extracted 'ico' icon to: {}", espanso_ico_image.to_str().unwrap_or("error")); - } - - let bmp_icon = espanso_bmp_image.to_str().unwrap_or_default(); - let ico_icon = espanso_ico_image.to_str().unwrap_or_default(); - let id = Arc::new(Mutex::new(0)); - let ico_file_c = U16CString::from_str(ico_icon).unwrap(); - let bmp_file_c = U16CString::from_str(bmp_icon).unwrap(); let manager = WindowsUIManager { id }; - // Setup the menu item callback - unsafe { - let self_ptr = &manager as *const WindowsUIManager as *const c_void; - register_menu_item_callback(self_ptr, menu_item_callback); - } - - thread::spawn(move || { - unsafe { - initialize_ui(ico_file_c.as_ptr(), bmp_file_c.as_ptr()); - } - }); - manager } }