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")]
|
#[cfg(target_os = "windows")]
|
||||||
fn print_config() {
|
fn print_config() {
|
||||||
println!("cargo:rustc-link-lib=static=winbridge");
|
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")]
|
#[cfg(target_os = "linux")]
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
#include "bridge.h"
|
#include "bridge.h"
|
||||||
|
#include <stdio.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
#define UNICODE
|
#define UNICODE
|
||||||
|
@ -13,31 +15,141 @@
|
||||||
// How many milliseconds must pass between keystrokes to refresh the keyboard layout
|
// How many milliseconds must pass between keystrokes to refresh the keyboard layout
|
||||||
const long refreshKeyboardLayoutInterval = 2000;
|
const long refreshKeyboardLayoutInterval = 2000;
|
||||||
|
|
||||||
|
void * manager_instance;
|
||||||
|
|
||||||
|
// Keyboard listening
|
||||||
|
|
||||||
DWORD lastKeyboardPressTick = 0;
|
DWORD lastKeyboardPressTick = 0;
|
||||||
HKL currentKeyboardLayout;
|
HKL currentKeyboardLayout;
|
||||||
HWND window;
|
HWND window;
|
||||||
|
|
||||||
const wchar_t* const winclass = L"Espanso";
|
const wchar_t* const winclass = L"Espanso";
|
||||||
|
|
||||||
KeypressCallback keypress_callback;
|
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;
|
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)
|
switch (msg)
|
||||||
{
|
{
|
||||||
case WM_DESTROY:
|
case WM_DESTROY:
|
||||||
std::cout << "\ndestroying window\n";
|
std::cout << "\ndestroying window\n";
|
||||||
PostQuitMessage(0);
|
PostQuitMessage(0);
|
||||||
|
DeleteObject(g_espanso_bmp);
|
||||||
|
DeleteObject(g_espanso_ico);
|
||||||
return 0L;
|
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
|
case WM_INPUT: // Message relative to the RAW INPUT events
|
||||||
{
|
{
|
||||||
// Get the input size
|
// 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
|
// 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.
|
// 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) {
|
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{
|
}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
|
// Initialize the default keyboard layout
|
||||||
currentKeyboardLayout = GetKeyboardLayout(0);
|
currentKeyboardLayout = GetKeyboardLayout(0);
|
||||||
|
|
||||||
|
@ -123,7 +244,7 @@ int32_t initialize_window() {
|
||||||
WNDCLASSEX wndclass = {
|
WNDCLASSEX wndclass = {
|
||||||
sizeof(WNDCLASSEX), // cbSize: Size of this structure
|
sizeof(WNDCLASSEX), // cbSize: Size of this structure
|
||||||
0, // style: Class styles
|
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, // 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.
|
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.
|
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.
|
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
|
// Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createwindowexw
|
||||||
window = CreateWindowEx(
|
window = CreateWindowEx(
|
||||||
|
@ -164,6 +303,62 @@ int32_t initialize_window() {
|
||||||
if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) { // Something went wrong, error.
|
if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) { // Something went wrong, error.
|
||||||
return -1;
|
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{
|
}else{
|
||||||
// Something went wrong, error.
|
// Something went wrong, error.
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -276,209 +471,14 @@ int32_t get_active_window_executable(wchar_t * buffer, int32_t size) {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI
|
// Notifications
|
||||||
#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;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t show_notification(wchar_t * message) {
|
int32_t show_notification(wchar_t * message) {
|
||||||
if (nw != NULL) {
|
if (nw != NULL) {
|
||||||
SetWindowText(hwnd_st_u, L" "); // Clear the previous text
|
wchar_t * buffer = new wchar_t[100];
|
||||||
SetWindowText(hwnd_st_u, message);
|
swprintf(buffer, 100, L"%ls", message);
|
||||||
|
|
||||||
// Show the window
|
PostMessage(nw, APPWM_NOTIFICATION_POPUP, reinterpret_cast<WPARAM>(buffer), 0);
|
||||||
ShowWindow(nw, SW_SHOW);
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,6 +486,7 @@ int32_t show_notification(wchar_t * message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void close_notification() {
|
void close_notification() {
|
||||||
// Hide the window
|
if (nw != NULL) {
|
||||||
ShowWindow(nw, SW_HIDE);
|
PostMessage(nw, APPWM_NOTIFICATION_CLOSE, 0, 0);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -4,31 +4,33 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdint.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,
|
* Called when a new keypress is made, the first argument is an int array,
|
||||||
* while the second is the size of the 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);
|
typedef void (*KeypressCallback)(void * self, int32_t *buffer, int32_t len, int32_t is_modifier, int32_t key_code);
|
||||||
|
|
||||||
extern KeypressCallback keypress_callback;
|
extern KeypressCallback keypress_callback;
|
||||||
extern void * interceptor_instance;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Register the callback that will be called when a keypress was made
|
* Register the callback that will be called when a keypress was made
|
||||||
*/
|
*/
|
||||||
extern "C" void register_keypress_callback(void *self, KeypressCallback callback);
|
extern "C" void register_keypress_callback(KeypressCallback callback);
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize the Windows worker's parameters
|
|
||||||
* return: 1 if OK, -1 otherwise.
|
|
||||||
*/
|
|
||||||
extern "C" int32_t initialize_window();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Start the event loop indefinitely. Blocking call.
|
* Start the event loop indefinitely. Blocking call.
|
||||||
*/
|
*/
|
||||||
extern "C" void eventloop();
|
extern "C" void eventloop();
|
||||||
|
|
||||||
|
// Keyboard Manager
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Type the given string by simulating Key Presses
|
* 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);
|
extern "C" void delete_string(int32_t count);
|
||||||
|
|
||||||
|
// Detect current application commands
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return the active windows's title
|
* 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
|
// UI
|
||||||
|
|
||||||
/*
|
|
||||||
* Initialize the notification window.
|
|
||||||
*/
|
|
||||||
extern "C" int32_t initialize_ui(wchar_t * ico_path, wchar_t * bmp_path);
|
|
||||||
|
|
||||||
// CONTEXT MENU
|
// CONTEXT MENU
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -75,8 +74,7 @@ typedef struct {
|
||||||
typedef void (*MenuItemCallback)(void * self, MenuItem * items, int32_t * item_count);
|
typedef void (*MenuItemCallback)(void * self, MenuItem * items, int32_t * item_count);
|
||||||
|
|
||||||
extern MenuItemCallback menu_item_callback;
|
extern MenuItemCallback menu_item_callback;
|
||||||
extern void * ui_manager_instance;
|
extern "C" void register_menu_item_callback(MenuItemCallback callback);
|
||||||
extern "C" void register_menu_item_callback(void *self, MenuItemCallback callback);
|
|
||||||
|
|
||||||
// NOTIFICATION
|
// NOTIFICATION
|
||||||
|
|
||||||
|
|
|
@ -10,23 +10,22 @@ pub struct WindowsMenuItem {
|
||||||
#[allow(improper_ctypes)]
|
#[allow(improper_ctypes)]
|
||||||
#[link(name="winbridge", kind="static")]
|
#[link(name="winbridge", kind="static")]
|
||||||
extern {
|
extern {
|
||||||
|
pub fn initialize(s: *const c_void, ico_path: *const u16, bmp_path: *const u16) -> i32;
|
||||||
|
|
||||||
// SYSTEM
|
// SYSTEM
|
||||||
pub fn get_active_window_name(buffer: *mut u16, size: i32) -> i32;
|
pub fn get_active_window_name(buffer: *mut u16, size: i32) -> i32;
|
||||||
pub fn get_active_window_executable(buffer: *mut u16, size: i32) -> i32;
|
pub fn get_active_window_executable(buffer: *mut u16, size: i32) -> i32;
|
||||||
|
|
||||||
// UI
|
// UI
|
||||||
pub fn initialize_ui(ico_path: *const u16, bmp_path: *const u16) -> i32;
|
|
||||||
pub fn show_notification(message: *const u16) -> i32;
|
pub fn show_notification(message: *const u16) -> i32;
|
||||||
pub fn close_notification();
|
pub fn close_notification();
|
||||||
pub fn register_menu_item_callback(s: *const c_void,
|
pub fn register_menu_item_callback(cb: extern fn(_self: *mut c_void, *mut WindowsMenuItem,
|
||||||
cb: extern fn(_self: *mut c_void, *mut WindowsMenuItem,
|
|
||||||
*mut i32));
|
*mut i32));
|
||||||
|
|
||||||
// KEYBOARD
|
// KEYBOARD
|
||||||
pub fn register_keypress_callback(s: *const c_void,
|
pub fn register_keypress_callback(cb: extern fn(_self: *mut c_void, *const i32,
|
||||||
cb: extern fn(_self: *mut c_void, *const i32,
|
|
||||||
i32, i32, i32));
|
i32, i32, i32));
|
||||||
pub fn initialize_window();
|
|
||||||
pub fn eventloop();
|
pub fn eventloop();
|
||||||
pub fn send_string(string: *const u16);
|
pub fn send_string(string: *const u16);
|
||||||
pub fn send_vkey(vk: i32);
|
pub fn send_vkey(vk: i32);
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::matcher::Match;
|
||||||
use std::fs::{File, create_dir_all};
|
use std::fs::{File, create_dir_all};
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use crate::keyboard::KeyModifier;
|
use crate::event::KeyModifier;
|
||||||
use crate::system::SystemManager;
|
use crate::system::SystemManager;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::process::exit;
|
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 crate::clipboard::ClipboardManager;
|
||||||
use log::{info};
|
use log::{info};
|
||||||
use crate::ui::UIManager;
|
use crate::ui::UIManager;
|
||||||
|
use crate::event::{ActionEventReceiver, Event};
|
||||||
|
|
||||||
pub struct Engine<'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>,
|
pub struct Engine<'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>,
|
||||||
U: UIManager> {
|
U: UIManager> {
|
||||||
|
@ -72,3 +73,11 @@ impl <'a, S: KeyboardSender, C: ClipboardManager, M: ConfigManager<'a>, U: UIMan
|
||||||
self.ui_manager.notify(message);
|
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")]
|
#[cfg(target_os = "macos")]
|
||||||
mod macos;
|
mod macos;
|
||||||
|
|
||||||
use std::sync::mpsc;
|
pub trait KeyboardSender { // TODO: rename KeyboardManager
|
||||||
use serde::{Serialize, Deserialize};
|
|
||||||
|
|
||||||
pub trait KeyboardInterceptor {
|
|
||||||
fn initialize(&self);
|
|
||||||
fn start(&self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait KeyboardSender {
|
|
||||||
fn send_string(&self, s: &str);
|
fn send_string(&self, s: &str);
|
||||||
fn send_enter(&self);
|
fn send_enter(&self);
|
||||||
fn trigger_paste(&self);
|
fn trigger_paste(&self);
|
||||||
fn delete_string(&self, count: i32);
|
fn delete_string(&self, count: i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
// WINDOWS IMPLEMENTATION
|
||||||
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}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub fn get_sender() -> impl KeyboardSender {
|
pub fn get_sender() -> impl KeyboardSender {
|
||||||
windows::WindowsKeyboardSender{}
|
windows::WindowsKeyboardSender{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// LINUX IMPLEMENTATIONS
|
// LINUX IMPLEMENTATION
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub fn get_interceptor(sender: mpsc::Sender<KeyEvent>) -> impl KeyboardInterceptor {
|
|
||||||
linux::LinuxKeyboardInterceptor {sender}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn get_sender() -> impl KeyboardSender {
|
pub fn get_sender() -> impl KeyboardSender {
|
||||||
linux::LinuxKeyboardSender{}
|
linux::LinuxKeyboardSender{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MAC IMPLEMENTATION
|
// MAC IMPLEMENTATION
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
pub fn get_interceptor(sender: mpsc::Sender<KeyEvent>) -> impl KeyboardInterceptor {
|
|
||||||
macos::MacKeyboardInterceptor {sender}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn get_sender() -> impl KeyboardSender {
|
pub fn get_sender() -> impl KeyboardSender {
|
||||||
macos::MacKeyboardSender{}
|
macos::MacKeyboardSender{}
|
||||||
|
|
|
@ -1,31 +1,8 @@
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::os::raw::{c_void};
|
use std::os::raw::{c_void};
|
||||||
use widestring::{U16CString};
|
use widestring::{U16CString};
|
||||||
use crate::keyboard::{KeyEvent, KeyModifier};
|
|
||||||
use crate::keyboard::KeyModifier::*;
|
|
||||||
use crate::bridge::windows::*;
|
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 {
|
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 std::sync::{mpsc, Arc};
|
||||||
use crate::keyboard::{KeyboardInterceptor, KeyEvent};
|
|
||||||
use crate::matcher::Matcher;
|
use crate::matcher::Matcher;
|
||||||
use crate::matcher::scrolling::ScrollingMatcher;
|
use crate::matcher::scrolling::ScrollingMatcher;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
|
@ -7,6 +6,9 @@ use crate::clipboard::ClipboardManager;
|
||||||
use crate::config::ConfigSet;
|
use crate::config::ConfigSet;
|
||||||
use crate::config::runtime::RuntimeConfigManager;
|
use crate::config::runtime::RuntimeConfigManager;
|
||||||
use crate::ui::UIManager;
|
use crate::ui::UIManager;
|
||||||
|
use crate::context::Context;
|
||||||
|
use crate::event::*;
|
||||||
|
use crate::event::manager::{EventManager, DefaultEventManager};
|
||||||
use std::{thread, time};
|
use std::{thread, time};
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -16,10 +18,12 @@ use simplelog::{CombinedLogger, TermLogger, TerminalMode};
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
|
||||||
mod ui;
|
mod ui;
|
||||||
|
mod event;
|
||||||
mod bridge;
|
mod bridge;
|
||||||
mod engine;
|
mod engine;
|
||||||
mod config;
|
mod config;
|
||||||
mod system;
|
mod system;
|
||||||
|
mod context;
|
||||||
mod matcher;
|
mod matcher;
|
||||||
mod keyboard;
|
mod keyboard;
|
||||||
mod clipboard;
|
mod clipboard;
|
||||||
|
@ -85,18 +89,18 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn espanso_main(config_set: ConfigSet) {
|
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 || {
|
thread::spawn(move || {
|
||||||
espanso_background(rxc, config_set);
|
espanso_background(receive_channel, config_set);
|
||||||
});
|
});
|
||||||
|
|
||||||
let interceptor = keyboard::get_interceptor(txc);
|
context.eventloop();
|
||||||
interceptor.initialize();
|
|
||||||
interceptor.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 system_manager = system::get_manager();
|
||||||
let config_manager = RuntimeConfigManager::new(config_set, system_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 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,
|
let engine = Engine::new(sender,
|
||||||
&clipboard_manager,
|
&clipboard_manager,
|
||||||
&config_manager,
|
&config_manager,
|
||||||
&ui_manager
|
&ui_manager
|
||||||
);
|
);
|
||||||
|
|
||||||
let matcher = ScrollingMatcher::new(&config_manager, engine);
|
let matcher = ScrollingMatcher::new(&config_manager, &engine);
|
||||||
matcher.watch(rxc);
|
|
||||||
|
let event_manager = DefaultEventManager::new(
|
||||||
|
receive_channel,
|
||||||
|
&matcher,
|
||||||
|
&engine,
|
||||||
|
);
|
||||||
|
|
||||||
|
event_manager.eventloop();
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use crate::keyboard::{KeyEvent, KeyModifier};
|
use crate::event::{KeyEvent, KeyModifier};
|
||||||
|
use crate::event::KeyEventReceiver;
|
||||||
|
|
||||||
pub(crate) mod scrolling;
|
pub(crate) mod scrolling;
|
||||||
|
|
||||||
|
@ -15,14 +16,14 @@ pub trait MatchReceiver {
|
||||||
fn on_toggle(&self, status: bool);
|
fn on_toggle(&self, status: bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Matcher {
|
pub trait Matcher : KeyEventReceiver {
|
||||||
fn handle_char(&self, c: char);
|
fn handle_char(&self, c: char);
|
||||||
fn handle_modifier(&self, m: KeyModifier);
|
fn handle_modifier(&self, m: KeyModifier);
|
||||||
fn watch(&self, receiver: Receiver<KeyEvent>) {
|
}
|
||||||
loop {
|
|
||||||
match receiver.recv() {
|
impl <M: Matcher> KeyEventReceiver for M {
|
||||||
Ok(event) => {
|
fn on_key_event(&self, e: KeyEvent) {
|
||||||
match event {
|
match e {
|
||||||
KeyEvent::Char(c) => {
|
KeyEvent::Char(c) => {
|
||||||
self.handle_char(c);
|
self.handle_char(c);
|
||||||
},
|
},
|
||||||
|
@ -30,9 +31,5 @@ pub trait Matcher {
|
||||||
self.handle_modifier(m);
|
self.handle_modifier(m);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
|
||||||
Err(_) => panic!("Keyboard interceptor broke receiver stream."),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::matcher::{Match, MatchReceiver};
|
use crate::matcher::{Match, MatchReceiver};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use crate::keyboard::KeyModifier;
|
use crate::event::KeyModifier;
|
||||||
use crate::config::ConfigManager;
|
use crate::config::ConfigManager;
|
||||||
use crate::keyboard::KeyModifier::BACKSPACE;
|
use crate::event::KeyModifier::BACKSPACE;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> {
|
pub struct ScrollingMatcher<'a, R: MatchReceiver, M: ConfigManager<'a>> {
|
||||||
config_manager: &'a M,
|
config_manager: &'a M,
|
||||||
receiver: R,
|
receiver: &'a R,
|
||||||
current_set_queue: RefCell<VecDeque<Vec<MatchEntry<'a>>>>,
|
current_set_queue: RefCell<VecDeque<Vec<MatchEntry<'a>>>>,
|
||||||
toggle_press_time: RefCell<SystemTime>,
|
toggle_press_time: RefCell<SystemTime>,
|
||||||
is_enabled: RefCell<bool>,
|
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> {
|
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 current_set_queue = RefCell::new(VecDeque::new());
|
||||||
let toggle_press_time = RefCell::new(SystemTime::now());
|
let toggle_press_time = RefCell::new(SystemTime::now());
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,15 @@ pub trait UIManager {
|
||||||
fn notify(&self, message: &str);
|
fn notify(&self, message: &str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum MenuItemType {
|
||||||
|
Button,
|
||||||
|
Separator
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MenuItem {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// MAC IMPLEMENTATION
|
// MAC IMPLEMENTATION
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn get_uimanager() -> impl UIManager {
|
pub fn get_uimanager() -> impl UIManager {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use std::process::Command;
|
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 widestring::U16CString;
|
||||||
use std::{fs, thread, time};
|
use std::{fs, thread, time};
|
||||||
use log::{info, debug};
|
use log::{info, debug};
|
||||||
|
@ -53,57 +53,12 @@ impl super::UIManager for WindowsUIManager {
|
||||||
|
|
||||||
impl WindowsUIManager {
|
impl WindowsUIManager {
|
||||||
pub fn new() -> 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 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 {
|
let manager = WindowsUIManager {
|
||||||
id
|
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
|
manager
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user