pynput icon indicating copy to clipboard operation
pynput copied to clipboard

Global hotkeys with ctrl and alt don't work on MacOS

Open ghost opened this issue 4 years ago • 15 comments

keyboard.GlobalHotKeys({"<ctrl>+k": test1, "<alt>+k": test2})

On Mac, hotkeys with the modifiers ctrl or alt simply don't work. The functions don't get called. Hotkeys with the cmd or shift modifiers, or with no modifiers at all, work perfectly fine. But for some reason there's a problem with ctrl and alt.

The same problem occurs if I use keyboard.HotKey, so the following example code shown in the docs doesn't work either:

from pynput import keyboard

def on_activate():
    print('Global hotkey activated!')

def for_canonical(f):
    return lambda k: f(l.canonical(k))

hotkey = keyboard.HotKey(
    keyboard.HotKey.parse('<ctrl>+<alt>+h'),
    on_activate)
with keyboard.Listener(
        on_press=for_canonical(hotkey.press),
        on_release=for_canonical(hotkey.release)) as l:
    l.join()

ghost avatar Aug 12 '20 23:08 ghost

Thank you for your report.

As I have noted in other issues, I no longer have access to a macOS system, so troubleshooting is very difficult. Could you please try to run the script below and post the output when you attempt the hotkey combination?

from pynput.keyboard import Key, Listener


def on_press(key):
    print('> {} ({})'.format(str(key), listener.canonical(key)))

def on_release(key):
    print('< {} ({})'.format(str(key), listener.canonical(key)))
    if key == Key.esc:
        return False


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

moses-palmer avatar Aug 27 '20 10:08 moses-palmer

Cmd + K

> Key.cmd (Key.cmd)
> 'k' ('k')
< 'k' ('k')
< Key.cmd (Key.cmd)

Shift + K

> Key.shift (Key.shift)
> 'K' ('k')
< 'K' ('k')
< Key.shift (Key.shift)

Ctrl + K

> Key.ctrl (Key.ctrl)
> '\x0b' ('\x0b')
< '\x0b' ('\x0b')
< Key.ctrl (Key.ctrl)

Alt + K

> Key.alt (Key.alt)
> '˚' ('˚')
< '˚' ('˚')
< Key.alt (Key.alt)

ghost avatar Aug 27 '20 15:08 ghost

Thank you for the log.

I suspect that CGEventKeyboardGetUnicodeString used here returns something unexpected by pynput. May I ask you to add some logging to Listener._event_to_key? All variables defined within the method are of interest.

moses-palmer avatar Aug 27 '20 18:08 moses-palmer

I came into the same problem, is it solved? @moses-palmer

wallezhang avatar Nov 03 '20 11:11 wallezhang

Unfortunately not. @wallezhang, can you provide logs?

moses-palmer avatar Nov 25 '20 20:11 moses-palmer

Hi there. Im having the same problem on a mac. When I use -n for example, it's not recognized. This is what it outputs:

Key.ctrl_r (Key.ctrl) '\x0e' ('\x0e') < '\x0e' ('\x0e') < Key.ctrl_r (Key.ctrl)

Can you give me an example of the kind of logging you'd like to see for this?

Thanks

brettelliot avatar Dec 25 '20 04:12 brettelliot

Thank you for your offer to debug.

I would like information about the event parameter in this function. Information can be extracted using the methods found here.

They are available under the Quartz module. I think that especially calling CGEventGetIntegerValueField using the constants found here would be helpful.

moses-palmer avatar Dec 25 '20 12:12 moses-palmer

OK, Below you'll see my debugging program and the output. I only included params that seemed to involve keyboard stuff but if you want more specific ones please let me know which ones and ill add them and run it again. Thanks for the support!

from pynput.keyboard import Key, Listener
import Quartz


class MyListener(Listener):

    def _event_to_key(self, event):
        my_dict = {}
        my_dict['CGEventGetType'] = Quartz.CGEventGetType(event)
        my_dict['kCGKeyboardEventAutorepeat'] = Quartz.CGEventGetIntegerValueField(
            event, Quartz.kCGKeyboardEventAutorepeat)
        my_dict['kCGKeyboardEventKeycode'] = Quartz.CGEventGetIntegerValueField(
            event, Quartz.kCGKeyboardEventKeycode)
        my_dict['kCGKeyboardEventKeyboardType'] = Quartz.CGEventGetIntegerValueField(
            event, Quartz.kCGKeyboardEventKeyboardType)

        print('\n')
        print("New _event_to_key call:")
        print(my_dict)

        return Listener._event_to_key(self, event)


def on_press(key):
    # print('> {} ({})'.format(str(key), listener.canonical(key)))
    pass


def on_release(key):
    # print('< {} ({})'.format(str(key), listener.canonical(key)))
    # if key == Key.esc:
    #     return False
    pass

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

And here's the output after typing "control" and "control-n" and "n" a few times:

New _event_to_key call: {'CGEventGetType': 12, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 62, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 14, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 0, 'kCGKeyboardEventKeyboardType': 0}

New _event_to_key call: {'CGEventGetType': 12, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 62, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 14, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 0, 'kCGKeyboardEventKeyboardType': 0}

New _event_to_key call: {'CGEventGetType': 12, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 62, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 10, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 45, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 14, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 0, 'kCGKeyboardEventKeyboardType': 0}

New _event_to_key call: {'CGEventGetType': 12, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 62, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 11, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 45, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 10, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 45, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 11, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 45, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 14, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 0, 'kCGKeyboardEventKeyboardType': 0}

New _event_to_key call: {'CGEventGetType': 12, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 62, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 10, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 45, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 11, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 45, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 14, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 0, 'kCGKeyboardEventKeyboardType': 0}

New _event_to_key call: {'CGEventGetType': 12, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 62, 'kCGKeyboardEventKeyboardType': 58}

New _event_to_key call: {'CGEventGetType': 14, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 0, 'kCGKeyboardEventKeyboardType': 0}

New _event_to_key call: {'CGEventGetType': 14, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 0, 'kCGKeyboardEventKeyboardType': 0}

New _event_to_key call: {'CGEventGetType': 14, 'kCGKeyboardEventAutorepeat': 0, 'kCGKeyboardEventKeycode': 0, 'kCGKeyboardEventKeyboardType': 0}

brettelliot avatar Dec 25 '20 18:12 brettelliot

@brettelliot Would you be so kind to take a look at https://stackoverflow.com/questions/64408695/example-for-darwin-intercept-in-pynput ?

I don't know anything about the "grammar" of Mac, but you seem knowledgeable. Any help is appreciated! Do I have to import Quartz?

SpecialCharacter avatar Dec 31 '20 18:12 SpecialCharacter

I have finally been able to acquire access to a system with macOS.

So far I have been able to reproduce the error, and unfortunately found that the simple solution I envisioned---just clearing the alt, ctrl and modifiers before passing the event to CGEventKeyboardGetUnicodeString---does not work; the unicode representation appears to be baked into the event.

I did, however, find a rather involved solution here. I remains to be seen whether this works from Python though.

moses-palmer avatar Jan 21 '21 18:01 moses-palmer

I am having a similar issue, but different, when trying to execute this function in Ubuntu:

from pynput.keyboard import Key, Controller
import time

def open_terminal():
    keyboard = Controller()
    time.sleep(1)
    with keyboard.pressed(Key.ctrl_l, Key.alt_l):
        keyboard.press('t')
        keyboard.release('t')
    time.sleep(1)

when I execute open_terminal() in an interactive console, instead of opening a Terminal (which is expected behaviour if I press those keys myself), the Ctrl, Alt and T keyboard events seem to be sent to the console instead of the OS, so you end up seeing this:

>>> open_terminal()
^[^T>>>

should I open a ticket about this issue, or is it worthless?

SebasSBM avatar Aug 10 '21 16:08 SebasSBM

I have same issue with ctrl in mac, I just wanted to press ctrl + right key, where it detects the right key but not the ctrl key :( have tried

print('pressing ctrl+right ') with keyboard.pressed(Key.ctrl): keyboard.press(Key.right) keyboard.release(Key.right)

but still not working.

savankaneriya avatar Aug 23 '22 19:08 savankaneriya

I solved it by toggling a variable "ctrl" from True to False in the on_press(key)/on_release(key), then:

if ctrl == True and key == Key.right
<do xyz>
ctrl = False

Does that help you?

SpecialCharacter avatar Aug 25 '22 07:08 SpecialCharacter

I solved it by toggling a variable "ctrl" from True to False in the on_press(key)/on_release(key), then:

if ctrl == True and key == Key.right
<do xyz>
ctrl = False

Does that help you?

can you elaborate by some more code ?

savankaneriya avatar Sep 07 '22 10:09 savankaneriya

@savankaneriya savankaneriya Sorry:

` cmd = False alt_gr = False echo = False

def on_press(key): global cmd global alt_gr global echo

try:
	print('alphanumeric key {0} pressed'.format(
		key.char))

except AttributeError:
	print('special key {0} pressed'.format(
		key))
	
	if key == Key.cmd:								# Win down # Apple down
		cmd = True
		echo = cmd

	elif key == Key.alt_gr:							# AltGr down / right Alt down
		alt_gr = True
		echo = alt_gr

def on_release(key): global cmd global alt_gr global echo

print('[alphanumeric/special] key {0} released'.format(
	key))

if key == keyboard.Key.esc: 							# <--- stop program
	# Stop listener
	return False

elif key == Key.cmd:									# Win up # Apple up
	cmd = False
	echo = cmd

elif key == Key.alt_gr:								# AltGr up / right Alt up
	alt_gr = False
	echo = alt_gr`

SpecialCharacter avatar Dec 17 '22 23:12 SpecialCharacter