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

Suppressing only hotkey events on Windows #170

Closed
wastedepository opened this issue Jul 9, 2019 · 16 comments
Closed

Suppressing only hotkey events on Windows #170

wastedepository opened this issue Jul 9, 2019 · 16 comments

Comments

@wastedepository
Copy link

wastedepository commented Jul 9, 2019

I'm on Windows. I want to use pynput to establish a hotkey (say, F1) while the foreground window is another program (say, Notepad). When the user presses F1, I do not want the foreground program to find out about that key press. But I want the Python script to be aware that F1 was pressed. If I press any other key, Notepad should receive the key as usual.

There are multiple threads claiming this is possible using the pynput.keyboard.Listener.__init__() win32_event_filter argument, possibly in combination with pynput.keyboard.Listener.suppress_event():
#163
#70
#47

After messing with this for two hours, I have no idea how to make it work. How is win32_event_filter supposed to help suppress hotkeys for the foreground window, while still having those keys be processed by the Python program?

The callback win32_event_filter sets up doesn't even have access to Listener.suppress_event(). Even if you use global variables to give it access, calling listener.suppress_event() from the win32_event_filter callback just causes the on_press and on_release callbacks to never be initiated.

(I also failed to set up a subclass which inherits from Listener and overwrites the suppress property inherited from AbstractListener.)

What is the correct way to do this? Maybe I am supposed to use only the win32_event_filter callback, and not use on_press or on_release at all?

I include this skeleton program to show the situation:

# TODO: This should not send F1 to the foreground window (Notepad), but should still print out "on press" for F1.

from pynput import keyboard

def on_press(key):
    print('on press', key)

def on_release(key):
    print('on release', key)

def win32_event_filter(msg, data):
    print(msg, data)

if __name__ == '__main__':
    # Set up keyboard shortcuts.
    with keyboard.Listener(on_press=on_press,
                           on_release=on_release,
                           win32_event_filter=win32_event_filter,
                           suppress=False) as listener:
        listener.join()
@wastedepository wastedepository changed the title Suppressing certain keyboard events Suppressing certain keyboard events on Windows Jul 9, 2019
@wastedepository wastedepository changed the title Suppressing certain keyboard events on Windows Suppressing keyboard events on Windows Jul 9, 2019
@wastedepository wastedepository changed the title Suppressing keyboard events on Windows Suppressing only hotkey events on Windows Jul 9, 2019
@XanaDublaKublaConch
Copy link

Same issue brought me here. The event filter runs before key up and key down and doesn't get passed a handle to the listener. I don't know what I'm supposed to do with this. The docs mention calling self.suppress_event() but there's no self. Suppressing from the filter blocks the key up/down. The filter also gets an event message and a virtual key/scan code, but my key up/down routines are using KeyCodes, so the comparisons are all off. I can't find a good way to convert between them. Kind of lost. I just want to make a global hotkey that doesn't get passed to the active window.

@moses-palmer
Copy link
Owner

The current API does not lend itself to global hotkeys---the workarounds mentioned in other thread have the potential to work, but require the end user to write platform specific code for each platform they want to support.

The correct solution from the perspective of this library would be to add an API for global hotkeys, since those API's already exist for the supported platforms.

Until that has been implemented, however, the only way to achieve this is using the event suppression functionality.

You are correct in your observation that the data passed to the filter is incompatible with what the event callbacks receive, so you will have to modify your checks accordingly. There is really no way around this, as the filter ha to be called before the event is fully translated.

You do not have to make the listener instance global, but you do need to name or in the same scope add the filter function.

@XanaDublaKublaConch
Copy link

Ok. Thanks for the update. I think I see how to make this work based on that info.

@lukakoczorowski
Copy link

Here's how I got around it

def win32_event_filter(msg, data):
   listener._suppress = False
   # ...
   if stop_propagation:
      listener._suppress = True

Might be a cleaner way, this works though

@wastedepository
Copy link
Author

wastedepository commented Mar 21, 2020

Could you clarify what you mean, by embedding your solution in the test code I originally posted? stop_propagation is not a known existing variable.

Same test code (non-working):

# TODO: This should not send F1 to the foreground window (Notepad), but should still print out "on press" for F1.

from pynput import keyboard

def on_press(key):
    print('on press', key)

def on_release(key):
    print('on release', key)

def win32_event_filter(msg, data):
    print(msg, data)

if __name__ == '__main__':
    # Set up keyboard shortcuts.
    with keyboard.Listener(on_press=on_press,
                           on_release=on_release,
                           win32_event_filter=win32_event_filter,
                           suppress=False) as listener:
        listener.join()

@lukakoczorowski
Copy link

Apologies for the delay,

from pynput import keyboard
 
def keyboard_listener():
	global listener
	def on_press(key):
		print('on press', key)

	def on_release(key):
		print('on release', key)
		if key == keyboard.Key.esc:
			return False # This will quit the listener
 
	def win32_event_filter(msg, data):
		if (msg == 257 or msg == 256) and data.vkCode == 112: # Key Down/Up & F1
			print("Suppressing F1 up")
			listener._suppress = True
            # return False # if you return False, your on_press/on_release will not be called
		else:
	 		listener._suppress = False
		return True
			
	return keyboard.Listener(
		on_press=on_press,
		on_release=on_release,
		win32_event_filter=win32_event_filter,
		suppress=False
	)

listener = keyboard_listener()

if __name__ == '__main__':
	with listener as ml:
		ml.join() 

@SpecialCharacter
Copy link

Dear lukakoczorowski, so you have an outer/global listener and an inner listener? I am trying to make sense of it... And the win32_event_filter comes between the on_press/release(key) and the inner listener and prevents any defined keys reaching the inner listener?

@SpecialCharacter
Copy link

PS: I tried your code and it worked.
Then I replaced the vkCode for F1 with RWin (= cmd_r) and it did not work. I tried 231 and 8c.
Do you know which is the correct vkCode?

@SpecialCharacter
Copy link

SpecialCharacter commented Jul 28, 2020

PPS: I went with a key that works, but now it says:
AttributeError: module 'pynput.keyboard' has no attribute 'type'
(I inserted some keyboard.type('abc').)

@SpecialCharacter
Copy link

Finally I got it to work!

from pynput import keyboard
kbc = keyboard.Controller()
Key = keyboard.Key

...
kbc.type('abc')

@SpecialCharacter
Copy link

Still, RWin (= cmd_r) and print_screen will not work.

@SpecialCharacter
Copy link

Is True/False the only result the win32_event_filter can export? Because I also want it to report a variable...

@moses-palmer
Copy link
Owner

The caller of the filter only responds to Boolean return values.

What other value would you like to return? The callback acts as a simple filter, so I cannot think of any other reasonable class of values.

@SpecialCharacter
Copy link

Thanks! It is possible to use several win32_event_filters (= linked to different variables)?

@SpecialCharacter
Copy link

OK, found out it is possible to have more arguments linked to different variables in the filter.
The only problem I have is when I press key B (variable b), I want key A to be reset, i.e. variable a deleted. Somehow this does not work yet.

@SpecialCharacter
Copy link

Ah, had to tweak the code in the arguments. Now it works.

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

5 participants