First draft of new event architecture
This commit is contained in:
parent
a8300832bc
commit
714dffe6c1
2
build.rs
2
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")]
|
||||
|
|
|
@ -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(¬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<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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
45
src/context/mod.rs
Normal 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
143
src/context/windows.rs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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> {
|
||||
|
@ -72,3 +73,11 @@ 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
43
src/event/manager.rs
Normal 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
40
src/event/mod.rs
Normal 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
|
||||
}
|
|
@ -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{}
|
||||
|
|
|
@ -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 {
|
||||
}
|
||||
|
||||
|
@ -60,36 +37,3 @@ impl super::KeyboardSender for WindowsKeyboardSender {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
src/main.rs
34
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<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();
|
||||
}
|
|
@ -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,14 +16,14 @@ 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 {
|
||||
}
|
||||
|
||||
impl <M: Matcher> KeyEventReceiver for M {
|
||||
fn on_key_event(&self, e: KeyEvent) {
|
||||
match e {
|
||||
KeyEvent::Char(c) => {
|
||||
self.handle_char(c);
|
||||
},
|
||||
|
@ -30,9 +31,5 @@ pub trait Matcher {
|
|||
self.handle_modifier(m);
|
||||
},
|
||||
}
|
||||
},
|
||||
Err(_) => panic!("Keyboard interceptor broke receiver stream."),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user