First draft of Windows ui-layer
This commit is contained in:
parent
b5e2d42ec4
commit
686ceb88da
25
Cargo.lock
generated
25
Cargo.lock
generated
|
@ -26,6 +26,8 @@ name = "espanso"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"espanso-detect 0.1.0",
|
"espanso-detect 0.1.0",
|
||||||
|
"espanso-ui 0.1.0",
|
||||||
|
"maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -34,6 +36,17 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cc 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"enum-as-inner 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"enum-as-inner 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lazycell 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"widestring 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "espanso-ui"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"cc 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lazycell 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"widestring 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"widestring 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
@ -46,6 +59,11 @@ dependencies = [
|
||||||
"unicode-segmentation 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"unicode-segmentation 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazycell"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
|
@ -54,6 +72,11 @@ dependencies = [
|
||||||
"cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "maplit"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.24"
|
version = "1.0.24"
|
||||||
|
@ -100,7 +123,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
"checksum cfg-if 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
"checksum enum-as-inner 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
|
"checksum enum-as-inner 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595"
|
||||||
"checksum heck 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
|
"checksum heck 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac"
|
||||||
|
"checksum lazycell 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
"checksum log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
"checksum log 0.4.14 (registry+https://github.com/rust-lang/crates.io-index)" = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
|
"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
|
||||||
"checksum proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
"checksum proc-macro2 1.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
|
||||||
"checksum quote 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
"checksum quote 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
|
||||||
"checksum syn 1.0.60 (registry+https://github.com/rust-lang/crates.io-index)" = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
|
"checksum syn 1.0.60 (registry+https://github.com/rust-lang/crates.io-index)" = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
|
||||||
|
|
|
@ -3,4 +3,5 @@
|
||||||
members = [
|
members = [
|
||||||
"espanso",
|
"espanso",
|
||||||
"espanso-detect",
|
"espanso-detect",
|
||||||
|
"espanso-ui",
|
||||||
]
|
]
|
|
@ -10,6 +10,7 @@ log = "0.4.14"
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
widestring = "0.4.3"
|
widestring = "0.4.3"
|
||||||
|
lazycell = "1.3.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
cc = "1.0.66"
|
cc = "1.0.66"
|
||||||
|
|
|
@ -17,7 +17,10 @@
|
||||||
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use log::{trace, warn};
|
use std::ffi::c_void;
|
||||||
|
|
||||||
|
use lazycell::LazyCell;
|
||||||
|
use log::{error, trace, warn};
|
||||||
use widestring::U16CStr;
|
use widestring::U16CStr;
|
||||||
|
|
||||||
use crate::event::Status::*;
|
use crate::event::Status::*;
|
||||||
|
@ -57,35 +60,81 @@ pub struct RawInputEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(improper_ctypes)]
|
#[allow(improper_ctypes)]
|
||||||
#[link(name = "native", kind = "static")]
|
#[link(name = "espansodetect", kind = "static")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
pub fn raw_eventloop(
|
pub fn detect_initialize(_self: *const Win32Source) -> *mut c_void;
|
||||||
_self: *const Win32Source,
|
|
||||||
|
pub fn detect_eventloop(
|
||||||
|
window: *const c_void,
|
||||||
event_callback: extern "C" fn(_self: *mut Win32Source, event: RawInputEvent),
|
event_callback: extern "C" fn(_self: *mut Win32Source, event: RawInputEvent),
|
||||||
);
|
) -> i32;
|
||||||
|
|
||||||
|
pub fn detect_destroy(window: *const c_void) -> i32;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Win32SourceCallback = Box<dyn Fn(InputEvent)>;
|
pub type Win32SourceCallback = Box<dyn Fn(InputEvent)>;
|
||||||
pub struct Win32Source {
|
pub struct Win32Source {
|
||||||
callback: Win32SourceCallback,
|
handle: *mut c_void,
|
||||||
|
callback: LazyCell<Win32SourceCallback>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Win32Source {
|
impl Win32Source {
|
||||||
pub fn new(callback: Win32SourceCallback) -> Win32Source {
|
pub fn new() -> Win32Source {
|
||||||
Self { callback }
|
Self {
|
||||||
|
handle: std::ptr::null_mut(),
|
||||||
|
callback: LazyCell::new(),
|
||||||
}
|
}
|
||||||
pub fn eventloop(&self) {
|
}
|
||||||
unsafe {
|
|
||||||
|
pub fn initialize(&mut self) {
|
||||||
|
let handle = unsafe { detect_initialize(self as *const Win32Source) };
|
||||||
|
|
||||||
|
if handle.is_null() {
|
||||||
|
panic!("Unable to initialize Win32EventLoop");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.handle = handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eventloop(&self, event_callback: Win32SourceCallback) {
|
||||||
|
if self.handle.is_null() {
|
||||||
|
panic!("Attempt to start Win32Source eventloop without initialization");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(_) = self.callback.fill(event_callback) {
|
||||||
|
panic!("Unable to set Win32Source event callback");
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" fn callback(_self: *mut Win32Source, event: RawInputEvent) {
|
extern "C" fn callback(_self: *mut Win32Source, event: RawInputEvent) {
|
||||||
let event: Option<InputEvent> = event.into();
|
let event: Option<InputEvent> = event.into();
|
||||||
|
if let Some(callback) = unsafe { (*_self).callback.borrow() } {
|
||||||
if let Some(event) = event {
|
if let Some(event) = event {
|
||||||
unsafe { (*(*_self).callback)(event) }
|
callback(event)
|
||||||
} else {
|
} else {
|
||||||
trace!("Unable to convert raw event to input event");
|
trace!("Unable to convert raw event to input event");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
raw_eventloop(self as *const Win32Source, callback);
|
let error_code = unsafe { detect_eventloop(self.handle, callback) };
|
||||||
|
|
||||||
|
if error_code <= 0 {
|
||||||
|
panic!("Win32Source eventloop returned a negative error code");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Win32Source {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.handle.is_null() {
|
||||||
|
error!("Win32Source destruction cannot be performed, handle is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = unsafe { detect_destroy(self.handle) };
|
||||||
|
|
||||||
|
if result != 0 {
|
||||||
|
error!("Win32EventLoop destruction returned non-zero code");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -260,17 +309,19 @@ mod tests {
|
||||||
raw.key_code = 0x4B;
|
raw.key_code = 0x4B;
|
||||||
|
|
||||||
let result: Option<InputEvent> = raw.into();
|
let result: Option<InputEvent> = raw.into();
|
||||||
assert_eq!(result.unwrap(), InputEvent::Keyboard(KeyboardEvent {
|
assert_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
InputEvent::Keyboard(KeyboardEvent {
|
||||||
key: Other(0x4B),
|
key: Other(0x4B),
|
||||||
status: Released,
|
status: Released,
|
||||||
value: Some("k".to_string()),
|
value: Some("k".to_string()),
|
||||||
variant: None,
|
variant: None,
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn raw_to_input_event_mouse_works_correctly() {
|
fn raw_to_input_event_mouse_works_correctly() {
|
||||||
|
|
||||||
let mut raw = default_raw_input_event();
|
let mut raw = default_raw_input_event();
|
||||||
raw.event_type = INPUT_EVENT_TYPE_MOUSE;
|
raw.event_type = INPUT_EVENT_TYPE_MOUSE;
|
||||||
raw.status = INPUT_STATUS_RELEASED;
|
raw.status = INPUT_STATUS_RELEASED;
|
||||||
|
@ -278,10 +329,13 @@ mod tests {
|
||||||
raw.key_code = INPUT_MOUSE_RIGHT_BUTTON;
|
raw.key_code = INPUT_MOUSE_RIGHT_BUTTON;
|
||||||
|
|
||||||
let result: Option<InputEvent> = raw.into();
|
let result: Option<InputEvent> = raw.into();
|
||||||
assert_eq!(result.unwrap(), InputEvent::Mouse(MouseEvent {
|
assert_eq!(
|
||||||
|
result.unwrap(),
|
||||||
|
InputEvent::Mouse(MouseEvent {
|
||||||
status: Released,
|
status: Released,
|
||||||
button: MouseButton::Right,
|
button: MouseButton::Right,
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -305,7 +359,8 @@ mod tests {
|
||||||
key_code: 123,
|
key_code: 123,
|
||||||
variant: INPUT_LEFT_VARIANT,
|
variant: INPUT_LEFT_VARIANT,
|
||||||
status: INPUT_STATUS_PRESSED,
|
status: INPUT_STATUS_PRESSED,
|
||||||
}.into();
|
}
|
||||||
|
.into();
|
||||||
assert!(result.is_none());
|
assert!(result.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "native.h"
|
#include "native.h"
|
||||||
|
#include <iostream>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
@ -39,29 +40,41 @@
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
// How many milliseconds must pass between events before refreshing the keyboard layout
|
// How many milliseconds must pass between events before refreshing the keyboard layout
|
||||||
const long refreshKeyboardLayoutInterval = 2000;
|
const long DETECT_REFRESH_KEYBOARD_LAYOUT_INTERVAL = 2000;
|
||||||
const USHORT mouseDownFlags = RI_MOUSE_LEFT_BUTTON_DOWN | RI_MOUSE_RIGHT_BUTTON_DOWN | RI_MOUSE_MIDDLE_BUTTON_DOWN |
|
const wchar_t *const DETECT_WINCLASS = L"EspansoDetect";
|
||||||
|
const USHORT MOUSE_DOWN_FLAGS = RI_MOUSE_LEFT_BUTTON_DOWN | RI_MOUSE_RIGHT_BUTTON_DOWN | RI_MOUSE_MIDDLE_BUTTON_DOWN |
|
||||||
RI_MOUSE_BUTTON_1_DOWN | RI_MOUSE_BUTTON_2_DOWN | RI_MOUSE_BUTTON_3_DOWN |
|
RI_MOUSE_BUTTON_1_DOWN | RI_MOUSE_BUTTON_2_DOWN | RI_MOUSE_BUTTON_3_DOWN |
|
||||||
RI_MOUSE_BUTTON_4_DOWN | RI_MOUSE_BUTTON_5_DOWN;
|
RI_MOUSE_BUTTON_4_DOWN | RI_MOUSE_BUTTON_5_DOWN;
|
||||||
const USHORT mouseUpFlags = RI_MOUSE_LEFT_BUTTON_UP | RI_MOUSE_RIGHT_BUTTON_UP | RI_MOUSE_MIDDLE_BUTTON_UP |
|
const USHORT MOUSE_UP_FLAGS = RI_MOUSE_LEFT_BUTTON_UP | RI_MOUSE_RIGHT_BUTTON_UP | RI_MOUSE_MIDDLE_BUTTON_UP |
|
||||||
RI_MOUSE_BUTTON_1_UP | RI_MOUSE_BUTTON_2_UP | RI_MOUSE_BUTTON_3_UP |
|
RI_MOUSE_BUTTON_1_UP | RI_MOUSE_BUTTON_2_UP | RI_MOUSE_BUTTON_3_UP |
|
||||||
RI_MOUSE_BUTTON_4_UP | RI_MOUSE_BUTTON_5_UP;
|
RI_MOUSE_BUTTON_4_UP | RI_MOUSE_BUTTON_5_UP;
|
||||||
|
|
||||||
DWORD lastKeyboardPressTick = 0;
|
typedef struct {
|
||||||
HKL currentKeyboardLayout;
|
HKL current_keyboard_layout;
|
||||||
HWND window;
|
DWORD last_key_press_tick;
|
||||||
const wchar_t *const winclass = L"Espanso";
|
|
||||||
|
|
||||||
void *self = NULL;
|
// Rust interop
|
||||||
EventCallback event_callback = NULL;
|
void * rust_instance;
|
||||||
|
EventCallback event_callback;
|
||||||
|
} DetectVariables;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Message handler procedure for the windows
|
* Message handler procedure for the window
|
||||||
*/
|
*/
|
||||||
LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPARAM lp)
|
LRESULT CALLBACK detect_window_procedure(HWND window, unsigned int msg, WPARAM wp, LPARAM lp)
|
||||||
{
|
{
|
||||||
|
DetectVariables * variables = reinterpret_cast<DetectVariables*>(GetWindowLongPtrW(window, GWLP_USERDATA));
|
||||||
|
|
||||||
switch (msg)
|
switch (msg)
|
||||||
{
|
{
|
||||||
|
case WM_DESTROY:
|
||||||
|
PostQuitMessage(0);
|
||||||
|
|
||||||
|
// Free the window variables
|
||||||
|
delete variables;
|
||||||
|
SetWindowLongPtrW(window, GWLP_USERDATA, NULL);
|
||||||
|
|
||||||
|
return 0L;
|
||||||
case WM_INPUT: // Message relative to the RAW INPUT events
|
case WM_INPUT: // Message relative to the RAW INPUT events
|
||||||
{
|
{
|
||||||
InputEvent event = {};
|
InputEvent event = {};
|
||||||
|
@ -104,7 +117,7 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR
|
||||||
DWORD currentTick = GetTickCount();
|
DWORD currentTick = GetTickCount();
|
||||||
|
|
||||||
// If enough time has passed between the last keypress and now, refresh the keyboard layout
|
// If enough time has passed between the last keypress and now, refresh the keyboard layout
|
||||||
if ((currentTick - lastKeyboardPressTick) > refreshKeyboardLayoutInterval)
|
if ((currentTick - variables->last_key_press_tick) > DETECT_REFRESH_KEYBOARD_LAYOUT_INTERVAL)
|
||||||
{
|
{
|
||||||
|
|
||||||
// Because keyboard layouts on windows are Window-specific, to get the current
|
// Because keyboard layouts on windows are Window-specific, to get the current
|
||||||
|
@ -119,11 +132,11 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR
|
||||||
// It's not always valid, so update the current value only if available.
|
// It's not always valid, so update the current value only if available.
|
||||||
if (newKeyboardLayout != 0)
|
if (newKeyboardLayout != 0)
|
||||||
{
|
{
|
||||||
currentKeyboardLayout = newKeyboardLayout;
|
variables->current_keyboard_layout = newKeyboardLayout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lastKeyboardPressTick = currentTick;
|
variables->last_key_press_tick = currentTick;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get keyboard state ( necessary to decode the associated Unicode char )
|
// Get keyboard state ( necessary to decode the associated Unicode char )
|
||||||
|
@ -134,7 +147,7 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR
|
||||||
// Refer to issue: https://github.com/federico-terzi/espanso/issues/86
|
// Refer to issue: https://github.com/federico-terzi/espanso/issues/86
|
||||||
UINT flags = 1 << 2;
|
UINT flags = 1 << 2;
|
||||||
|
|
||||||
int result = ToUnicodeEx(raw->data.keyboard.VKey, raw->data.keyboard.MakeCode, lpKeyState.data(), reinterpret_cast<LPWSTR>(event.buffer), (sizeof(event.buffer)/sizeof(event.buffer[0])) - 1, flags, currentKeyboardLayout);
|
int result = ToUnicodeEx(raw->data.keyboard.VKey, raw->data.keyboard.MakeCode, lpKeyState.data(), reinterpret_cast<LPWSTR>(event.buffer), (sizeof(event.buffer)/sizeof(event.buffer[0])) - 1, flags, variables->current_keyboard_layout);
|
||||||
|
|
||||||
// Handle the corresponding string if present
|
// Handle the corresponding string if present
|
||||||
if (result >= 1)
|
if (result >= 1)
|
||||||
|
@ -187,16 +200,16 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR
|
||||||
else if (raw->header.dwType == RIM_TYPEMOUSE) // Mouse events
|
else if (raw->header.dwType == RIM_TYPEMOUSE) // Mouse events
|
||||||
{
|
{
|
||||||
// Make sure the mouse event belongs to the supported ones
|
// Make sure the mouse event belongs to the supported ones
|
||||||
if ((raw->data.mouse.usButtonFlags & (mouseDownFlags | mouseUpFlags)) == 0) {
|
if ((raw->data.mouse.usButtonFlags & (MOUSE_DOWN_FLAGS | MOUSE_UP_FLAGS)) == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
event.event_type = INPUT_EVENT_TYPE_MOUSE;
|
event.event_type = INPUT_EVENT_TYPE_MOUSE;
|
||||||
|
|
||||||
if ((raw->data.mouse.usButtonFlags & mouseDownFlags) != 0)
|
if ((raw->data.mouse.usButtonFlags & MOUSE_DOWN_FLAGS) != 0)
|
||||||
{
|
{
|
||||||
event.status = INPUT_STATUS_PRESSED;
|
event.status = INPUT_STATUS_PRESSED;
|
||||||
} else if ((raw->data.mouse.usButtonFlags & mouseUpFlags) != 0) {
|
} else if ((raw->data.mouse.usButtonFlags & MOUSE_UP_FLAGS) != 0) {
|
||||||
event.status = INPUT_STATUS_RELEASED;
|
event.status = INPUT_STATUS_RELEASED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,9 +234,9 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR
|
||||||
}
|
}
|
||||||
|
|
||||||
// If valid, send the event to the Rust layer
|
// If valid, send the event to the Rust layer
|
||||||
if (event.event_type != 0 && self != NULL && event_callback != NULL)
|
if (event.event_type != 0 && variables->rust_instance != NULL && variables->event_callback != NULL)
|
||||||
{
|
{
|
||||||
event_callback(self, event);
|
variables->event_callback(variables->rust_instance, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -233,17 +246,16 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t raw_eventloop(void *_self, EventCallback _callback)
|
void * detect_initialize(void *_self)
|
||||||
{
|
{
|
||||||
// Initialize the default keyboard layout
|
HWND window = NULL;
|
||||||
currentKeyboardLayout = GetKeyboardLayout(0);
|
|
||||||
|
|
||||||
// Initialize the Worker window
|
// Initialize the Worker window
|
||||||
// Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa
|
// Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa
|
||||||
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_procedure, // lpfnWndProc: Pointer to the window procedure
|
detect_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.
|
||||||
|
@ -251,16 +263,22 @@ int32_t raw_eventloop(void *_self, EventCallback _callback)
|
||||||
LoadCursor(0, IDC_ARROW), // hCursor: A handle to the class cursor.
|
LoadCursor(0, IDC_ARROW), // hCursor: A handle to the class cursor.
|
||||||
NULL, // hbrBackground: A handle to the class background brush.
|
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
|
NULL, // lpszMenuName: Pointer to a null-terminated character string that specifies the resource name of the class menu
|
||||||
winclass, // lpszClassName: A pointer to a null-terminated string or is an atom.
|
DETECT_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.
|
NULL // hIconSm: A handle to a small icon that is associated with the window class.
|
||||||
};
|
};
|
||||||
|
|
||||||
if (RegisterClassEx(&wndclass))
|
if (RegisterClassEx(&wndclass))
|
||||||
{
|
{
|
||||||
|
DetectVariables * variables = new DetectVariables();
|
||||||
|
variables->rust_instance = _self;
|
||||||
|
|
||||||
|
// Initialize the default keyboard layout
|
||||||
|
variables->current_keyboard_layout = GetKeyboardLayout(0);
|
||||||
|
|
||||||
// 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(
|
||||||
0, // dwExStyle: The extended window style of the window being created.
|
0, // dwExStyle: The extended window style of the window being created.
|
||||||
winclass, // lpClassName: A null-terminated string or a class atom created by a previous call to the RegisterClass
|
DETECT_WINCLASS, // lpClassName: A null-terminated string or a class atom created by a previous call to the RegisterClass
|
||||||
L"Espanso Worker Window", // lpWindowName: The window name.
|
L"Espanso Worker Window", // lpWindowName: The window name.
|
||||||
WS_OVERLAPPEDWINDOW, // dwStyle: The style of the window being created.
|
WS_OVERLAPPEDWINDOW, // dwStyle: The style of the window being created.
|
||||||
CW_USEDEFAULT, // X: The initial horizontal position of the window.
|
CW_USEDEFAULT, // X: The initial horizontal position of the window.
|
||||||
|
@ -273,6 +291,8 @@ int32_t raw_eventloop(void *_self, EventCallback _callback)
|
||||||
NULL // lpParam: Pointer to a value to be passed to the window
|
NULL // lpParam: Pointer to a value to be passed to the window
|
||||||
);
|
);
|
||||||
|
|
||||||
|
SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast<::LONG_PTR>(variables));
|
||||||
|
|
||||||
// Register raw inputs
|
// Register raw inputs
|
||||||
RAWINPUTDEVICE Rid[2];
|
RAWINPUTDEVICE Rid[2];
|
||||||
|
|
||||||
|
@ -288,22 +308,27 @@ int32_t raw_eventloop(void *_self, EventCallback _callback)
|
||||||
|
|
||||||
if (RegisterRawInputDevices(Rid, 2, sizeof(Rid[0])) == FALSE)
|
if (RegisterRawInputDevices(Rid, 2, sizeof(Rid[0])) == FALSE)
|
||||||
{ // Something went wrong, error.
|
{ // Something went wrong, error.
|
||||||
return -1;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Something went wrong, error.
|
// Something went wrong, error.
|
||||||
return -2;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
event_callback = _callback;
|
return window;
|
||||||
self = _self;
|
}
|
||||||
|
|
||||||
|
int32_t detect_eventloop(void * window, EventCallback _callback)
|
||||||
|
{
|
||||||
if (window)
|
if (window)
|
||||||
{
|
{
|
||||||
|
DetectVariables * variables = reinterpret_cast<DetectVariables*>(GetWindowLongPtrW((HWND) window, GWLP_USERDATA));
|
||||||
|
variables->event_callback = _callback;
|
||||||
|
|
||||||
// Hide the window
|
// Hide the window
|
||||||
ShowWindow(window, SW_HIDE);
|
ShowWindow((HWND) window, SW_HIDE);
|
||||||
|
|
||||||
// Enter the Event loop
|
// Enter the Event loop
|
||||||
MSG msg;
|
MSG msg;
|
||||||
|
@ -311,8 +336,11 @@ int32_t raw_eventloop(void *_self, EventCallback _callback)
|
||||||
DispatchMessage(&msg);
|
DispatchMessage(&msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
event_callback = NULL;
|
|
||||||
self = NULL;
|
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t detect_destroy(void * window) {
|
||||||
|
if (window) {
|
||||||
|
return DestroyWindow((HWND) window);
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,10 +60,16 @@ typedef struct {
|
||||||
int32_t status;
|
int32_t status;
|
||||||
} InputEvent;
|
} InputEvent;
|
||||||
|
|
||||||
typedef void (*EventCallback)(void * self, InputEvent data);
|
typedef void (*EventCallback)(void * rust_istance, InputEvent data);
|
||||||
extern EventCallback event_callback;
|
|
||||||
|
|
||||||
// Initialize the Raw Input API and run the event loop. Blocking call.
|
|
||||||
extern "C" int32_t raw_eventloop(void * self, EventCallback callback);
|
// Initialize the Raw Input API and the Window.
|
||||||
|
extern "C" void * detect_initialize(void * rust_istance);
|
||||||
|
|
||||||
|
// Run the event loop. Blocking call.
|
||||||
|
extern "C" int32_t detect_eventloop(void * window, EventCallback callback);
|
||||||
|
|
||||||
|
// Destroy the given window.
|
||||||
|
extern "C" int32_t detect_destroy(void * window);
|
||||||
|
|
||||||
#endif //ESPANSO_DETECT_H
|
#endif //ESPANSO_DETECT_H
|
16
espanso-ui/Cargo.toml
Normal file
16
espanso-ui/Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
[package]
|
||||||
|
name = "espanso-ui"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Federico Terzi <federico-terzi@users.noreply.github.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
build="build.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4.14"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
widestring = "0.4.3"
|
||||||
|
lazycell = "1.3.0"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
cc = "1.0.66"
|
57
espanso-ui/build.rs
Normal file
57
espanso-ui/build.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* This file is part of espanso.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 Federico Terzi
|
||||||
|
*
|
||||||
|
* espanso is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* espanso is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn cc_config() {
|
||||||
|
println!("cargo:rerun-if-changed=src/win32/native.cpp");
|
||||||
|
println!("cargo:rerun-if-changed=src/win32/native.h");
|
||||||
|
cc::Build::new()
|
||||||
|
.cpp(true)
|
||||||
|
.include("src/win32/native.h")
|
||||||
|
.file("src/win32/native.cpp")
|
||||||
|
.compile("espansoui");
|
||||||
|
|
||||||
|
println!("cargo:rustc-link-lib=static=espansoui");
|
||||||
|
println!("cargo:rustc-link-lib=dylib=user32");
|
||||||
|
#[cfg(target_env = "gnu")]
|
||||||
|
println!("cargo:rustc-link-lib=dylib=stdc++");
|
||||||
|
#[cfg(target_env = "gnu")]
|
||||||
|
println!("cargo:rustc-link-lib=dylib=gdiplus");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn cc_config() {
|
||||||
|
println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu/");
|
||||||
|
println!("cargo:rustc-link-lib=static=linuxbridge");
|
||||||
|
println!("cargo:rustc-link-lib=dylib=X11");
|
||||||
|
println!("cargo:rustc-link-lib=dylib=Xtst");
|
||||||
|
println!("cargo:rustc-link-lib=dylib=xdo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn cc_config() {
|
||||||
|
println!("cargo:rustc-link-lib=dylib=c++");
|
||||||
|
println!("cargo:rustc-link-lib=static=macbridge");
|
||||||
|
println!("cargo:rustc-link-lib=framework=Cocoa");
|
||||||
|
println!("cargo:rustc-link-lib=framework=IOKit");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
cc_config();
|
||||||
|
}
|
23
espanso-ui/src/event.rs
Normal file
23
espanso-ui/src/event.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* This file is part of espanso.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 Federico Terzi
|
||||||
|
*
|
||||||
|
* espanso is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* espanso is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub enum UIEvent {
|
||||||
|
TrayIconClick,
|
||||||
|
}
|
8
espanso-ui/src/icons.rs
Normal file
8
espanso-ui/src/icons.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum TrayIcon {
|
||||||
|
Normal,
|
||||||
|
Disabled,
|
||||||
|
|
||||||
|
// For example, when macOS activates SecureInput
|
||||||
|
SystemDisabled,
|
||||||
|
}
|
5
espanso-ui/src/lib.rs
Normal file
5
espanso-ui/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub mod event;
|
||||||
|
pub mod icons;
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
pub mod win32;
|
269
espanso-ui/src/win32/mod.rs
Normal file
269
espanso-ui/src/win32/mod.rs
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
/*
|
||||||
|
* This file is part of espanso.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 Federico Terzi
|
||||||
|
*
|
||||||
|
* espanso is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* espanso is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
cmp::min,
|
||||||
|
collections::HashMap,
|
||||||
|
ffi::c_void,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicPtr, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
thread::ThreadId,
|
||||||
|
};
|
||||||
|
|
||||||
|
use lazycell::LazyCell;
|
||||||
|
use log::{error, trace};
|
||||||
|
use widestring::WideString;
|
||||||
|
|
||||||
|
use crate::{event::UIEvent, icons::TrayIcon};
|
||||||
|
|
||||||
|
// IMPORTANT: if you change these, also edit the native.h file.
|
||||||
|
const MAX_FILE_PATH: usize = 260;
|
||||||
|
const MAX_ICON_COUNT: usize = 3;
|
||||||
|
|
||||||
|
const UI_EVENT_TYPE_ICON_CLICK: i32 = 1;
|
||||||
|
//const UI_EVENT_TYPE_CONTEXT_MENU_CLICK: i32 = 2;
|
||||||
|
|
||||||
|
// Take a look at the native.h header file for an explanation of the fields
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct RawUIOptions {
|
||||||
|
pub show_icon: i32,
|
||||||
|
|
||||||
|
pub icon_paths: [[u16; MAX_FILE_PATH]; MAX_ICON_COUNT],
|
||||||
|
pub icon_paths_count: i32,
|
||||||
|
}
|
||||||
|
// Take a look at the native.h header file for an explanation of the fields
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct RawUIEvent {
|
||||||
|
pub event_type: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(improper_ctypes)]
|
||||||
|
#[link(name = "espansoui", kind = "static")]
|
||||||
|
extern "C" {
|
||||||
|
pub fn ui_initialize(_self: *const Win32EventLoop, options: RawUIOptions) -> *mut c_void;
|
||||||
|
pub fn ui_eventloop(
|
||||||
|
window_handle: *const c_void,
|
||||||
|
event_callback: extern "C" fn(_self: *mut Win32EventLoop, event: RawUIEvent),
|
||||||
|
) -> i32;
|
||||||
|
pub fn ui_destroy(window_handle: *const c_void) -> i32;
|
||||||
|
pub fn ui_update_tray_icon(window_handle: *const c_void, index: i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Win32UIOptions<'a> {
|
||||||
|
pub show_icon: bool,
|
||||||
|
pub icon_paths: &'a Vec<(TrayIcon, String)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(options: Win32UIOptions) -> (Win32Remote, Win32EventLoop) {
|
||||||
|
let handle: Arc<AtomicPtr<c_void>> = Arc::new(AtomicPtr::new(std::ptr::null_mut()));
|
||||||
|
|
||||||
|
// Validate icons
|
||||||
|
if options.icon_paths.len() > MAX_ICON_COUNT {
|
||||||
|
panic!("Win32 UI received too many icon paths, please increase the MAX_ICON_COUNT constant to support more");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the icon paths to the internal representation
|
||||||
|
let mut icon_indexes: HashMap<TrayIcon, usize> = HashMap::new();
|
||||||
|
let mut icons = Vec::new();
|
||||||
|
for (index, (tray_icon, path)) in options.icon_paths.iter().enumerate() {
|
||||||
|
icon_indexes.insert(tray_icon.clone(), index);
|
||||||
|
icons.push(path.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let eventloop = Win32EventLoop::new(handle.clone(), icons, options.show_icon);
|
||||||
|
let remote = Win32Remote::new(handle, icon_indexes);
|
||||||
|
|
||||||
|
(remote, eventloop)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Win32UIEventCallback = Box<dyn Fn(UIEvent)>;
|
||||||
|
|
||||||
|
pub struct Win32EventLoop {
|
||||||
|
handle: Arc<AtomicPtr<c_void>>,
|
||||||
|
|
||||||
|
show_icon: bool,
|
||||||
|
icons: Vec<String>,
|
||||||
|
|
||||||
|
// Internal
|
||||||
|
_event_callback: LazyCell<Win32UIEventCallback>,
|
||||||
|
_init_thread_id: LazyCell<ThreadId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Win32EventLoop {
|
||||||
|
pub(crate) fn new(handle: Arc<AtomicPtr<c_void>>, icons: Vec<String>, show_icon: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
handle,
|
||||||
|
icons,
|
||||||
|
show_icon,
|
||||||
|
_event_callback: LazyCell::new(),
|
||||||
|
_init_thread_id: LazyCell::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initialize(&mut self) {
|
||||||
|
let window_handle = self.handle.load(Ordering::Acquire);
|
||||||
|
if !window_handle.is_null() {
|
||||||
|
panic!("Attempt to initialize Win32EventLoop on non-null window handle");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the icon paths to the raw representation
|
||||||
|
let mut icon_paths: [[u16; MAX_FILE_PATH]; MAX_ICON_COUNT] =
|
||||||
|
[[0; MAX_FILE_PATH]; MAX_ICON_COUNT];
|
||||||
|
for i in 0..self.icons.len() {
|
||||||
|
let wide_path = WideString::from_str(&self.icons[i]);
|
||||||
|
let len = min(wide_path.len(), MAX_FILE_PATH - 1);
|
||||||
|
icon_paths[i][0..len].clone_from_slice(&wide_path.as_slice()[..len]);
|
||||||
|
// TODO: test overflow, correct case
|
||||||
|
}
|
||||||
|
|
||||||
|
let options = RawUIOptions {
|
||||||
|
show_icon: if self.show_icon { 1 } else { 0 },
|
||||||
|
icon_paths,
|
||||||
|
icon_paths_count: self.icons.len() as i32,
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle = unsafe { ui_initialize(self as *const Win32EventLoop, options) };
|
||||||
|
|
||||||
|
if handle.is_null() {
|
||||||
|
panic!("Unable to initialize Win32EventLoop");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.handle.store(handle, Ordering::Release);
|
||||||
|
|
||||||
|
// TODO: explain
|
||||||
|
self
|
||||||
|
._init_thread_id
|
||||||
|
.fill(std::thread::current().id())
|
||||||
|
.expect("Unable to set initialization thread id");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&self, event_callback: Win32UIEventCallback) {
|
||||||
|
// Make sure the run() method is called in the same thread as initialize()
|
||||||
|
if let Some(init_id) = self._init_thread_id.borrow() {
|
||||||
|
if init_id != &std::thread::current().id() {
|
||||||
|
panic!("Win32EventLoop run() and initialize() methods should be called in the same thread");
|
||||||
|
// TODO: test
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let window_handle = self.handle.load(Ordering::Acquire);
|
||||||
|
if window_handle.is_null() {
|
||||||
|
panic!("Attempt to run Win32EventLoop on a null window handle");
|
||||||
|
// TODO: test
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(_) = self._event_callback.fill(event_callback) {
|
||||||
|
panic!("Unable to set Win32EventLoop callback");
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" fn callback(_self: *mut Win32EventLoop, event: RawUIEvent) {
|
||||||
|
if let Some(callback) = unsafe { (*_self)._event_callback.borrow() } {
|
||||||
|
let event: Option<UIEvent> = event.into();
|
||||||
|
if let Some(event) = event {
|
||||||
|
callback(event)
|
||||||
|
} else {
|
||||||
|
trace!("Unable to convert raw event to input event");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_code = unsafe { ui_eventloop(window_handle, callback) };
|
||||||
|
|
||||||
|
if error_code <= 0 {
|
||||||
|
panic!("Win32EventLoop exited with <= 0 code")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Win32EventLoop {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let handle = self.handle.swap(std::ptr::null_mut(), Ordering::Acquire);
|
||||||
|
if handle.is_null() {
|
||||||
|
error!("Win32EventLoop destruction cannot be performed, handle is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = unsafe { ui_destroy(handle) };
|
||||||
|
|
||||||
|
if result != 0 {
|
||||||
|
error!("Win32EventLoop destruction returned non-zero code");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Win32Remote {
|
||||||
|
handle: Arc<AtomicPtr<c_void>>,
|
||||||
|
|
||||||
|
// Maps icon name to their index
|
||||||
|
icon_indexes: HashMap<TrayIcon, usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Win32Remote {
|
||||||
|
pub(crate) fn new(
|
||||||
|
handle: Arc<AtomicPtr<c_void>>,
|
||||||
|
icon_indexes: HashMap<TrayIcon, usize>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
handle,
|
||||||
|
icon_indexes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_tray_icon(&self, icon: TrayIcon) {
|
||||||
|
let handle = self.handle.load(Ordering::Acquire);
|
||||||
|
if handle.is_null() {
|
||||||
|
error!("Unable to update tray icon, pointer is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(index) = self.icon_indexes.get(&icon) {
|
||||||
|
unsafe { ui_update_tray_icon(handle, (*index) as i32) }
|
||||||
|
} else {
|
||||||
|
error!("Unable to update tray icon, invalid icon id");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RawUIEvent> for Option<UIEvent> {
|
||||||
|
fn from(raw: RawUIEvent) -> Option<UIEvent> {
|
||||||
|
match raw.event_type {
|
||||||
|
// Keyboard events
|
||||||
|
UI_EVENT_TYPE_ICON_CLICK => {
|
||||||
|
return Some(UIEvent::TrayIconClick);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn constants_are_not_changed_by_mistake() {
|
||||||
|
assert_eq!(MAX_FILE_PATH, 260);
|
||||||
|
assert_eq!(MAX_ICON_COUNT, 3);
|
||||||
|
}
|
||||||
|
}
|
276
espanso-ui/src/win32/native.cpp
Normal file
276
espanso-ui/src/win32/native.cpp
Normal file
|
@ -0,0 +1,276 @@
|
||||||
|
/*
|
||||||
|
* This file is part of espanso.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 Federico Terzi
|
||||||
|
*
|
||||||
|
* espanso is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* espanso is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "native.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#define UNICODE
|
||||||
|
|
||||||
|
#ifdef __MINGW32__
|
||||||
|
#ifndef WINVER
|
||||||
|
#define WINVER 0x0606
|
||||||
|
#endif
|
||||||
|
#define STRSAFE_NO_DEPRECATE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <winuser.h>
|
||||||
|
#include <strsafe.h>
|
||||||
|
#pragma comment(lib, "Shell32.lib")
|
||||||
|
#include <shellapi.h>
|
||||||
|
|
||||||
|
#pragma comment(lib, "Gdi32.lib")
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#define APPWM_ICON_CLICK (WM_APP + 1)
|
||||||
|
#define APPWM_SHOW_CONTEXT_MENU (WM_APP + 2)
|
||||||
|
#define APPWM_UPDATE_TRAY_ICON (WM_APP + 3)
|
||||||
|
|
||||||
|
const wchar_t *const ui_winclass = L"EspansoUI";
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
UIOptions options;
|
||||||
|
NOTIFYICONDATA nid;
|
||||||
|
HICON g_icons[MAX_ICON_COUNT];
|
||||||
|
|
||||||
|
// Rust interop
|
||||||
|
void *rust_instance;
|
||||||
|
EventCallback event_callback;
|
||||||
|
} UIVariables;
|
||||||
|
|
||||||
|
|
||||||
|
// Needed to detect when Explorer crashes
|
||||||
|
UINT WM_TASKBARCREATED = RegisterWindowMessage(L"TaskbarCreated");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Message handler procedure for the window
|
||||||
|
*/
|
||||||
|
LRESULT CALLBACK ui_window_procedure(HWND window, unsigned int msg, WPARAM wp, LPARAM lp)
|
||||||
|
{
|
||||||
|
UIEvent event = {};
|
||||||
|
UIVariables * variables = reinterpret_cast<UIVariables*>(GetWindowLongPtrW(window, GWLP_USERDATA));
|
||||||
|
|
||||||
|
switch (msg)
|
||||||
|
{
|
||||||
|
case WM_DESTROY:
|
||||||
|
PostQuitMessage(0);
|
||||||
|
|
||||||
|
// Free the tray icons
|
||||||
|
for (int i = 0; i < variables->options.icon_paths_count; i++)
|
||||||
|
{
|
||||||
|
DeleteObject(variables->g_icons[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free the window variables
|
||||||
|
delete variables;
|
||||||
|
SetWindowLongPtrW(window, GWLP_USERDATA, NULL);
|
||||||
|
|
||||||
|
return 0L;
|
||||||
|
case WM_COMMAND: // Click on the tray icon context menu
|
||||||
|
{
|
||||||
|
UINT idItem = (UINT)LOWORD(wp);
|
||||||
|
UINT flags = (UINT)HIWORD(wp);
|
||||||
|
|
||||||
|
if (flags == 0)
|
||||||
|
{
|
||||||
|
std::cout << "click menu" << std::flush;
|
||||||
|
//context_menu_click_callback(manager_instance, (int32_t)idItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case APPWM_SHOW_CONTEXT_MENU: // Request to show context menu
|
||||||
|
{
|
||||||
|
HMENU hPopupMenu = CreatePopupMenu();
|
||||||
|
|
||||||
|
// Create the menu
|
||||||
|
|
||||||
|
/*
|
||||||
|
int32_t count = static_cast<int32_t>(lp);
|
||||||
|
std::unique_ptr<MenuItem[]> items(reinterpret_cast<MenuItem*>(wp));
|
||||||
|
|
||||||
|
for (int i = 0; i<count; i++) {
|
||||||
|
if (items[i].type == 1) {
|
||||||
|
InsertMenu(hPopupMenu, i, MF_BYPOSITION | MF_STRING, items[i].id, items[i].name);
|
||||||
|
}else{
|
||||||
|
InsertMenu(hPopupMenu, i, MF_BYPOSITION | MF_SEPARATOR, items[i].id, NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
POINT pt;
|
||||||
|
GetCursorPos(&pt);
|
||||||
|
SetForegroundWindow(nw);
|
||||||
|
TrackPopupMenu(hPopupMenu, TPM_BOTTOMALIGN | TPM_LEFTALIGN, pt.x, pt.y, 0, nw, NULL);
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case APPWM_UPDATE_TRAY_ICON: // Request to update the tray icon
|
||||||
|
{
|
||||||
|
int32_t index = (int32_t) lp;
|
||||||
|
if (index >= variables->options.icon_paths_count) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
variables->nid.hIcon = variables->g_icons[index];
|
||||||
|
if (variables->options.show_icon) {
|
||||||
|
Shell_NotifyIcon(NIM_MODIFY, &variables->nid);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case APPWM_ICON_CLICK: // Click on the tray icon
|
||||||
|
{
|
||||||
|
switch (lp)
|
||||||
|
{
|
||||||
|
case WM_LBUTTONUP:
|
||||||
|
case WM_RBUTTONUP:
|
||||||
|
event.event_type = UI_EVENT_TYPE_ICON_CLICK;
|
||||||
|
if (variables->event_callback && variables->rust_instance)
|
||||||
|
{
|
||||||
|
variables->event_callback(variables->rust_instance, event);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if (msg == WM_TASKBARCREATED)
|
||||||
|
{ // Explorer crashed, recreate the icon
|
||||||
|
if (variables->options.show_icon)
|
||||||
|
{
|
||||||
|
Shell_NotifyIcon(NIM_ADD, &variables->nid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return DefWindowProc(window, msg, wp, lp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void * ui_initialize(void *_self, UIOptions _options) {
|
||||||
|
HWND window = NULL;
|
||||||
|
|
||||||
|
SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
|
||||||
|
|
||||||
|
// Service Window
|
||||||
|
|
||||||
|
// Docs: https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-wndclassexa
|
||||||
|
WNDCLASSEX uiwndclass = {
|
||||||
|
sizeof(WNDCLASSEX), // cbSize: Size of this structure
|
||||||
|
0, // style: Class styles
|
||||||
|
ui_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
|
||||||
|
ui_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(&uiwndclass))
|
||||||
|
{
|
||||||
|
// Initialize the service window
|
||||||
|
window = CreateWindowEx(
|
||||||
|
0, // dwExStyle: The extended window style of the window being created.
|
||||||
|
ui_winclass, // lpClassName: A null-terminated string or a class atom created by a previous call to the RegisterClass
|
||||||
|
L"Espanso UI Window", // lpWindowName: The window name.
|
||||||
|
WS_OVERLAPPEDWINDOW, // 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.
|
||||||
|
100, // 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 (window)
|
||||||
|
{
|
||||||
|
UIVariables * variables = new UIVariables();
|
||||||
|
variables->options = _options;
|
||||||
|
variables->rust_instance = _self;
|
||||||
|
SetWindowLongPtrW(window, GWLP_USERDATA, reinterpret_cast<::LONG_PTR>(variables));
|
||||||
|
|
||||||
|
// Load the tray icons
|
||||||
|
for (int i = 0; i < variables->options.icon_paths_count; i++)
|
||||||
|
{
|
||||||
|
variables->g_icons[i] = (HICON)LoadImage(NULL, variables->options.icon_paths[i], IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR | LR_SHARED | LR_DEFAULTSIZE | LR_LOADFROMFILE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the window
|
||||||
|
ShowWindow(window, SW_HIDE);
|
||||||
|
|
||||||
|
// Setup the icon in the notification space
|
||||||
|
SendMessage(window, WM_SETICON, ICON_BIG, (LPARAM)variables->g_icons[0]);
|
||||||
|
SendMessage(window, WM_SETICON, ICON_SMALL, (LPARAM)variables->g_icons[0]);
|
||||||
|
|
||||||
|
// Notification
|
||||||
|
variables->nid.cbSize = sizeof(variables->nid);
|
||||||
|
variables->nid.hWnd = window;
|
||||||
|
variables->nid.uID = 1;
|
||||||
|
variables->nid.uFlags = NIF_ICON | NIF_TIP | NIF_MESSAGE;
|
||||||
|
variables->nid.uCallbackMessage = APPWM_ICON_CLICK;
|
||||||
|
variables->nid.hIcon = variables->g_icons[0];
|
||||||
|
StringCchCopyW(variables->nid.szTip, ARRAYSIZE(variables->nid.szTip), L"espanso");
|
||||||
|
|
||||||
|
// Show the tray icon
|
||||||
|
if (variables->options.show_icon)
|
||||||
|
{
|
||||||
|
Shell_NotifyIcon(NIM_ADD, &variables->nid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return window;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t ui_eventloop(void * window, EventCallback _callback)
|
||||||
|
{
|
||||||
|
if (window)
|
||||||
|
{
|
||||||
|
UIVariables * variables = reinterpret_cast<UIVariables*>(GetWindowLongPtrW((HWND) window, GWLP_USERDATA));
|
||||||
|
variables->event_callback = _callback;
|
||||||
|
|
||||||
|
// Enter the Event loop
|
||||||
|
MSG msg;
|
||||||
|
while (GetMessage(&msg, 0, 0, 0))
|
||||||
|
DispatchMessage(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t ui_destroy(void * window) {
|
||||||
|
if (window) {
|
||||||
|
return DestroyWindow((HWND) window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ui_update_tray_icon(void * window, int32_t index)
|
||||||
|
{
|
||||||
|
if (window) {
|
||||||
|
PostMessage((HWND) window, APPWM_UPDATE_TRAY_ICON, 0, index);
|
||||||
|
}
|
||||||
|
}
|
59
espanso-ui/src/win32/native.h
Normal file
59
espanso-ui/src/win32/native.h
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
/*
|
||||||
|
* This file is part of espanso.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019-2021 Federico Terzi
|
||||||
|
*
|
||||||
|
* espanso is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* espanso is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with espanso. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef ESPANSO_UI_H
|
||||||
|
#define ESPANSO_UI_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Explicitly define this constant as we need to use it from the Rust side
|
||||||
|
// https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation
|
||||||
|
#define MAX_FILE_PATH 260
|
||||||
|
#define MAX_ICON_COUNT 3
|
||||||
|
|
||||||
|
#define UI_EVENT_TYPE_ICON_CLICK 1
|
||||||
|
#define UI_EVENT_TYPE_CONTEXT_MENU_CLICK 2
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t show_icon;
|
||||||
|
|
||||||
|
wchar_t icon_paths[MAX_ICON_COUNT][MAX_FILE_PATH];
|
||||||
|
int32_t icon_paths_count;
|
||||||
|
} UIOptions;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int32_t event_type;
|
||||||
|
} UIEvent;
|
||||||
|
|
||||||
|
typedef void (*EventCallback)(void * self, UIEvent data);
|
||||||
|
|
||||||
|
// Initialize the hidden UI window, the tray icon and returns the window handle.
|
||||||
|
extern "C" void * ui_initialize(void * self, UIOptions options);
|
||||||
|
|
||||||
|
// Run the event loop. Blocking call.
|
||||||
|
extern "C" int32_t ui_eventloop(void * window, EventCallback callback);
|
||||||
|
|
||||||
|
// Destroy the given window.
|
||||||
|
extern "C" int32_t ui_destroy(void * window);
|
||||||
|
|
||||||
|
// Updates the tray icon to the given one. The method accepts an index that refers to
|
||||||
|
// the icon within the UIOptions.icon_paths array.
|
||||||
|
extern "C" void ui_update_tray_icon(void * window, int32_t index);
|
||||||
|
|
||||||
|
#endif //ESPANSO_UI_H
|
|
@ -10,3 +10,5 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
espanso-detect = { path = "../espanso-detect" }
|
espanso-detect = { path = "../espanso-detect" }
|
||||||
|
espanso-ui = { path = "../espanso-ui" }
|
||||||
|
maplit = "1.0.2"
|
|
@ -1,8 +1,42 @@
|
||||||
|
use espanso_detect::event::InputEvent;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hello, world!z");
|
println!("Hello, world!z");
|
||||||
|
|
||||||
let source = espanso_detect::win32::Win32Source::new(Box::new(|event| {
|
let icon_paths = vec![
|
||||||
|
(
|
||||||
|
espanso_ui::icons::TrayIcon::Normal,
|
||||||
|
r"C:\Users\Freddy\AppData\Local\espanso\espanso.ico".to_string(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
espanso_ui::icons::TrayIcon::Disabled,
|
||||||
|
r"C:\Users\Freddy\AppData\Local\espanso\espansored.ico".to_string(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
let (remote, mut eventloop) = espanso_ui::win32::create(espanso_ui::win32::Win32UIOptions {
|
||||||
|
show_icon: true,
|
||||||
|
icon_paths: &icon_paths,
|
||||||
|
});
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let mut source = espanso_detect::win32::Win32Source::new();
|
||||||
|
source.initialize();
|
||||||
|
source.eventloop(Box::new(move |event: InputEvent| {
|
||||||
println!("ev {:?}", event);
|
println!("ev {:?}", event);
|
||||||
|
match event {
|
||||||
|
InputEvent::Mouse(_) => {}
|
||||||
|
InputEvent::Keyboard(evt) => {
|
||||||
|
if evt.key == espanso_detect::event::Key::Shift {
|
||||||
|
remote.update_tray_icon(espanso_ui::icons::TrayIcon::Disabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
source.eventloop();
|
});
|
||||||
|
|
||||||
|
eventloop.initialize();
|
||||||
|
eventloop.run(Box::new(|event| {
|
||||||
|
println!("ui {:?}", event);
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user