Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to listen Key combination? #20

Closed
521xueweihan opened this issue Mar 31, 2017 · 28 comments
Closed

How to listen Key combination? #20

521xueweihan opened this issue Mar 31, 2017 · 28 comments

Comments

@521xueweihan
Copy link

521xueweihan commented Mar 31, 2017

Great library!

When I use the pynput I want to listen the key combination, Such as: ctrl+cmd

I can't find the way to do that,So I ask you for help.👻

@moses-palmer
Copy link
Owner

Thank you!

The listener does not maintain a list of the currently pressed keys---this would be a new, quite reasonable, feature.

You can however emulate that using the following snippet:

from pynput import keyboard

# The key combination to check
COMBINATION = {keyboard.Key.cmd, keyboard.Key.ctrl}

# The currently active modifiers
current = set()


def on_press(key):
    if key in COMBINATION:
        current.add(key)
        if all(k in current for k in COMBINATION):
            print('All modifiers active!')
    if key == keyboard.Key.esc:
        listener.stop()


def on_release(key):
    try:
        current.remove(key)
    except KeyError:
        pass


with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
    listener.join()

This works as expected, with the following caveat: If one of the modifier keys is pressed before the listener is started, it has to be released and pressed again for the program to detect it.

I will update the road map in the wiki.

@521xueweihan
Copy link
Author

Thank you for your replay, I learn a lot 🙏

@arcadeperfect
Copy link

Hi,

Thanks for the lib

How can I combine your above example with a regular character to create a hotkey?

IE shift+ctrl+alt+x causes some code to execute.

Thanks

@gaoyaoxin
Copy link

@arcadeperfect

from pynput import keyboard

# The key combination to check
COMBINATION = {keyboard.Key.shift, keyboard.Key.ctrl, keyboard.Key.alt, keyboard.KeyCode.from_char('x')}

# The currently active modifiers
current = set()


def on_press(key):
    if key in COMBINATION:
        current.add(key)
        if all(k in current for k in COMBINATION):
            print('All modifiers active!')
    if key == keyboard.Key.esc:
        listener.stop()


def on_release(key):
    try:
        current.remove(key)
    except KeyError:
        pass


with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
    listener.join()

keyboard.KeyCode.from_char('x') or its alike works fine for alphanumeric keys.
Please note that the order of keys pressed is important. Beginning with alt and then x works fine while others may not.
In my point of view, this might have something to with system-built-in shortcuts which often combines ctrl, alt and shift. However, without shift, ctrl+alt+x works fine in any order.
Hope the designer (@moses-palmer) for this phenomenal package which can listen/monitor global keyboard events have some view on this!

@devxpy
Copy link

devxpy commented Apr 1, 2018

@moses-palmer How are you able to modify the current varialbe from a function. AFAIK, it's not possible to modify global variable from inside a function in python without explicity declaring global in your function definition.

https://stackoverflow.com/questions/4522786/modifying-a-global-variable-inside-a-function

@moses-palmer
Copy link
Owner

moses-palmer commented Apr 1, 2018 via email

@devxpy
Copy link

devxpy commented Apr 1, 2018

That explains it, thanks!

Here is a modified version that also prints a message when you release that key combination!

class MockButton:
    def __init__(self, *keys):
        self.combination = {*keys}
        self.currently_pressed = set()
        self.is_pressed = False

        listener = Listener(on_press=self._on_press, on_release=self._on_release)
        listener.start()

    def _on_press(self, key):
        if key in self.combination:
            self.currently_pressed.add(key)

        if self.currently_pressed == self.combination:
            self.is_pressed = True
            print('pressed!')

    def _on_release(self, key):
        try:
            self.currently_pressed.remove(key)

            if self.is_pressed and len(self.currently_pressed) == 0:
                self.is_pressed = False
                print('released!')

        except KeyError:
            pass


if __name__ == '__main__':
    btn = MockButton(Key.alt, Key.ctrl)
    input()

