boppreh / keyboard

Hook and simulate global keyboard events on Windows and Linux.
MIT License
3.81k stars 434 forks source link

Windows support #4

Closed fpp-gh closed 8 years ago

fpp-gh commented 8 years ago

Hi,

This pure-python, cross-platform lib is quite impressive. It could help a lot on workstations where adding stuff is severely restricted (ie no pip, etc.).

One thing I'm looking for is to reproduce (at least partly) the abbreviation expansion functionality found in Autohotkey (among others). This replaces text patterns as you type, without hotkeys (ie., type 'tm" followed by space and its gets replaced by a trademark symbol for example).

I have not been able to do this using the add_hotkey method (which is probably normal). Could there be a way to achieve it using the lower-level functions ?

TIA, fp

DolajoCZ commented 8 years ago

Hello boppreh, first I would like to thank you for this useful module. Unfortunately, I ran into a problem. I'm trying to make a program which will do Ctrl+a (select all) and the Ctrl+c. If I select all manually and then execute program (only with Ctrl+c) everything is OK, but with Ctrl+a and then Ctrl+c nothing will happen (without error but also without data in clipboard). I use W10 and P3.5.1 . Can you please advise me where I'm wrong ? There is a simple test program. Many thanks `import keyboard as kb from tkinter import *

def Test(): print("Before") kb.send("ctrl+a") kb.send("ctrl+c") print("After") return()

root=Tk() kb.add_hotkey("shift", Test) root.mainloop()`

boppreh commented 8 years ago

Checking now.

boppreh commented 8 years ago

I think I found the problem. You are sending ctrl+a and ctrl+c while shift is being held. In my computer you can hear an error beep. Solutions:

Changing the behavior of send to automatically stash state may or may note make sense, I would have to think more about it. Until then you can always wrap the function to force this behavior:

import keyboard as kb
def my_send(key):
    kb.stash_state()
    kb.send(key)

my_send('ctrl+a')

Now you have a my_send function that ensures the keyboard state is clean before sending the keys.

Edit: another option is to add a flag that makes hotkeys trigger on key release instead of key press. Any opinions on the matter would be appreciated.

DolajoCZ commented 8 years ago

Thanks so much boppreh for the quick response, now it works as I need.

fpp-gh commented 8 years ago

Sorry for not reporting lately, there was a Bank holiday over here :-) But I have some very good news for a change !

Today at lunch break I updated the keyboard lib and my test files to the latest versions on the "cursed target machine" (the locked-down W7 machine with Py33).

Aaaand... it did not blow up on me all the time I ran tests on it !!! :-) I loaded several different sets of abbreviations and never gave an error, it even was almost snappy...

One big caveat however is that in the latest version of the lib (and maybe the previous) the behaviour is different between Python 2.7 and Python 3.3 runtimes... I tested side-by-side on the W10/Py27 laptop and the W7/Py33 workstation, with exact same version of keyboard and test files.

With Py27, everything works as expected.

With Py33, everything mostly works, except that:

Can you reproduce this ? Or is the cursed machine that special ? :-)

All in all, very good progress anyway !

boppreh commented 8 years ago

Thanks for the feedback, I'll try to reproduce it when I get home.

boppreh commented 8 years ago

I couldn't reproduce the issues on Win10/Py35 or Win10/Py27. And those don't sound like the usual differences between Py2 and Py3. Just to be sure I've added tests for the cases you mentioned.

I'll try to get a Python3 version installed later today and see if that's the problem.

