commit
2f5c48f2e2
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -366,7 +366,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "espanso"
|
name = "espanso"
|
||||||
version = "0.5.4"
|
version = "0.5.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
"backtrace 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "espanso"
|
name = "espanso"
|
||||||
version = "0.5.4"
|
version = "0.5.5"
|
||||||
authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
|
authors = ["Federico Terzi <federicoterzi96@gmail.com>"]
|
||||||
license = "GPL-3.0"
|
license = "GPL-3.0"
|
||||||
description = "Cross-platform Text Expander written in Rust"
|
description = "Cross-platform Text Expander written in Rust"
|
||||||
|
|
46
SECURITY.md
Normal file
46
SECURITY.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Security
|
||||||
|
|
||||||
|
Espanso has always been designed with a strong focus on security.
|
||||||
|
In the following section, there is an overview of the critical security
|
||||||
|
components.
|
||||||
|
|
||||||
|
If you have any doubt, don't hesitate to contact me.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
In its most basic form, a text expander is composed of two parts:
|
||||||
|
|
||||||
|
* A **global key detector** that intercepts the keys pressed by the user,
|
||||||
|
in order to determine if a trigger was typed.
|
||||||
|
|
||||||
|
* A **key injection mechanism** that injects the
|
||||||
|
final text into the current application, a process known as *expansion*.
|
||||||
|
|
||||||
|
At this point, some of you may think that espanso is acting as a keylogger,
|
||||||
|
due to the *global key detector* we mentioned before. The good news is, **it's not!**
|
||||||
|
|
||||||
|
While espanso detects key presses as a keylogger would do,
|
||||||
|
**it doesn't log anything**. Moreover, to further reduce risks, espanso only
|
||||||
|
stores in memory the last 3 chars by default (you can change this amount by
|
||||||
|
setting the `backspace_limit` parameter in the config) and this is needed
|
||||||
|
to allow the user to correct wrongly typed triggers by pressing backspace,
|
||||||
|
up to 3 characters.
|
||||||
|
|
||||||
|
The matching part is implemented with an efficient [data structure](https://github.com/federico-terzi/espanso/blob/master/src/matcher/scrolling.rs)
|
||||||
|
that keeps track of the compatible matches in a "rolling" basis. So that in the worst case scenario,
|
||||||
|
the longest sequence of chars kept in memory would be equal to the longest trigger.
|
||||||
|
|
||||||
|
And of course, if you don't trust me you can examine all the code! That's
|
||||||
|
the wonderful thing about open source :)
|
||||||
|
|
||||||
|
### Implementation
|
||||||
|
|
||||||
|
The *global key detector* is implemented on top of various OS-dependent APIs, in particular:
|
||||||
|
|
||||||
|
* On Windows, it uses the [RawInput API](https://docs.microsoft.com/en-us/windows/win32/inputdev/raw-input).
|
||||||
|
* On macOS, it uses [addGlobalMonitorForEvents](https://developer.apple.com/documentation/appkit/nsevent/1535472-addglobalmonitorforevents).
|
||||||
|
* On Linux, it uses the [X Record Extension](https://www.x.org/releases/X11R7.6/doc/libXtst/recordlib.html).
|
||||||
|
|
||||||
|
## Reporting Security Issues
|
||||||
|
|
||||||
|
To report a security issue, please email me at federicoterzi96[at]gmail.com
|
|
@ -4,6 +4,6 @@ project(liblinuxbridge)
|
||||||
set (CMAKE_CXX_STANDARD 14)
|
set (CMAKE_CXX_STANDARD 14)
|
||||||
set(CMAKE_REQUIRED_INCLUDES "/usr/local/include" "/usr/include")
|
set(CMAKE_REQUIRED_INCLUDES "/usr/local/include" "/usr/include")
|
||||||
|
|
||||||
add_library(linuxbridge STATIC bridge.cpp bridge.h)
|
add_library(linuxbridge STATIC bridge.cpp bridge.h fast_xdo.cpp fast_xdo.h)
|
||||||
|
|
||||||
install(TARGETS linuxbridge DESTINATION .)
|
install(TARGETS linuxbridge DESTINATION .)
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "bridge.h"
|
#include "bridge.h"
|
||||||
|
#include "fast_xdo.h"
|
||||||
|
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
@ -302,18 +303,82 @@ void send_enter() {
|
||||||
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Return", 1000);
|
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Return", 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fast_release_all_keys() {
|
||||||
|
Window focused;
|
||||||
|
int revert_to;
|
||||||
|
XGetInputFocus(xdo_context->xdpy, &focused, &revert_to);
|
||||||
|
|
||||||
|
char keys[32];
|
||||||
|
XQueryKeymap(xdo_context->xdpy, keys); // Get the current status of the keyboard
|
||||||
|
for (int i = 0; i<32; i++) {
|
||||||
|
// Only those that show a keypress should be changed
|
||||||
|
if (keys[i] != 0) {
|
||||||
|
for (int k = 0; k<8; k++) {
|
||||||
|
if ((keys[i] & (1 << k)) != 0) { // Bit by bit check
|
||||||
|
int key_code = i*8 + k;
|
||||||
|
fast_send_event(xdo_context, focused, key_code, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XFlush(xdo_context->xdpy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fast_send_string(const char * string) {
|
||||||
|
// It may happen that when an expansion is triggered, some keys are still pressed.
|
||||||
|
// This causes a problem if the expanded match contains that character, as the injection
|
||||||
|
// will not be able to register that keypress (as it is already pressed).
|
||||||
|
// To solve the problem, before an expansion we get which keys are currently pressed
|
||||||
|
// and inject a key_release event so that they can be further registered.
|
||||||
|
fast_release_all_keys();
|
||||||
|
|
||||||
|
Window focused;
|
||||||
|
int revert_to;
|
||||||
|
XGetInputFocus(xdo_context->xdpy, &focused, &revert_to);
|
||||||
|
|
||||||
|
fast_enter_text_window(xdo_context, focused, string, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _fast_send_keycode_to_focused_window(int KeyCode, int32_t count) {
|
||||||
|
int keycode = XKeysymToKeycode(xdo_context->xdpy, KeyCode);
|
||||||
|
|
||||||
|
Window focused;
|
||||||
|
int revert_to;
|
||||||
|
XGetInputFocus(xdo_context->xdpy, &focused, &revert_to);
|
||||||
|
|
||||||
|
for (int i = 0; i<count; i++) {
|
||||||
|
fast_send_event(xdo_context, focused, keycode, 1);
|
||||||
|
fast_send_event(xdo_context, focused, keycode, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
XFlush(xdo_context->xdpy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fast_send_enter() {
|
||||||
|
_fast_send_keycode_to_focused_window(XK_Return, 1);
|
||||||
|
}
|
||||||
|
|
||||||
void delete_string(int32_t count) {
|
void delete_string(int32_t count) {
|
||||||
for (int i = 0; i<count; i++) {
|
for (int i = 0; i<count; i++) {
|
||||||
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "BackSpace", 1000);
|
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "BackSpace", 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fast_delete_string(int32_t count) {
|
||||||
|
_fast_send_keycode_to_focused_window(XK_BackSpace, count);
|
||||||
|
}
|
||||||
|
|
||||||
void left_arrow(int32_t count) {
|
void left_arrow(int32_t count) {
|
||||||
for (int i = 0; i<count; i++) {
|
for (int i = 0; i<count; i++) {
|
||||||
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Left", 1000);
|
xdo_send_keysequence_window(xdo_context, CURRENTWINDOW, "Left", 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fast_left_arrow(int32_t count) {
|
||||||
|
_fast_send_keycode_to_focused_window(XK_Left, count);
|
||||||
|
}
|
||||||
|
|
||||||
void trigger_paste() {
|
void trigger_paste() {
|
||||||
// Before sending the paste shortcut, trigger the press and release of the Shift key
|
// Before sending the paste shortcut, trigger the press and release of the Shift key
|
||||||
// this is needed because for some triggers, for example ending with ":", the user
|
// this is needed because for some triggers, for example ending with ":", the user
|
||||||
|
|
|
@ -62,21 +62,41 @@ extern "C" void register_keypress_callback(KeypressCallback callback);
|
||||||
*/
|
*/
|
||||||
extern "C" void send_string(const char * string);
|
extern "C" void send_string(const char * string);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Type the given string by simulating Key Presses using a faster inject method
|
||||||
|
*/
|
||||||
|
extern "C" void fast_send_string(const char * string);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Send the backspace keypress, *count* times.
|
* Send the backspace keypress, *count* times.
|
||||||
*/
|
*/
|
||||||
extern "C" void delete_string(int32_t count);
|
extern "C" void delete_string(int32_t count);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send the backspace keypress, *count* times using a faster inject method
|
||||||
|
*/
|
||||||
|
extern "C" void fast_delete_string(int32_t count);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Send an Enter key press
|
* Send an Enter key press
|
||||||
*/
|
*/
|
||||||
extern "C" void send_enter();
|
extern "C" void send_enter();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send an Enter key press using a faster inject method
|
||||||
|
*/
|
||||||
|
extern "C" void fast_send_enter();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Send the left arrow keypress, *count* times.
|
* Send the left arrow keypress, *count* times.
|
||||||
*/
|
*/
|
||||||
extern "C" void left_arrow(int32_t count);
|
extern "C" void left_arrow(int32_t count);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Send the left arrow keypress, *count* times using a faster inject method
|
||||||
|
*/
|
||||||
|
extern "C" void fast_left_arrow(int32_t count);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Trigger normal paste ( Pressing CTRL+V )
|
* Trigger normal paste ( Pressing CTRL+V )
|
||||||
*/
|
*/
|
||||||
|
|
246
native/liblinuxbridge/fast_xdo.cpp
Normal file
246
native/liblinuxbridge/fast_xdo.cpp
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
//
|
||||||
|
// Most of this code has been taken from the wonderful XDOTOOL: https://github.com/jordansissel/xdotool/blob/master/COPYRIGHT
|
||||||
|
// and modified to use XSendEvent instead of XTestFakeKeyEvent.
|
||||||
|
|
||||||
|
#include <locale.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "fast_xdo.h"
|
||||||
|
|
||||||
|
extern "C" { // Needed to avoid C++ compiler name mangling
|
||||||
|
#include <xdo.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
void fast_init_xkeyevent(const xdo_t *xdo, XKeyEvent *xk) {
|
||||||
|
xk->display = xdo->xdpy;
|
||||||
|
xk->subwindow = None;
|
||||||
|
xk->time = CurrentTime;
|
||||||
|
xk->same_screen = True;
|
||||||
|
|
||||||
|
/* Should we set these at all? */
|
||||||
|
xk->x = xk->y = xk->x_root = xk->y_root = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fast_send_key(const xdo_t *xdo, Window window, charcodemap_t *key,
|
||||||
|
int modstate, int is_press, useconds_t delay) {
|
||||||
|
/* Properly ensure the modstate is set by finding a key
|
||||||
|
* that activates each bit in the modifier state */
|
||||||
|
int mask = modstate | key->modmask;
|
||||||
|
|
||||||
|
/* Since key events have 'state' (shift, etc) in the event, we don't
|
||||||
|
* need to worry about key press ordering. */
|
||||||
|
XKeyEvent xk;
|
||||||
|
fast_init_xkeyevent(xdo, &xk);
|
||||||
|
xk.window = window;
|
||||||
|
xk.keycode = key->code;
|
||||||
|
xk.state = mask | (key->group << 13);
|
||||||
|
xk.type = (is_press ? KeyPress : KeyRelease);
|
||||||
|
XSendEvent(xdo->xdpy, xk.window, True, 0, (XEvent *)&xk);
|
||||||
|
|
||||||
|
/* Skipping the usleep if delay is 0 is much faster than calling usleep(0) */
|
||||||
|
XFlush(xdo->xdpy);
|
||||||
|
if (delay > 0) {
|
||||||
|
usleep(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int fast_send_keysequence_window_list_do(const xdo_t *xdo, Window window, charcodemap_t *keys,
|
||||||
|
int nkeys, int pressed, int *modifier, useconds_t delay) {
|
||||||
|
int i = 0;
|
||||||
|
int modstate = 0;
|
||||||
|
int keymapchanged = 0;
|
||||||
|
|
||||||
|
/* Find an unused keycode in case we need to bind unmapped keysyms */
|
||||||
|
KeySym *keysyms = NULL;
|
||||||
|
int keysyms_per_keycode = 0;
|
||||||
|
int scratch_keycode = 0; /* Scratch space for temporary keycode bindings */
|
||||||
|
keysyms = XGetKeyboardMapping(xdo->xdpy, xdo->keycode_low,
|
||||||
|
xdo->keycode_high - xdo->keycode_low,
|
||||||
|
&keysyms_per_keycode);
|
||||||
|
|
||||||
|
/* Find a keycode that is unused for scratchspace */
|
||||||
|
for (i = xdo->keycode_low; i <= xdo->keycode_high; i++) {
|
||||||
|
int j = 0;
|
||||||
|
int key_is_empty = 1;
|
||||||
|
for (j = 0; j < keysyms_per_keycode; j++) {
|
||||||
|
/*char *symname;*/
|
||||||
|
int symindex = (i - xdo->keycode_low) * keysyms_per_keycode + j;
|
||||||
|
/*symname = XKeysymToString(keysyms[symindex]);*/
|
||||||
|
if (keysyms[symindex] != 0) {
|
||||||
|
key_is_empty = 0;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (key_is_empty) {
|
||||||
|
scratch_keycode = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XFree(keysyms);
|
||||||
|
|
||||||
|
/* Allow passing NULL for modifier in case we don't care about knowing
|
||||||
|
* the modifier map state after we finish */
|
||||||
|
if (modifier == NULL)
|
||||||
|
modifier = &modstate;
|
||||||
|
|
||||||
|
for (i = 0; i < nkeys; i++) {
|
||||||
|
if (keys[i].needs_binding == 1) {
|
||||||
|
KeySym keysym_list[] = { keys[i].symbol };
|
||||||
|
//_xdo_debug(xdo, "Mapping sym %lu to %d", keys[i].symbol, scratch_keycode);
|
||||||
|
XChangeKeyboardMapping(xdo->xdpy, scratch_keycode, 1, keysym_list, 1);
|
||||||
|
XSync(xdo->xdpy, False);
|
||||||
|
/* override the code in our current key to use the scratch_keycode */
|
||||||
|
keys[i].code = scratch_keycode;
|
||||||
|
keymapchanged = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
//fprintf(stderr, "keyseqlist_do: Sending %lc %s (%d, mods %x)\n",
|
||||||
|
//keys[i].key, (pressed ? "down" : "up"), keys[i].code, *modifier);
|
||||||
|
fast_send_key(xdo, window, &(keys[i]), *modifier, pressed, delay);
|
||||||
|
|
||||||
|
if (keys[i].needs_binding == 1) {
|
||||||
|
/* If we needed to make a new keymapping for this keystroke, we
|
||||||
|
* should sync with the server now, after the keypress, so that
|
||||||
|
* the next mapping or removal doesn't conflict. */
|
||||||
|
XSync(xdo->xdpy, False);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pressed) {
|
||||||
|
*modifier |= keys[i].modmask;
|
||||||
|
} else {
|
||||||
|
*modifier &= ~(keys[i].modmask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (keymapchanged) {
|
||||||
|
KeySym keysym_list[] = { 0 };
|
||||||
|
//printf(xdo, "Reverting scratch keycode (sym %lu to %d)",
|
||||||
|
// keys[i].symbol, scratch_keycode);
|
||||||
|
XChangeKeyboardMapping(xdo->xdpy, scratch_keycode, 1, keysym_list, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Necessary? */
|
||||||
|
XFlush(xdo->xdpy);
|
||||||
|
return XDO_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeySym fast_keysym_from_char(const xdo_t *xdo, wchar_t key) {
|
||||||
|
int i = 0;
|
||||||
|
int len = xdo->charcodes_len;
|
||||||
|
|
||||||
|
//printf("Finding symbol for key '%c'\n", key);
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
//printf(" => %c vs %c (%d)\n",
|
||||||
|
//key, xdo->charcodes[i].key, (xdo->charcodes[i].key == key));
|
||||||
|
if (xdo->charcodes[i].key == key) {
|
||||||
|
//printf(" => MATCH to symbol: %lu\n", xdo->charcodes[i].symbol);
|
||||||
|
return xdo->charcodes[i].symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key >= 0x100) key += 0x01000000;
|
||||||
|
if (XKeysymToString(key)) return key;
|
||||||
|
return NoSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fast_charcodemap_from_keysym(const xdo_t *xdo, charcodemap_t *key, KeySym keysym) {
|
||||||
|
int i = 0;
|
||||||
|
int len = xdo->charcodes_len;
|
||||||
|
|
||||||
|
key->code = 0;
|
||||||
|
key->symbol = keysym;
|
||||||
|
key->group = 0;
|
||||||
|
key->modmask = 0;
|
||||||
|
key->needs_binding = 1;
|
||||||
|
|
||||||
|
for (i = 0; i < len; i++) {
|
||||||
|
if (xdo->charcodes[i].symbol == keysym) {
|
||||||
|
key->code = xdo->charcodes[i].code;
|
||||||
|
key->group = xdo->charcodes[i].group;
|
||||||
|
key->modmask = xdo->charcodes[i].modmask;
|
||||||
|
key->needs_binding = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void fast_charcodemap_from_char(const xdo_t *xdo, charcodemap_t *key) {
|
||||||
|
KeySym keysym = fast_keysym_from_char(xdo, key->key);
|
||||||
|
fast_charcodemap_from_keysym(xdo, key, keysym);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* XXX: Return proper code if errors found */
|
||||||
|
int fast_enter_text_window(const xdo_t *xdo, Window window, const char *string, useconds_t delay) {
|
||||||
|
|
||||||
|
/* Since we're doing down/up, the delay should be based on the number
|
||||||
|
* of keys pressed (including shift). Since up/down is two calls,
|
||||||
|
* divide by two. */
|
||||||
|
delay /= 2;
|
||||||
|
|
||||||
|
/* XXX: Add error handling */
|
||||||
|
//int nkeys = strlen(string);
|
||||||
|
//charcodemap_t *keys = calloc(nkeys, sizeof(charcodemap_t));
|
||||||
|
charcodemap_t key;
|
||||||
|
//int modifier = 0;
|
||||||
|
setlocale(LC_CTYPE,"");
|
||||||
|
mbstate_t ps = { 0 };
|
||||||
|
ssize_t len;
|
||||||
|
while ( (len = mbsrtowcs(&key.key, &string, 1, &ps)) ) {
|
||||||
|
if (len == -1) {
|
||||||
|
fprintf(stderr, "Invalid multi-byte sequence encountered\n");
|
||||||
|
return XDO_ERROR;
|
||||||
|
}
|
||||||
|
fast_charcodemap_from_char(xdo, &key);
|
||||||
|
if (key.code == 0 && key.symbol == NoSymbol) {
|
||||||
|
fprintf(stderr, "I don't what key produces '%lc', skipping.\n",
|
||||||
|
key.key);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
//printf("Found key for %c\n", key.key);
|
||||||
|
//printf("code: %d\n", key.code);
|
||||||
|
//printf("sym: %s\n", XKeysymToString(key.symbol));
|
||||||
|
}
|
||||||
|
|
||||||
|
//printf(stderr,
|
||||||
|
//"Key '%c' maps to code %d / sym %lu in group %d / mods %d (%s)\n",
|
||||||
|
//key.key, key.code, key.symbol, key.group, key.modmask,
|
||||||
|
//(key.needs_binding == 1) ? "needs binding" : "ok");
|
||||||
|
|
||||||
|
//_xdo_send_key(xdo, window, keycode, modstate, True, delay);
|
||||||
|
//_xdo_send_key(xdo, window, keycode, modstate, False, delay);
|
||||||
|
fast_send_keysequence_window_list_do(xdo, window, &key, 1, True, NULL, delay / 2);
|
||||||
|
key.needs_binding = 0;
|
||||||
|
fast_send_keysequence_window_list_do(xdo, window, &key, 1, False, NULL, delay / 2);
|
||||||
|
|
||||||
|
/* XXX: Flush here or at the end? or never? */
|
||||||
|
//XFlush(xdo->xdpy);
|
||||||
|
} /* walk string generating a keysequence */
|
||||||
|
|
||||||
|
//free(keys);
|
||||||
|
return XDO_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fast_send_event(const xdo_t *xdo, Window window, int keycode, int pressed) {
|
||||||
|
XKeyEvent xk;
|
||||||
|
xk.display = xdo->xdpy;
|
||||||
|
xk.window = window;
|
||||||
|
xk.root = XDefaultRootWindow(xdo->xdpy);
|
||||||
|
xk.subwindow = None;
|
||||||
|
xk.time = CurrentTime;
|
||||||
|
xk.x = 1;
|
||||||
|
xk.y = 1;
|
||||||
|
xk.x_root = 1;
|
||||||
|
xk.y_root = 1;
|
||||||
|
xk.same_screen = True;
|
||||||
|
xk.keycode = keycode;
|
||||||
|
xk.state = 0;
|
||||||
|
xk.type = (pressed ? KeyPress : KeyRelease);
|
||||||
|
|
||||||
|
XEvent event;
|
||||||
|
event.xkey =xk;
|
||||||
|
|
||||||
|
XSendEvent(xdo->xdpy, window, True, 0, &event);
|
||||||
|
}
|
21
native/liblinuxbridge/fast_xdo.h
Normal file
21
native/liblinuxbridge/fast_xdo.h
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
//
|
||||||
|
// Most of this code has been taken from the wonderful XDOTOOL: https://github.com/jordansissel/xdotool/blob/master/COPYRIGHT
|
||||||
|
// and modified to use XSendEvent instead of XTestFakeKeyEvent.
|
||||||
|
|
||||||
|
#ifndef LIBLINUXBRIDGE_FAST_XDO_H
|
||||||
|
#define LIBLINUXBRIDGE_FAST_XDO_H
|
||||||
|
|
||||||
|
extern "C" { // Needed to avoid C++ compiler name mangling
|
||||||
|
#include <xdo.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
KeySym fast_keysym_from_char(const xdo_t *xdo, wchar_t key);
|
||||||
|
void fast_charcodemap_from_char(const xdo_t *xdo, charcodemap_t *key);
|
||||||
|
void fast_charcodemap_from_keysym(const xdo_t *xdo, charcodemap_t *key, KeySym keysym);
|
||||||
|
void fast_init_xkeyevent(const xdo_t *xdo, XKeyEvent *xk);
|
||||||
|
void fast_send_key(const xdo_t *xdo, Window window, charcodemap_t *key,
|
||||||
|
int modstate, int is_press, useconds_t delay);
|
||||||
|
int fast_enter_text_window(const xdo_t *xdo, Window window, const char *string, useconds_t delay);
|
||||||
|
void fast_send_event(const xdo_t *xdo, Window window, int keycode, int pressed);
|
||||||
|
|
||||||
|
#endif //LIBLINUXBRIDGE_FAST_XDO_H
|
|
@ -24,16 +24,18 @@
|
||||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
|
||||||
{
|
{
|
||||||
// Setup status icon
|
// Setup status icon
|
||||||
myStatusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
|
if (show_icon) {
|
||||||
|
myStatusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:NSSquareStatusItemLength] retain];
|
||||||
|
|
||||||
NSString *nsIconPath = [NSString stringWithUTF8String:icon_path];
|
NSString *nsIconPath = [NSString stringWithUTF8String:icon_path];
|
||||||
NSImage *statusImage = [[NSImage alloc] initWithContentsOfFile:nsIconPath];
|
NSImage *statusImage = [[NSImage alloc] initWithContentsOfFile:nsIconPath];
|
||||||
[statusImage setTemplate:YES];
|
[statusImage setTemplate:YES];
|
||||||
|
|
||||||
[myStatusItem.button setImage:statusImage];
|
[myStatusItem.button setImage:statusImage];
|
||||||
[myStatusItem setHighlightMode:YES];
|
[myStatusItem setHighlightMode:YES];
|
||||||
[myStatusItem.button setAction:@selector(statusIconClick:)];
|
[myStatusItem.button setAction:@selector(statusIconClick:)];
|
||||||
[myStatusItem.button setTarget:self];
|
[myStatusItem.button setTarget:self];
|
||||||
|
}
|
||||||
|
|
||||||
// Setup key listener
|
// Setup key listener
|
||||||
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSEventMaskKeyDown | NSEventMaskFlagsChanged | NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown)
|
[NSEvent addGlobalMonitorForEventsMatchingMask:(NSEventMaskKeyDown | NSEventMaskFlagsChanged | NSEventMaskLeftMouseDown | NSEventMaskRightMouseDown)
|
||||||
|
|
|
@ -26,11 +26,12 @@ extern "C" {
|
||||||
|
|
||||||
extern void * context_instance;
|
extern void * context_instance;
|
||||||
extern char * icon_path;
|
extern char * icon_path;
|
||||||
|
extern int32_t show_icon;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Initialize the AppDelegate and check for accessibility permissions
|
* Initialize the AppDelegate and check for accessibility permissions
|
||||||
*/
|
*/
|
||||||
int32_t initialize(void * context, const char * icon_path);
|
int32_t initialize(void * context, const char * icon_path, int32_t show_icon);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Start the event loop indefinitely. Blocking call.
|
* Start the event loop indefinitely. Blocking call.
|
||||||
|
|
|
@ -33,15 +33,17 @@ extern "C" {
|
||||||
|
|
||||||
void * context_instance;
|
void * context_instance;
|
||||||
char * icon_path;
|
char * icon_path;
|
||||||
|
int32_t show_icon;
|
||||||
AppDelegate * delegate_ptr;
|
AppDelegate * delegate_ptr;
|
||||||
|
|
||||||
KeypressCallback keypress_callback;
|
KeypressCallback keypress_callback;
|
||||||
IconClickCallback icon_click_callback;
|
IconClickCallback icon_click_callback;
|
||||||
ContextMenuClickCallback context_menu_click_callback;
|
ContextMenuClickCallback context_menu_click_callback;
|
||||||
|
|
||||||
int32_t initialize(void * context, const char * _icon_path) {
|
int32_t initialize(void * context, const char * _icon_path, int32_t _show_icon) {
|
||||||
context_instance = context;
|
context_instance = context;
|
||||||
icon_path = strdup(_icon_path);
|
icon_path = strdup(_icon_path);
|
||||||
|
show_icon = _show_icon;
|
||||||
|
|
||||||
AppDelegate *delegate = [[AppDelegate alloc] init];
|
AppDelegate *delegate = [[AppDelegate alloc] init];
|
||||||
delegate_ptr = delegate;
|
delegate_ptr = delegate;
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
const long refreshKeyboardLayoutInterval = 2000;
|
const long refreshKeyboardLayoutInterval = 2000;
|
||||||
|
|
||||||
void * manager_instance;
|
void * manager_instance;
|
||||||
|
int32_t show_icon;
|
||||||
|
|
||||||
// Keyboard listening
|
// Keyboard listening
|
||||||
|
|
||||||
|
@ -298,14 +299,17 @@ LRESULT CALLBACK window_procedure(HWND window, unsigned int msg, WPARAM wp, LPAR
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
if (msg == WM_TASKBARCREATED) { // Explorer crashed, recreate the icon
|
if (msg == WM_TASKBARCREATED) { // Explorer crashed, recreate the icon
|
||||||
Shell_NotifyIcon(NIM_ADD, &nid);
|
if (show_icon) {
|
||||||
|
Shell_NotifyIcon(NIM_ADD, &nid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return DefWindowProc(window, msg, wp, lp);
|
return DefWindowProc(window, msg, wp, lp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path) {
|
int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path, int32_t _show_icon) {
|
||||||
manager_instance = self;
|
manager_instance = self;
|
||||||
|
show_icon = _show_icon;
|
||||||
|
|
||||||
// Load the images
|
// Load the images
|
||||||
g_espanso_bmp = (HBITMAP)LoadImage(NULL, bmp_path, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
|
g_espanso_bmp = (HBITMAP)LoadImage(NULL, bmp_path, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
|
||||||
|
@ -440,7 +444,9 @@ int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path) {
|
||||||
StringCchCopy(nid.szTip, ARRAYSIZE(nid.szTip), L"espanso");
|
StringCchCopy(nid.szTip, ARRAYSIZE(nid.szTip), L"espanso");
|
||||||
|
|
||||||
// Show the notification.
|
// Show the notification.
|
||||||
Shell_NotifyIcon(NIM_ADD, &nid);
|
if (show_icon) {
|
||||||
|
Shell_NotifyIcon(NIM_ADD, &nid);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
// Something went wrong, error.
|
// Something went wrong, error.
|
||||||
|
|
|
@ -33,7 +33,7 @@ extern void * manager_instance;
|
||||||
* Initialize the Windows parameters
|
* Initialize the Windows parameters
|
||||||
* return: 1 if OK, -1 otherwise.
|
* return: 1 if OK, -1 otherwise.
|
||||||
*/
|
*/
|
||||||
extern "C" int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path);
|
extern "C" int32_t initialize(void * self, wchar_t * ico_path, wchar_t * bmp_path, int32_t show_icon);
|
||||||
|
|
||||||
#define LEFT_VARIANT 1
|
#define LEFT_VARIANT 1
|
||||||
#define RIGHT_VARIANT 2
|
#define RIGHT_VARIANT 2
|
||||||
|
|
|
@ -47,4 +47,9 @@ extern {
|
||||||
pub fn trigger_alt_shift_ins_paste();
|
pub fn trigger_alt_shift_ins_paste();
|
||||||
pub fn trigger_ctrl_alt_paste();
|
pub fn trigger_ctrl_alt_paste();
|
||||||
pub fn trigger_copy();
|
pub fn trigger_copy();
|
||||||
|
|
||||||
|
pub fn fast_send_string(string: *const c_char);
|
||||||
|
pub fn fast_delete_string(count: i32);
|
||||||
|
pub fn fast_left_arrow(count: i32);
|
||||||
|
pub fn fast_send_enter();
|
||||||
}
|
}
|
|
@ -29,7 +29,7 @@ pub struct MacMenuItem {
|
||||||
#[allow(improper_ctypes)]
|
#[allow(improper_ctypes)]
|
||||||
#[link(name="macbridge", kind="static")]
|
#[link(name="macbridge", kind="static")]
|
||||||
extern {
|
extern {
|
||||||
pub fn initialize(s: *const c_void, icon_path: *const c_char);
|
pub fn initialize(s: *const c_void, icon_path: *const c_char, show_icon: i32);
|
||||||
pub fn eventloop();
|
pub fn eventloop();
|
||||||
pub fn headless_eventloop();
|
pub fn headless_eventloop();
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub struct WindowsMenuItem {
|
||||||
#[link(name="winbridge", kind="static")]
|
#[link(name="winbridge", kind="static")]
|
||||||
extern {
|
extern {
|
||||||
pub fn start_daemon_process() -> i32;
|
pub fn start_daemon_process() -> i32;
|
||||||
pub fn initialize(s: *const c_void, ico_path: *const u16, bmp_path: *const u16) -> i32;
|
pub fn initialize(s: *const c_void, ico_path: *const u16, bmp_path: *const u16, show_icon: i32) -> 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;
|
||||||
|
|
|
@ -66,6 +66,9 @@ fn default_restore_clipboard_delay() -> i32 { 300 }
|
||||||
fn default_exclude_default_entries() -> bool {false}
|
fn default_exclude_default_entries() -> bool {false}
|
||||||
fn default_secure_input_watcher_enabled() -> bool {true}
|
fn default_secure_input_watcher_enabled() -> bool {true}
|
||||||
fn default_secure_input_notification() -> bool {true}
|
fn default_secure_input_notification() -> bool {true}
|
||||||
|
fn default_show_notifications() -> bool {true}
|
||||||
|
fn default_show_icon() -> bool {true}
|
||||||
|
fn default_fast_inject() -> bool {false}
|
||||||
fn default_secure_input_watcher_interval() -> i32 {5000}
|
fn default_secure_input_watcher_interval() -> i32 {5000}
|
||||||
fn default_matches() -> Vec<Match> { Vec::new() }
|
fn default_matches() -> Vec<Match> { Vec::new() }
|
||||||
fn default_global_vars() -> Vec<MatchVariable> { Vec::new() }
|
fn default_global_vars() -> Vec<MatchVariable> { Vec::new() }
|
||||||
|
@ -156,6 +159,15 @@ pub struct Configs {
|
||||||
#[serde(default = "default_exclude_default_entries")]
|
#[serde(default = "default_exclude_default_entries")]
|
||||||
pub exclude_default_entries: bool,
|
pub exclude_default_entries: bool,
|
||||||
|
|
||||||
|
#[serde(default = "default_show_notifications")]
|
||||||
|
pub show_notifications: bool,
|
||||||
|
|
||||||
|
#[serde(default = "default_show_icon")]
|
||||||
|
pub show_icon: bool,
|
||||||
|
|
||||||
|
#[serde(default = "default_fast_inject")]
|
||||||
|
pub fast_inject: bool,
|
||||||
|
|
||||||
#[serde(default = "default_matches")]
|
#[serde(default = "default_matches")]
|
||||||
pub matches: Vec<Match>,
|
pub matches: Vec<Match>,
|
||||||
|
|
||||||
|
@ -205,6 +217,8 @@ impl Configs {
|
||||||
validate_field!(result, self.secure_input_watcher_enabled, default_secure_input_watcher_enabled());
|
validate_field!(result, self.secure_input_watcher_enabled, default_secure_input_watcher_enabled());
|
||||||
validate_field!(result, self.secure_input_watcher_interval, default_secure_input_watcher_interval());
|
validate_field!(result, self.secure_input_watcher_interval, default_secure_input_watcher_interval());
|
||||||
validate_field!(result, self.secure_input_notification, default_secure_input_notification());
|
validate_field!(result, self.secure_input_notification, default_secure_input_notification());
|
||||||
|
validate_field!(result, self.show_notifications, default_show_notifications());
|
||||||
|
validate_field!(result, self.show_icon, default_show_icon());
|
||||||
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -243,7 +257,7 @@ impl Default for BackendType {
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
BackendType::Clipboard
|
BackendType::Auto
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,6 +281,7 @@ impl Configs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
|
eprintln!("Error: Cannot load file {:?}", path);
|
||||||
Err(ConfigLoadError::FileNotFound)
|
Err(ConfigLoadError::FileNotFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -377,6 +392,11 @@ impl ConfigSet {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Skip hidden files
|
||||||
|
if path.file_name().unwrap_or_default().to_str().unwrap_or_default().starts_with(".") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
let mut config = Configs::load_config(&path)?;
|
let mut config = Configs::load_config(&path)?;
|
||||||
|
|
||||||
// Make sure the config does not contain reserved fields
|
// Make sure the config does not contain reserved fields
|
||||||
|
@ -952,6 +972,32 @@ mod tests {
|
||||||
assert_eq!(config_set.specific.len(), 0);
|
assert_eq!(config_set.specific.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hidden_files_are_ignored() {
|
||||||
|
let (data_dir, package_dir) = create_temp_espanso_directories_with_default_content(
|
||||||
|
r###"
|
||||||
|
matches:
|
||||||
|
- trigger: ":lol"
|
||||||
|
replace: "LOL"
|
||||||
|
- trigger: ":yess"
|
||||||
|
replace: "Bob"
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
|
||||||
|
create_user_config_file(data_dir.path(), ".specific.yml", r###"
|
||||||
|
name: specific1
|
||||||
|
|
||||||
|
exclude_default_entries: true
|
||||||
|
|
||||||
|
matches:
|
||||||
|
- trigger: "hello"
|
||||||
|
replace: "newstring"
|
||||||
|
"###);
|
||||||
|
|
||||||
|
let config_set = ConfigSet::load(data_dir.path(), package_dir.path()).unwrap();
|
||||||
|
assert_eq!(config_set.specific.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_config_set_no_parent_configs_works_correctly() {
|
fn test_config_set_no_parent_configs_works_correctly() {
|
||||||
let (data_dir, package_dir) = create_temp_espanso_directories();
|
let (data_dir, package_dir) = create_temp_espanso_directories();
|
||||||
|
|
|
@ -83,7 +83,13 @@ impl MacContext {
|
||||||
register_context_menu_click_callback(context_menu_click_callback);
|
register_context_menu_click_callback(context_menu_click_callback);
|
||||||
|
|
||||||
let status_icon_path = CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default();
|
let status_icon_path = CString::new(status_icon_target.to_str().unwrap_or_default()).unwrap_or_default();
|
||||||
initialize(context_ptr, status_icon_path.as_ptr());
|
let show_icon = if config.show_icon {
|
||||||
|
1
|
||||||
|
}else{
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
initialize(context_ptr, status_icon_path.as_ptr(), show_icon);
|
||||||
}
|
}
|
||||||
|
|
||||||
context
|
context
|
||||||
|
|
|
@ -87,8 +87,14 @@ impl WindowsContext {
|
||||||
let ico_file_c = U16CString::from_str(ico_icon).unwrap();
|
let ico_file_c = U16CString::from_str(ico_icon).unwrap();
|
||||||
let bmp_file_c = U16CString::from_str(bmp_icon).unwrap();
|
let bmp_file_c = U16CString::from_str(bmp_icon).unwrap();
|
||||||
|
|
||||||
|
let show_icon = if config.show_icon {
|
||||||
|
1
|
||||||
|
}else{
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize the windows
|
// Initialize the windows
|
||||||
let res = initialize(context_ptr, ico_file_c.as_ptr(), bmp_file_c.as_ptr());
|
let res = initialize(context_ptr, ico_file_c.as_ptr(), bmp_file_c.as_ptr(), show_icon);
|
||||||
if res != 1 {
|
if res != 1 {
|
||||||
panic!("Can't initialize Windows context")
|
panic!("Can't initialize Windows context")
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
m.triggers[trigger_offset].chars().count() as i32 + 1 // Count also the separator
|
m.triggers[trigger_offset].chars().count() as i32 + 1 // Count also the separator
|
||||||
};
|
};
|
||||||
|
|
||||||
self.keyboard_manager.delete_string(char_count);
|
self.keyboard_manager.delete_string(&config, char_count);
|
||||||
|
|
||||||
let mut previous_clipboard_content : Option<String> = None;
|
let mut previous_clipboard_content : Option<String> = None;
|
||||||
|
|
||||||
|
@ -194,10 +194,10 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
|
|
||||||
for (i, split) in splits.enumerate() {
|
for (i, split) in splits.enumerate() {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
self.keyboard_manager.send_enter();
|
self.keyboard_manager.send_enter(&config);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.keyboard_manager.send_string(split);
|
self.keyboard_manager.send_string(&config, split);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
BackendType::Clipboard => {
|
BackendType::Clipboard => {
|
||||||
|
@ -206,7 +206,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
previous_clipboard_content = self.return_content_if_preserve_clipboard_is_enabled();
|
previous_clipboard_content = self.return_content_if_preserve_clipboard_is_enabled();
|
||||||
|
|
||||||
self.clipboard_manager.set_clipboard(&target_string);
|
self.clipboard_manager.set_clipboard(&target_string);
|
||||||
self.keyboard_manager.trigger_paste(&config.paste_shortcut);
|
self.keyboard_manager.trigger_paste(&config);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
error!("Unsupported backend type evaluation.");
|
error!("Unsupported backend type evaluation.");
|
||||||
|
@ -216,7 +216,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
|
|
||||||
if let Some(moves) = cursor_rewind {
|
if let Some(moves) = cursor_rewind {
|
||||||
// Simulate left arrow key presses to bring the cursor into the desired position
|
// Simulate left arrow key presses to bring the cursor into the desired position
|
||||||
self.keyboard_manager.move_cursor_left(moves);
|
self.keyboard_manager.move_cursor_left(&config, moves);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RenderResult::Image(image_path) => {
|
RenderResult::Image(image_path) => {
|
||||||
|
@ -225,7 +225,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
previous_clipboard_content = self.return_content_if_preserve_clipboard_is_enabled();
|
previous_clipboard_content = self.return_content_if_preserve_clipboard_is_enabled();
|
||||||
|
|
||||||
self.clipboard_manager.set_clipboard_image(&image_path);
|
self.clipboard_manager.set_clipboard_image(&image_path);
|
||||||
self.keyboard_manager.trigger_paste(&config.paste_shortcut);
|
self.keyboard_manager.trigger_paste(&config);
|
||||||
},
|
},
|
||||||
RenderResult::Error => {
|
RenderResult::Error => {
|
||||||
error!("Could not render match: {}", m.triggers[trigger_offset]);
|
error!("Could not render match: {}", m.triggers[trigger_offset]);
|
||||||
|
@ -257,7 +257,11 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
let mut enabled_ref = self.enabled.borrow_mut();
|
let mut enabled_ref = self.enabled.borrow_mut();
|
||||||
*enabled_ref = status;
|
*enabled_ref = status;
|
||||||
|
|
||||||
self.ui_manager.notify(message);
|
let config = self.config_manager.default_config();
|
||||||
|
|
||||||
|
if config.show_notifications {
|
||||||
|
self.ui_manager.notify(message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_passive(&self) {
|
fn on_passive(&self) {
|
||||||
|
@ -279,7 +283,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
||||||
|
|
||||||
// Trigger a copy shortcut to transfer the content of the selection to the clipboard
|
// Trigger a copy shortcut to transfer the content of the selection to the clipboard
|
||||||
self.keyboard_manager.trigger_copy();
|
self.keyboard_manager.trigger_copy(&config);
|
||||||
|
|
||||||
// Sleep for a while, giving time to effectively copy the text
|
// Sleep for a while, giving time to effectively copy the text
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
||||||
|
@ -308,7 +312,7 @@ impl <'a, S: KeyboardManager, C: ClipboardManager, M: ConfigManager<'a>, U: UIMa
|
||||||
self.clipboard_manager.set_clipboard(&payload);
|
self.clipboard_manager.set_clipboard(&payload);
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
std::thread::sleep(std::time::Duration::from_millis(100)); // TODO: avoid hardcoding
|
||||||
self.keyboard_manager.trigger_paste(&config.paste_shortcut);
|
self.keyboard_manager.trigger_paste(&config);
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
warn!("Cannot expand passive match")
|
warn!("Cannot expand passive match")
|
||||||
|
@ -351,7 +355,8 @@ impl <'a, S: KeyboardManager, C: ClipboardManager,
|
||||||
SystemEvent::SecureInputEnabled(app_name, path) => {
|
SystemEvent::SecureInputEnabled(app_name, path) => {
|
||||||
info!("SecureInput has been acquired by {}, preventing espanso from working correctly. Full path: {}", app_name, path);
|
info!("SecureInput has been acquired by {}, preventing espanso from working correctly. Full path: {}", app_name, path);
|
||||||
|
|
||||||
if self.config_manager.default_config().secure_input_notification {
|
let config = self.config_manager.default_config();
|
||||||
|
if config.secure_input_notification && config.show_notifications {
|
||||||
self.ui_manager.notify_delay(&format!("{} has activated SecureInput. Espanso won't work until you disable it.", app_name), 5000);
|
self.ui_manager.notify_delay(&format!("{} has activated SecureInput. Espanso won't work until you disable it.", app_name), 5000);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,28 +21,39 @@ use std::ffi::CString;
|
||||||
use crate::bridge::linux::*;
|
use crate::bridge::linux::*;
|
||||||
use super::PasteShortcut;
|
use super::PasteShortcut;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
use crate::config::Configs;
|
||||||
|
|
||||||
pub struct LinuxKeyboardManager {
|
pub struct LinuxKeyboardManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::KeyboardManager for LinuxKeyboardManager {
|
impl super::KeyboardManager for LinuxKeyboardManager {
|
||||||
fn send_string(&self, s: &str) {
|
fn send_string(&self, active_config: &Configs, s: &str) {
|
||||||
let res = CString::new(s);
|
let res = CString::new(s);
|
||||||
match res {
|
match res {
|
||||||
Ok(cstr) => unsafe { send_string(cstr.as_ptr()); }
|
Ok(cstr) => unsafe {
|
||||||
|
if active_config.fast_inject {
|
||||||
|
fast_send_string(cstr.as_ptr());
|
||||||
|
}else{
|
||||||
|
send_string(cstr.as_ptr());
|
||||||
|
}
|
||||||
|
}
|
||||||
Err(e) => panic!(e.to_string())
|
Err(e) => panic!(e.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_enter(&self) {
|
fn send_enter(&self, active_config: &Configs) {
|
||||||
unsafe {
|
unsafe {
|
||||||
send_enter();
|
if active_config.fast_inject {
|
||||||
|
fast_send_enter();
|
||||||
|
}else{
|
||||||
|
send_enter();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_paste(&self, shortcut: &PasteShortcut) {
|
fn trigger_paste(&self, active_config: &Configs) {
|
||||||
unsafe {
|
unsafe {
|
||||||
match shortcut {
|
match active_config.paste_shortcut {
|
||||||
PasteShortcut::Default => {
|
PasteShortcut::Default => {
|
||||||
let is_special = is_current_window_special();
|
let is_special = is_current_window_special();
|
||||||
|
|
||||||
|
@ -79,17 +90,27 @@ impl super::KeyboardManager for LinuxKeyboardManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_string(&self, count: i32) {
|
fn delete_string(&self, active_config: &Configs, count: i32) {
|
||||||
unsafe {delete_string(count)}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn move_cursor_left(&self, count: i32) {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
left_arrow(count);
|
if active_config.fast_inject {
|
||||||
|
fast_delete_string(count);
|
||||||
|
}else{
|
||||||
|
delete_string(count)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_copy(&self) {
|
fn move_cursor_left(&self, active_config: &Configs, count: i32) {
|
||||||
|
unsafe {
|
||||||
|
if active_config.fast_inject {
|
||||||
|
fast_left_arrow(count);
|
||||||
|
}else{
|
||||||
|
left_arrow(count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trigger_copy(&self, _: &Configs) {
|
||||||
unsafe {
|
unsafe {
|
||||||
trigger_copy();
|
trigger_copy();
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,12 +21,13 @@ use std::ffi::CString;
|
||||||
use crate::bridge::macos::*;
|
use crate::bridge::macos::*;
|
||||||
use super::PasteShortcut;
|
use super::PasteShortcut;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
use crate::config::Configs;
|
||||||
|
|
||||||
pub struct MacKeyboardManager {
|
pub struct MacKeyboardManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::KeyboardManager for MacKeyboardManager {
|
impl super::KeyboardManager for MacKeyboardManager {
|
||||||
fn send_string(&self, s: &str) {
|
fn send_string(&self, _: &Configs, s: &str) {
|
||||||
let res = CString::new(s);
|
let res = CString::new(s);
|
||||||
match res {
|
match res {
|
||||||
Ok(cstr) => unsafe { send_string(cstr.as_ptr()); }
|
Ok(cstr) => unsafe { send_string(cstr.as_ptr()); }
|
||||||
|
@ -34,16 +35,16 @@ impl super::KeyboardManager for MacKeyboardManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_enter(&self) {
|
fn send_enter(&self, _: &Configs) {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Send the kVK_Return key press
|
// Send the kVK_Return key press
|
||||||
send_vkey(0x24);
|
send_vkey(0x24);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_paste(&self, shortcut: &PasteShortcut) {
|
fn trigger_paste(&self, active_config: &Configs) {
|
||||||
unsafe {
|
unsafe {
|
||||||
match shortcut {
|
match active_config.paste_shortcut {
|
||||||
PasteShortcut::Default => {
|
PasteShortcut::Default => {
|
||||||
unsafe {
|
unsafe {
|
||||||
trigger_paste();
|
trigger_paste();
|
||||||
|
@ -56,17 +57,17 @@ impl super::KeyboardManager for MacKeyboardManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_copy(&self) {
|
fn trigger_copy(&self, _: &Configs) {
|
||||||
unsafe {
|
unsafe {
|
||||||
trigger_copy();
|
trigger_copy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_string(&self, count: i32) {
|
fn delete_string(&self, _: &Configs, count: i32) {
|
||||||
unsafe {delete_string(count)}
|
unsafe {delete_string(count)}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_cursor_left(&self, count: i32) {
|
fn move_cursor_left(&self, _: &Configs, count: i32) {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Simulate the Left arrow count times
|
// Simulate the Left arrow count times
|
||||||
send_multi_vkey(0x7B, count);
|
send_multi_vkey(0x7B, count);
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
use crate::config::Configs;
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
mod windows;
|
mod windows;
|
||||||
|
@ -29,12 +30,12 @@ mod linux;
|
||||||
mod macos;
|
mod macos;
|
||||||
|
|
||||||
pub trait KeyboardManager {
|
pub trait KeyboardManager {
|
||||||
fn send_string(&self, s: &str);
|
fn send_string(&self, active_config: &Configs, s: &str);
|
||||||
fn send_enter(&self);
|
fn send_enter(&self, active_config: &Configs);
|
||||||
fn trigger_paste(&self, shortcut: &PasteShortcut);
|
fn trigger_paste(&self, active_config: &Configs);
|
||||||
fn delete_string(&self, count: i32);
|
fn delete_string(&self, active_config: &Configs, count: i32);
|
||||||
fn move_cursor_left(&self, count: i32);
|
fn move_cursor_left(&self, active_config: &Configs, count: i32);
|
||||||
fn trigger_copy(&self);
|
fn trigger_copy(&self, active_config: &Configs);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
|
|
|
@ -21,12 +21,13 @@ use widestring::{U16CString};
|
||||||
use crate::bridge::windows::*;
|
use crate::bridge::windows::*;
|
||||||
use super::PasteShortcut;
|
use super::PasteShortcut;
|
||||||
use log::error;
|
use log::error;
|
||||||
|
use crate::config::Configs;
|
||||||
|
|
||||||
pub struct WindowsKeyboardManager {
|
pub struct WindowsKeyboardManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl super::KeyboardManager for WindowsKeyboardManager {
|
impl super::KeyboardManager for WindowsKeyboardManager {
|
||||||
fn send_string(&self, s: &str) {
|
fn send_string(&self, _: &Configs, s: &str) {
|
||||||
let res = U16CString::from_str(s);
|
let res = U16CString::from_str(s);
|
||||||
match res {
|
match res {
|
||||||
Ok(s) => {
|
Ok(s) => {
|
||||||
|
@ -39,16 +40,16 @@ impl super::KeyboardManager for WindowsKeyboardManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_enter(&self) {
|
fn send_enter(&self, _: &Configs) {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Send the VK_RETURN key press
|
// Send the VK_RETURN key press
|
||||||
send_vkey(0x0D);
|
send_vkey(0x0D);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_paste(&self, shortcut: &PasteShortcut) {
|
fn trigger_paste(&self, active_config: &Configs) {
|
||||||
unsafe {
|
unsafe {
|
||||||
match shortcut {
|
match active_config.paste_shortcut {
|
||||||
PasteShortcut::Default => {
|
PasteShortcut::Default => {
|
||||||
unsafe {
|
unsafe {
|
||||||
trigger_paste();
|
trigger_paste();
|
||||||
|
@ -61,20 +62,20 @@ impl super::KeyboardManager for WindowsKeyboardManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_string(&self, count: i32) {
|
fn delete_string(&self, _: &Configs, count: i32) {
|
||||||
unsafe {
|
unsafe {
|
||||||
delete_string(count)
|
delete_string(count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn move_cursor_left(&self, count: i32) {
|
fn move_cursor_left(&self, _: &Configs, count: i32) {
|
||||||
unsafe {
|
unsafe {
|
||||||
// Send the left arrow key multiple times
|
// Send the left arrow key multiple times
|
||||||
send_multi_vkey(0x25, count)
|
send_multi_vkey(0x25, count)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn trigger_copy(&self) {
|
fn trigger_copy(&self, _: &Configs) {
|
||||||
unsafe {
|
unsafe {
|
||||||
trigger_copy();
|
trigger_copy();
|
||||||
}
|
}
|
||||||
|
|
|
@ -350,7 +350,9 @@ fn daemon_background(receive_channel: Receiver<Event>, config_set: ConfigSet, is
|
||||||
let config_manager = RuntimeConfigManager::new(config_set, system_manager);
|
let config_manager = RuntimeConfigManager::new(config_set, system_manager);
|
||||||
|
|
||||||
let ui_manager = ui::get_uimanager();
|
let ui_manager = ui::get_uimanager();
|
||||||
ui_manager.notify("espanso is running!");
|
if config_manager.default_config().show_notifications {
|
||||||
|
ui_manager.notify("espanso is running!");
|
||||||
|
}
|
||||||
|
|
||||||
let clipboard_manager = clipboard::get_manager();
|
let clipboard_manager = clipboard::get_manager();
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user