@GlassGruber
Copy link

Sorry to bump this up again, is just a question because maybe I'm misunderstanding something.
I've used your code above for this type of hotkey combination ctrl + c.
It wasn't working, so I fiddled a bit suspecting that maybe the key press wasn't fired properly, instead it was, but the key returned is not the one expected:

In [1]: from pynput import keyboard

In [2]: keyboard.Key.ctrl
Out[2]: <Key.ctrl: <17>>

In [3]: keyboard.Key.ctrl_l
Out[3]: <Key.ctrl_l: <162>>

keyboard.Key.ctrl is what I set in my script, but keyboard.Key.ctrl_l is instead what is caught by the listener; with this mismatch the condition to print the message is never met.
Am I wrong assuming that Key.ctrl should be considered a general alternative for Key.ctrl_l or Key.ctrl_r?
I'm on a Windows 7-64bit with Python 3.7

Thank you!

@moses-palmer
Copy link
Owner

You are correct in assuming that Key.ctrl is supposed to be a generic alternative to Key.ctrl_l and Key.ctrl_r.

In the case of comparisons, however, things do get a bit tricky, especially considering keycode_set_1 == keycode_set_2.

I guess a possible solution is to add a set of alternate key codes for each KeyCode instance, and override __eq__ and __hash__. In the mean time, you can use key in (Key.ctrl, Key.ctrl_l, Key.ctrl_r) when doing comparisons. That would not work for the code in @devxpy 's comment though.

@moses-palmer moses-palmer reopened this Aug 13, 2018
@GlassGruber
Copy link

GlassGruber commented Aug 14, 2018

In the mean time, you can use key in (Key.ctrl, Key.ctrl_l, Key.ctrl_r) when doing comparisons. That would not work for the code in @devxpy 's comment though.

Thank you @moses-palmer, I thought so, this is the code I used if anyone needs this. As a small bonus the following code listens only to double pressing of ctrl + c in less than a second:

from pynput import keyboard
import datetime

# The key combinations to check
COMBINATIONS = [
    {keyboard.Key.ctrl_l, keyboard.KeyCode(char='c')},
    {keyboard.Key.ctrl_r, keyboard.KeyCode(char='c')}
]

# The currently active modifiers
current = set()

tnow = datetime.datetime.now()
tcounter = 0

def on_press(key):
    if any([key in comb for comb in COMBINATIONS]):
        current.add(key)
        if any(all(k in current for k in comb) for comb in COMBINATIONS):
            global tnow
            global tcounter
            tcounter += 1
            if datetime.datetime.now() - tnow < datetime.timedelta(seconds=1):
                if tcounter > 1:
                    tcounter = 0
                    main_function()
            else:
                tnow = datetime.datetime.now()
    if key == keyboard.Key.esc:
        listener.stop()


def on_release(key):
    try:
        current.remove(key)
    except KeyError:
        pass

def main_function():
    print('Main function fired!')
    # rest of your code here...

with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
    listener.join()

