App changes text discovered by other applications
Hello,
First, big thanks for your work and this application. I was looking for tiling windows manager which is natural for me and I found :D
My environment:
- Python 3.13
- master source code
My configuration:
- Only hotkeys
After launching application instant text replacement implemented via AutoHotKeys application stopped working. To discover issue I used following python code:
# text_replacer.py
from pynput.keyboard import Key, Listener
import pyautogui
replacements = {"#teststring": "Add new comment"}
def on_press(key):
global typed_keys
global listening
key_str = str(key).replace("'", "")
print(key_str)
if key_str == macro_starter:
typed_keys = []
listening = True
if listening:
if key_str.isalpha():
typed_keys.append(key_str)
if key == macro_ender:
candidate_keyword = ""
candidate_keyword = candidate_keyword.join(typed_keys)
if candidate_keyword != "":
if candidate_keyword in replacements.keys():
pyautogui.press("backspace", presses=len(candidate_keyword) + 2)
pyautogui.typewrite(replacements[candidate_keyword])
listening = False
macro_starter = "#"
macro_ender = Key.space
listening = True
typed_keys = []
with Listener(on_press=on_press) as listener:
listener.join()
Testing code received string ##tteessttssttrriinngg instead of #teststring. I changed my AutoHotKeys script to repeat twice all letters and is working again. Looks like your application changing input for applications like AutoHotKeys.
Could you look into this issue or at least elaborate how debug this issue? I know python and I can help but I'm still learning your project.
Best Regards
I have also issue to type ~ char when application is running. Probably root cause is the same.
I enabled DEBUG level and single key generates following logs:
2025-01-29 14:34:09,070 [jigsawwm.jmk.sysinout] [ThreadPoolEx] [DEBUG] sys >>> JmkEvent(S, down, sys, 0, 0)
2025-01-29 14:34:09,070 [jigsawwm.jmk.hotkey] [ThreadPoolEx] [DEBUG] JmkEvent(S, down, sys, 0, 0) >>> hotkey
2025-01-29 14:34:09,071 [jigsawwm.jmk.hotkey] [ThreadPoolEx] [DEBUG] current pressed keys: {<Vk.S: 83>}
2025-01-29 14:34:09,071 [jigsawwm.jmk.sysinout] [ThreadPoolEx] [DEBUG] JmkEvent(S, down, sys, 0, 0) >>> sys
2025-01-29 14:34:09,071 [jigsawwm.jmk.sysinout] [MainThread ] [DEBUG] synthesized event KBDLLHOOKDATA(vkCode=83, scanCode=31, flags=16, time=2336237125, dwExtraInfo=272760864), skipping
2025-01-29 14:34:09,144 [jigsawwm.jmk.sysinout] [ThreadPoolEx] [DEBUG] sys >>> JmkEvent(S, up, sys, 128, 0)
2025-01-29 14:34:09,144 [jigsawwm.jmk.hotkey] [ThreadPoolEx] [DEBUG] JmkEvent(S, up, sys, 128, 0) >>> hotkey
2025-01-29 14:34:09,145 [jigsawwm.jmk.hotkey] [ThreadPoolEx] [DEBUG] current pressed keys: {<Vk.S: 83>}
2025-01-29 14:34:09,145 [jigsawwm.jmk.sysinout] [ThreadPoolEx] [DEBUG] JmkEvent(S, up, sys, 128, 0) >>> sys
2025-01-29 14:34:09,145 [jigsawwm.jmk.sysinout] [MainThread ] [DEBUG] synthesized event KBDLLHOOKDATA(vkCode=83, scanCode=31, flags=144, time=2336237187, dwExtraInfo=272760864), skipping
Looks like following code in jmk/sysinout.py fixed the issue of duplicate events:
def input_event(
self,
_code: int,
msgid: Union[hook.KBDLLHOOKMSGID, hook.MSLLHOOKMSGID],
msg: Union[hook.KBDLLHOOKDATA, hook.MSLLHOOKDATA],
) -> bool:
"""Handles keyboard events and call callback if the combination
had been registered
"""
if self.is_running is False:
return False
if is_synthesized(msg):
logger.info("synthesized event %s, skipping", msg)
return False
# Check for duplicate events using dwExtraInfo
if hasattr(self, 'last_dwExtraInfo') and self.last_dwExtraInfo == msg.dwExtraInfo:
logger.info("duplicate event %s, skipping", msg)
return False
self.last_dwExtraInfo = msg.dwExtraInfo
# convert keyboard/mouse event to a unified virtual key representation
vkey, pressed = None, None
if isinstance(msgid, hook.KBDLLHOOKMSGID):
vkey = Vk(msg.vkCode)
if vkey == Vk.PACKET:
return False
if msgid == hook.KBDLLHOOKMSGID.WM_KEYDOWN:
pressed = True
elif msgid == hook.KBDLLHOOKMSGID.WM_KEYUP:
pressed = False
else:
return False
elif isinstance(msgid, hook.MSLLHOOKMSGID):
if msgid == hook.MSLLHOOKMSGID.WM_LBUTTONDOWN:
vkey = Vk.LBUTTON
pressed = True
elif msgid == hook.MSLLHOOKMSGID.WM_LBUTTONUP:
vkey = Vk.LBUTTON
pressed = False
elif msgid == hook.MSLLHOOKMSGID.WM_RBUTTONDOWN:
vkey = Vk.RBUTTON
pressed = True
elif msgid == hook.MSLLHOOKMSGID.WM_RBUTTONUP:
vkey = Vk.RBUTTON
pressed = False
elif msgid == hook.MSLLHOOKMSGID.WM_MBUTTONDOWN:
vkey = Vk.MBUTTON
pressed = True
elif msgid == hook.MSLLHOOKMSGID.WM_MBUTTONUP:
vkey = Vk.MBUTTON
pressed = False
elif msgid == hook.MSLLHOOKMSGID.WM_XBUTTONDOWN:
vkey = Vk.XBUTTON1 if msg.hiword() == 1 else Vk.XBUTTON2
pressed = True
elif msgid == hook.MSLLHOOKMSGID.WM_XBUTTONUP:
vkey = Vk.XBUTTON1 if msg.hiword() == 1 else Vk.XBUTTON2
pressed = False
elif msgid == hook.MSLLHOOKMSGID.WM_MOUSEWHEEL:
delta = msg.get_wheel_delta()
if delta > 0:
vkey = Vk.WHEEL_UP
else:
vkey = Vk.WHEEL_DOWN
pressed = False
# skip events that out of our interest
if vkey is None or pressed is None:
return False
self.enqueue(self.on_input, vkey, pressed, msg.flags, msg.dwExtraInfo)
if self.disabled:
logger.debug("disabled due to %s, skipping %s", self.disabled_reason, msg)
return False
return True
but I see some problems when AutoHotKeys would like to enter some text. Text is incorrectly generated.
I designed the jmk module with replacing AHK in mind. They are not supposed to be running at the same time. What is your use case for AHK?
I have two cases:
- Control media player via keyboard shortcuts
- Automatic text completion/replacement
Thanks for replying. Can you elaborate how do you utilize AHK to address your use cases?
- It is hotkey remapping?
- How does text completion/replacement behave? Like, when you type "foo" it would replace it with "bar" by sending three backspaces and
bar?
- Looks like remapping. I did it like so:
^+PgDn::Media_Prev
^+PgUp::Media_Next
^+space::Media_Play_Pause
- I'm not sure but probably you are right. When JigsawWM is working not whole text is deleted and new text is wrongly formatted if text is long. This is example how to do that in AHK:
:*T:#test1::
{
dts := FormatTime(, "yyyy.MM.dd")
text := "*" . dts . ": Daily note:*`n" . "* First point as example"
SendInput(text)
}
or
:*T:#test2::
(
line 1
-
line 2
-
line 3
-
line 4
)
@pchomik Thanks for the explanation!
-
Hotkey Remapping:
Thejmktool supports hotkey remapping. You can find an example here: GitHub link. -
Text Replacement (Workaround):
While text replacement isn’t directly supported, I achieved a similar functionality using layered keys:- Configure the
tabkey to activatelayer 1when held down. GitHub link - Assign text-sending functions to specific keys within that layer. Example: GitHub link.
-
Usage:
- Hold the
tabkey and presstto insert today’s date. - Hold
taband press another key (e.g.,time) to insert the current time, etc.
- Hold the
- Configure the