First draft of new event architecture

This commit is contained in:
Federico Terzi 2019-09-12 22:14:41 +02:00
parent a8300832bc
commit 714dffe6c1
17 changed files with 568 additions and 419 deletions

View File

@ -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")]

View File

@ -1,7 +1,9 @@
#include "bridge.h"
#include <stdio.h>
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <array>
#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<wchar_t> ptr(reinterpret_cast<wchar_t*>(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<count; i++) {
if (items[i].type == 1) {
InsertMenu(hPopupMenu, 0, MF_BYPOSITION | MF_STRING, items[i].id, items[i].name);
}
}
POINT pt;
GetCursorPos(&pt);
SetForegroundWindow(nw);
TrackPopupMenu(hPopupMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, nw, NULL);
break;
}
}
case WM_PAINT:
{
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(window, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, g_espanso_bmp);
GetObject(g_espanso_bmp, sizeof(bm), &bm);
BitBlt(hdc, 10, 10, 80, 80, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
EndPaint(window, &ps);
break;
}
case WM_CTLCOLORSTATIC:
hdcStatic = (HDC)wp;
SetTextColor(hdcStatic, RGB(0, 0, 0));
SetBkColor(hdcStatic, RGB(255, 255, 255));
//SetBkMode(hdcStatic, OPAQUE);
return (LRESULT)GetStockObject(NULL_BRUSH);
case WM_INPUT: // Message relative to the RAW INPUT events
{
// Get the input size
@ -99,9 +211,9 @@ LRESULT CALLBACK window_worker_procedure(HWND window, unsigned int msg, WPARAM w
// We need to call the callback in two different ways based on the type of key
// The only modifier we use that has a result > 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<int32_t*>(buffer.data()), buffer.size(), 0, raw->data.keyboard.VKey);
keypress_callback(manager_instance, reinterpret_cast<int32_t*>(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(&notificationwndclass))
{
// 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<count; i++) {
if (items[i].type == 1) {
InsertMenu(hPopupMenu, 0, MF_BYPOSITION | MF_STRING, items[i].id, items[i].name);
}
}
POINT pt;
GetCursorPos(&pt);
SetForegroundWindow(nw);
TrackPopupMenu(hPopupMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, nw, NULL);
break;
}
}
case WM_PAINT:
{
BITMAP bm;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(window, &ps);
HDC hdcMem = CreateCompatibleDC(hdc);
HBITMAP hbmOld = (HBITMAP) SelectObject(hdcMem, g_espanso_bmp);
GetObject(g_espanso_bmp, sizeof(bm), &bm);
BitBlt(hdc, 10, 10, 80, 80, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld);
DeleteDC(hdcMem);
EndPaint(window, &ps);
}
break;
case WM_CTLCOLORSTATIC:
hdcStatic = (HDC)wp;
SetTextColor(hdcStatic, RGB(0, 0, 0));
SetBkColor(hdcStatic, RGB(255, 255, 255));
//SetBkMode(hdcStatic, OPAQUE);
return (LRESULT)GetStockObject(NULL_BRUSH);
default:
return DefWindowProc(window, msg, wp, lp);
}
}
int32_t initialize_ui(wchar_t * ico_path, wchar_t * bmp_path) {
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);
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
// Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa
WNDCLASSEX wndclass = {
sizeof(WNDCLASSEX), // cbSize: Size of this structure
0, // style: Class styles
notification_worker_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))
{
// Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
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_ICONNOTIFY;
nid.hIcon = g_espanso_ico;
StringCchCopy(nid.szTip, ARRAYSIZE(nid.szTip), L"espanso");
// Show the notification.
Shell_NotifyIcon(NIM_ADD, &nid);
// Enter the Event loop
MSG msg;
while (GetMessage(&msg, 0, 0, 0)) DispatchMessage(&msg);
}
}
else {
// Something went wrong, error.
return GetLastError();
}
return 1;
}
// Notifications
int32_t show_notification(wchar_t * message) {
if (nw != NULL) {
SetWindowText(hwnd_st_u, L" "); // Clear the previous text
SetWindowText(hwnd_st_u, message);
wchar_t * buffer = new wchar_t[100];
swprintf(buffer, 100, L"%ls", message);
// Show the window
ShowWindow(nw, SW_SHOW);
PostMessage(nw, APPWM_NOTIFICATION_POPUP, reinterpret_cast<WPARAM>(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);
}
}

View File

@ -4,31 +4,33 @@
#include <stdio.h>
#include <stdint.h>
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

View File

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

View File

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

45
src/context/mod.rs Normal file
View File

@ -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<Event>) -> Box<dyn Context> { // TODO
macos::MacUIManager::new()
}
// LINUX IMPLEMENTATION
#[cfg(target_os = "linux")]
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> { // TODO
let manager = linux::LinuxUIManager{};
manager.initialize();
manager
}
// WINDOWS IMPLEMENTATION
#[cfg(target_os = "windows")]
pub fn new(send_channel: Sender<Event>) -> Box<dyn Context> {
windows::WindowsContext::new(send_channel)
}

143
src/context/windows.rs Normal file
View File

@ -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<Event>,
id: Arc<Mutex<i32>>
}
impl WindowsContext {
pub fn new(send_channel: Sender<Event>) -> Box<WindowsContext> {
// 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<KeyModifier> = 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;
}
}

View File

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

43
src/event/manager.rs Normal file
View File

@ -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<Event>,
key_receiver: &'a K,
action_receiver: &'a A,
}
impl<'a, K: KeyEventReceiver, A: ActionEventReceiver> DefaultEventManager<'a, K, A> {
pub fn new(receive_channel: Receiver<Event>, 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"),
}
}
}
}

40
src/event/mod.rs Normal file
View File

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

View File

@ -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<KeyEvent>) -> 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<KeyEvent>) -> 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<KeyEvent>) -> impl KeyboardInterceptor {
macos::MacKeyboardInterceptor {sender}
}
#[cfg(target_os = "macos")]
pub fn get_sender() -> impl KeyboardSender {
macos::MacKeyboardSender{}

View File

@ -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<KeyEvent>
}
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<KeyModifier> = 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();
}
}
}
}

View File

@ -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<KeyEvent>, config_set: ConfigSet) {
fn espanso_background(receive_channel: Receiver<Event>, 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<KeyEvent>, 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();
}

View File

@ -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<KeyEvent>) {
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 <M: Matcher> 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);
},
}
}
}

View File

@ -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<VecDeque<Vec<MatchEntry<'a>>>>,
toggle_press_time: RefCell<SystemTime>,
is_enabled: RefCell<bool>,
@ -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());

View File

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

View File

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