C:\Users\BoppreH\Desktop\source\keyboard>C:\Python27\python.exe
Python 2.7.12 (v2.7.12:d33e0cf91556, Jun 27 2016, 15:24:40) [MSC v.1500 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from keyboard import *
>>> add_abbreviation('tm', 'trademark')
<function handler at 0x0000000002A0BBA8>
>>> add_abbreviation('TM', 'TRADEMARK')
<function handler at 0x0000000002A0BCF8>
>>> # trademark TRADEMARK
...
>>> add_abbreviation('123', '123456789')
<function handler at 0x0000000002A0BDD8>
>>> # 123456789
...
>>> add_abbreviation('newline', '\n')
<function handler at 0x0000000002A0BEB8>
>>> #
...
>>>
^C
C:\Users\BoppreH\Desktop\source\keyboard>python
Python 3.5.2 (v3.5.2:4def2a2901a5, Jun 25 2016, 22:18:55) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from keyboard import *
>>> add_abbreviation('tm', 'trademark')
<function add_word_listener.<locals>.handler at 0x000002B8DD49F620>
>>> add_abbreviation('TM', 'TRADEMARK')
<function add_word_listener.<locals>.handler at 0x000002B8DD49F7B8>
>>> # trademark TRADEMARK
...
>>> add_abbreviation('123', '123456789')
<function add_word_listener.<locals>.handler at 0x000002B8DD49F8C8>
>>> # 123456789
...
>>> add_abbreviation('newline', '\n')
<function add_word_listener.<locals>.handler at 0x000002B8DD49F9D8>
>>> #
...
>>>
>>>
boppreh commented 8 years ago

Tried with Python3.3 on Windows 10, now 32 bits, and still no luck reproducing the issue. Are you sure it's not a bug on your end?

C:\Users\BoppreH\Desktop\source\keyboard>C:\Python33\python.exe
Python 3.3.5 (v3.3.5:62cf4e77f785, Mar  9 2014, 10:37:12) [MSC v.1600 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from keyboard import *
>>> add_abbreviation('tm', 'trademark')
<function add_word_listener.<locals>.handler at 0x02CF6A08>
>>> add_abbreviation('TM', 'TRADEMARK')
<function add_word_listener.<locals>.handler at 0x02CF6AE0>
>>> # trademark TRADEMARK
...
>>> add_abbreviation('123', '123456789')
<function add_word_listener.<locals>.handler at 0x02CF6B70>
>>> # 123456789
...
>>> add_abbreviation('newline', '\n')
<function add_word_listener.<locals>.handler at 0x02CF6C00>
>>> #
...
>>>
fpp-gh commented 8 years ago

That is never sure :-) I'll triple-check, thanks for looking into this.

fpp-gh commented 8 years ago

Well I just spent a while one this, and I'm actually sorry to say I can't see where I might have gone wrong... Here is the test sample:

# coding: utf-8
import keyboard as kb
aab = kb.add_abbreviation

abs = [
("r1l", "@&é\"\'(-è_çà)="),
("r1u", "#1234567890°+"),
("r2l", "azeryuiop$"),
("r2u", "AZERTYUIOP€"),
("r3l", "qsdfghjklm\\*"),
("r3u", "QSDFGHJKLM%ù"),
("r4l", "<wxcvbn,.:!"),
("r4u", ">WXCVBN?;/§"),
("circ","âê$u$i$o ÂÊÛÎÔ"),
("trem","äëÿüïö Ä˨YÜÏÖ"),
("ààà", "rhâoui!"),
("fpp", "that's me"),
("FPP", "THAT'S ME")
]
for a in abs : aab(*a)
input("type any key")  # raw_input for Py2 testing

I tried two things:

  1. the Py2 version has u'' prefixes for the strings, and last time I ran it as is in Py3, so I removed them
  2. Originally the non-ascii chars were input in the Windows code page (cp1552), which was also the coding declaration for the python script. I changed it to utf-8 and re-input all the chars.

Unfortunately the results are much the same as previously: works 100% on Py2, works well enough on Py33 but with the same regressions I mentioned earlier.

I suspect a buggy and/or incomplete Py33 distro on the cursed machine (it doesn't have tkinter for example). Do you see anything else I could try in my code ?

boppreh commented 8 years ago

Sorry, all hotkeys worked on the three version I have here (2.7, 3.3, 3.5).

You can do keyboard.hook(print) to print all events and check if there's anything funny going on, but I'm afraid I can't be of much help here.

fpp-gh commented 8 years ago

Thanks for the confirmation. I guess I'll just have to make do with what I'm burdened with :-) However, even with case-less and digit-less keywords, and no multi-line expansions, your lib can still be very useful and make life less painful on that machine, so it was not all in vain, far from it!

I haven't tried my hand at hotkeys (launchers) yet, but if they work OK, it certainly will have earned its keep :-) I also plan to try the mouse part on some other 'clickety-click' irritants. Will keep you posted...

boppreh commented 8 years ago

Sorry about that. Maybe you can try sending a series of hardcoded scan codes, or some similar hack. That's lower level and more likely to bypass whatever bug is doing this.

And bugs reports or API suggestions for the mouse module are extremely welcome.

boppreh commented 8 years ago

Hi @fpp-gh

The library got more popular now, and I'm trying to tidy up the issues. I'm closing this issue, but your feedback is still extremely appreciated. If you have any problem that you think I may be able to help, please don't hesitate to open another issue or even send me an email (at my profile). Same thing if you think I forgot to address some previous comment.

You single-handedly improved this library by 300% with your detailed bug reports.

fpp-gh commented 8 years ago

Thanks, always happy to help a good project along :-) I still intend to make practical use of this lib in everyday life, and will chime in again if needed. Right now I'm being waylaid by less fun subjects, so it may be a while... Nice to see it's finally getting the attention it deserves though!

fpp-gh commented 8 years ago

Hehe, I see that you have been shortlisted in the "worthy read" section of yesterday's "Import Python"... Fame and glory are coming :-)

boppreh commented 8 years ago

Ooh, that's awesome. Thanks for telling me.

Qwerty-Space commented 6 years ago

Sorry to awaken an old issue, but it's on a similar topic. Please let me know if I should open a new issue @boppreh.

So, I'm trying to replicate AutoHotkey's functionality further. For example, the triggers=[] equivalent in AutoHotkey are -()[]{}:;'"/\,.?!`n `t. So if I were to replace "btw" with "by the way" it would work if I typed any of those keys. However, it would also input those keys.

So, if I typed "btw!" it would replace it with "by the way!". I have tried to replicate this in keyboard unsuccessfully. All I've achieved is something like "by the wayw" being produced. Or, if I have ?!"!{} as triggers, it just inputs them as if I hadn't pressed "shift" For example "btw!" would become "by the way1"