If you have comments on above code to improve it or any suggestions (in particular the rather sloppy global declare, I'm still learning) please let me know!

Thank you.

@gabycperezdias
Copy link

gabycperezdias commented Aug 22, 2018

Hi @GlassGruber, I tried your code, but for some reason (I am on Windows) my "c" or "v" is like this only when pressed alone. When I have ctrl pressed, I get '\x16' '\x13' and don't match the combination.... @moses-palmer do you have any hint?

@gabycperezdias
Copy link

gabycperezdias commented Aug 23, 2018

On a mac (changing the ctrl to cmd) I get the 'v' as '√'
@moses-palmer Is it possible that this is an issue? Or, is there a way to get the correct combination?

@gabycperezdias
Copy link

gabycperezdias commented Sep 13, 2018

Does anyone have any idea how to fix this? @521xueweihan? @devxpy? @moses-palmer? @GlassGruber?

@moses-palmer
Copy link
Owner

moses-palmer commented Sep 13, 2018

What version do you use? Commit 63aab1a, included in pynput 1.4, introduces a fix for keyboard layout handling, which is especially notable when running the application in a command window.

I have tested the code in @GlassGruber's comment using the master branch, which correctly identified Ctrl + C, and with that commit reverted, which caused your issue with only a non-ASCII key being generated.

Note that this on Windows; the Mac issue is different, and unresolved. Running the test script and printing all characters reveal that all alphanumeric keys are altered. Ctrl + C does work however; perhaps the simple and necessary solution is to add a special case for macOS? Using Cmd already mandates this.

@gabycperezdias
Copy link

gabycperezdias commented Sep 14, 2018

@moses-palmer I've tried the pynput 1.4 and the master.

I've used this: (I also changed 'c' for 'v' and ctrl for cmd on mac)

    def on_press(key):
        print('KEY', key)
        try: print('CHAR', key.char) # letters, numbers et
        except: print('NAME', keyboard.KeyCode().from_char(key.name)) 

On Mac High Sierra - sequence: cmd / v / cmd+v
Both gave me:
KEY Key.cmd
NAME 'cmd'
KEY 'v'
CHAR v
vKEY Key.cmd
NAME 'cmd'
KEY '√'
CHAR √
mainFunction was never fired. (If I changed the code to combine with ctrl instead of cmd, the main function is fired when 'v' is pressed 3 times after ctrl pressed (and not released) and 'v' is shown correctly)

Windows 10 and Ubuntu 18 - sequence: ctrl / v / ctrl+v
KEY Key.ctrl_l
NAME 'ctrl_l'
KEY 'v'
CHAR v
KEY Key.ctrl_l
NAME 'ctrl_l'
KEY 'v'
CHAR v
Main function is only fired if ctrl key is pressed and 'v' is typed twice. but the char is shown correctly

@beterhans
Copy link

Hi GlassGruber Thanks

I edited your code so I can use in my script

basiclly I need to break a while loop. with ctrl - q
but the code demo is in a loop I can't run two loop in my script.
Luckliy I figure out to use start instead of join.
and ctrl shift keys are different on windows and mac
So I changed a bit to match all system.

Here is it

import pynput,time

is_quit = False

KeyComb_Quit = [
    {pynput.keyboard.Key.ctrl, pynput.keyboard.KeyCode(char='q')},
    {pynput.keyboard.Key.ctrl_l, pynput.keyboard.KeyCode(char='q')},
    {pynput.keyboard.Key.ctrl_r, pynput.keyboard.KeyCode(char='q')}

]

def on_press(key):
    global is_quit
    if any([key in comb for comb in KeyComb_Quit]):
        current.add(key)
        if any(all(k in current for k in comb) for comb in KeyComb_Quit):
            is_quit = True

def on_release(key):
    try:
        current.remove(key)
    except KeyError:
        pass


# The currently active modifiers
current = set()

listener = pynput.keyboard.Listener(on_press=on_press, on_release=on_release)
listener.start()

##### MAIN Script #####
while True:
    do something
    time.sleep(0.00833)
    if is_quit:
        break

@SpecialCharacter
Copy link

Sorry, I don't get it how to use join(key) correctly.
I have the listener output "´s". How do I turn it into "ś"?

@redstoneleo
Copy link

Any progress on implement the key combination feature for this project ?

@GarrettStrahan
Copy link

GarrettStrahan commented Jun 24, 2019

So I wrote some code & its causing me a headache. I want to have two different combinations. The code I wrote the first combination of keys Ctrl+G or g work but Ctrl + H or h did not but crashes the program. I have re-written my code so many times trying to figure it out that it does not makes sense. Any help? Please help me! I'm really new to python!

Indentation is not working, but if you look at the .txt it is there.

2_types_of_hotkeys.txt

from pynput import keyboard

COMBINATIONS = [
(keyboard.Key.ctrl_l, keyboard.KeyCode(char='g')),
(keyboard.Key.ctrl_l, keyboard.KeyCode(char='G'))]

COMBINATIONS2 = [
(keyboard.Key.ctrl, keyboard.KeyCode(char='h')),
(keyboard.Key.ctrl, keyboard.KeyCode(char='H'))]
#Not working just yet
current = set()

def execute1(): #Function call for character Ctrl G or Ctrl g
print("Detected hotkey")

def execute2(): #Function call for character Ctrl H or Ctrl h
print("Detected 2nd hotkey")

def on_press(key):
if any([key in COMBO for COMBO in COMBINATIONS]):
current.add(key)
if any(all(k in current for k in COMBO) for COMBO in COMBINATIONS):
execute1()
if any(all(k in current for k in COMBO) for COMBO in COMBINATIONS2):
execute2()

def on_release(key):
if any([key in COMBO for COMBO in COMBINATIONS]):
current.remove(key)
if any([key in COMBO for COMBO in COMBINATIONS2]):
current.remove(key)

with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()

@platelminto
Copy link

platelminto commented Sep 11, 2019

Using the given snippet, dead keys pollute the list and make further comparisons break. For example, when typing in Shift+t, a 'Shift' will be added, then a 'T'. If you then release the Shift, you are left with a 'T', and releasing 't' does not remove it, and the set now will keep containing 'T'.

Is there a way to disable registering dead keys?

As a current solution, I remove the last element in the list when a key is released, ignoring which key it actually is.

@segalion
Copy link

See
#182 (comment)

@moses-palmer
Copy link
Owner

@platelminto, if you run into issues with dead keys, you may want to take a look at #118. The branch fixup-win32-keyboard-listener has a proposed solution.

@pagujeeva
Copy link

How do we capture the windows super key events like (windows+r or windows+m etc)

@SpecialCharacter
Copy link

Can you give an example?

@yemreak
Copy link
Contributor

yemreak commented Jul 19, 2020

Sorry about my last sentence, It's happened due to misunderstanding so I deleted it

@SpecialCharacter
Copy link

SpecialCharacter commented Sep 26, 2020

@platelminto Had the same problem. I solved it by tracking the shift key down / shift key up movements.
It works also for windows super key events.

@a2435191
Copy link

from pynput import keyboard
from pynput.keyboard import Key
from threading import Thread
import time

COMBINATION = {
    Key.down,
    Key.up,
    Key.left
    #Key.right
}



# The key combination to check


# The currently active modifiers
current = set()


def on_press(key):
    if key in COMBINATION:
        current.add(key)
        if all(k in current for k in COMBINATION):
            print('All modifiers active!')
    if key == keyboard.Key.esc:
        listener.stop()


def on_release(key):
    try:
        current.remove(key)
    except KeyError:
        pass


with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
    listener.join()

I was looking for a way to detect multiple keypresses at once, and this seemed to be the answer. The only problem is that, for whatever reason, only two keys can be returned at the same time (with occasional exception). How do I detect 3+ keyboard inputs at once?

@luizoti
Copy link

luizoti commented Mar 22, 2022

from pynput import keyboard
from pynput.keyboard import Key
from threading import Thread
import time

COMBINATION = {
    Key.down,
    Key.up,
    Key.left
    #Key.right
}



# The key combination to check


# The currently active modifiers
current = set()


def on_press(key):
    if key in COMBINATION:
        current.add(key)
        if all(k in current for k in COMBINATION):
            print('All modifiers active!')
    if key == keyboard.Key.esc:
        listener.stop()


def on_release(key):
    try:
        current.remove(key)
    except KeyError:
        pass


with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
    listener.join()

I was looking for a way to detect multiple keypresses at once, and this seemed to be the answer. The only problem is that, for whatever reason, only two keys can be returned at the same time (with occasional exception). How do I detect 3+ keyboard inputs at once?

It seems that pyinput can't handle three or more events at the same time, and where it always replaces the second key with the third. From the looks of it it does this without calling the on_release event.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests