在Linux上用Python监听全局按键组合

6
我刚写了一个小程序,每隔几分钟从flickr下载新的壁纸。现在我想添加“喜欢”壁纸的功能,这样它就会比不喜欢或不感兴趣的壁纸更频繁地出现。
我想为此功能分配全局键盘快捷键。例如:如果我按下ctrl+7,则会在Python中执行某种“喜欢”功能。
是否有任何库可以做到这一点(例如在JavaScript中,我可以使用shortcut("ctrl-b", someFunction);定义快捷方式)?
否则,我该如何做到这一点?我看到了this similar SO question,但它已经过时了。
3个回答

7
我不知道有任何旨在扩展的库,但正如您提供的链接所述,pykeylogger的后端提供了一个示例,说明如何实现它,但对于您的需求来说似乎有点过于复杂。 pykeylogger使用python-xlib模块捕获X显示器上的按键。有人已经创建了一个更轻量级的示例,演示了如何通过pastebin进行操作。下面是从该示例中直接复制的源代码。
from Xlib.display import Display
from Xlib import X
from Xlib.ext import record
from Xlib.protocol import rq

disp = None

def handler(reply):
    """ This function is called when a xlib event is fired """
    data = reply.data
    while len(data):
        event, data = rq.EventField(None).parse_binary_value(data, disp.display, None, None)

        # KEYCODE IS FOUND USERING event.detail
        print(event.detail)

        if event.type == X.KeyPress:
            # BUTTON PRESSED
            print("pressed")
        elif event.type == X.KeyRelease:
            # BUTTON RELEASED
            print("released")

# get current display
disp = Display()
root = disp.screen().root

# Monitor keypress and button press
ctx = disp.record_create_context(
            0,
            [record.AllClients],
            [{
                    'core_requests': (0, 0),
                    'core_replies': (0, 0),
                    'ext_requests': (0, 0, 0, 0),
                    'ext_replies': (0, 0, 0, 0),
                    'delivered_events': (0, 0),
                    'device_events': (X.KeyReleaseMask, X.ButtonReleaseMask),
                    'errors': (0, 0),
                    'client_started': False,
                    'client_died': False,
            }])
disp.record_enable_context(ctx, handler)
disp.record_free_context(ctx)

while 1:
    # Infinite wait, doesn't do anything as no events are grabbed
    event = root.display.next_event()

您需要扩展处理程序以适应您的需求,而不仅仅是将其打印到屏幕上,然后将其转换为单独的线程。
(痛苦的)替代方法是直接监听键盘,而不依赖于外部库或X会话。在Linux中,所有内容都是文件,您的键盘输入将位于/dev/input中,您可以像读取文件一样在后台读取它,例如open('/dev/input/even2', 'rb')。这不建议使用,因为它需要升级权限,找出哪个设备是键盘,然后创建自己的键映射。只是想让您知道必要时可能的情况。
编辑:还发现了使用Python gtk3在X上进行全局按键绑定,其中似乎有更多的示例。

5

如果您想查看我最终使用的代码,我在这里制作了一个小的要点

按键码可能因计算机而异。此外,我快速创建的键盘监听器类没有针对可能发生的所有问题进行保护。不过,对于我来说,它运行得非常好。


1
这是一个更清晰的版本,它还会解码并打印键名,而不仅仅是数字:
#!/usr/bin/env python2

# Captures all keyboard and mouse events, including modifiers
# Adapted from https://dev59.com/gH3aa4cB1Zd3GeqPeooi
# Requires python-xlib

from Xlib.display import Display
from Xlib import X, XK
from Xlib.ext import record
from Xlib.protocol import rq


class Listener:
  def __init__(self):
    self.disp = None
    self.keys_down = set()

  def keycode_to_key(self, keycode, state):
    i = 0
    if state & X.ShiftMask:
      i += 1
    if state & X.Mod1Mask:
      i += 2
    return self.disp.keycode_to_keysym(keycode, i)

  def key_to_string(self, key):
    keys = []
    for name in dir(XK):
      if name.startswith("XK_") and getattr(XK, name) == key:
        keys.append(name.lstrip("XK_").replace("_L", "").replace("_R", ""))
    if keys:
      return " or ".join(keys)
    return "[%d]" % key

  def keycode_to_string(self, keycode, state):
    return self.key_to_string(self.keycode_to_key(keycode, state))

  def mouse_to_string(self, code):
    if code == X.Button1:
      return "Button1"
    elif code == X.Button2:
      return "Button2"
    elif code == X.Button3:
      return "Button3"
    elif code == X.Button4:
      return "Button4"
    elif code == X.Button5:
      return "Button5"
    else:
      return "{%d}" % code

  def down(self, key):
    self.keys_down.add(key)
    self.print_keys()

  def up(self, key):
    if key in self.keys_down:
      self.keys_down.remove(key)
      self.print_keys()

  def print_keys(self):
    keys = list(self.keys_down)
    print "Currently pressed:", ", ".join(keys)

  def event_handler(self, reply):
    data = reply.data
    while data:
      event, data = rq.EventField(None).parse_binary_value(data, self.disp.display, None, None)
      if event.type == X.KeyPress:
        self.down(self.keycode_to_string(event.detail, event.state))
      elif event.type == X.KeyRelease:
        self.up(self.keycode_to_string(event.detail, event.state))
      elif event.type == X.ButtonPress:
        self.down(self.mouse_to_string(event.detail))
      elif event.type == X.ButtonRelease:
        self.up(self.mouse_to_string(event.detail))

  def run(self):
    self.disp = Display()
    XK.load_keysym_group('xf86')
    root = self.disp.screen().root
    ctx = self.disp.record_create_context(0,
                                          [record.AllClients],
                                          [{
                                            'core_requests': (0, 0),
                                            'core_replies': (0, 0),
                                            'ext_requests': (0, 0, 0, 0),
                                            'ext_replies': (0, 0, 0, 0),
                                            'delivered_events': (0, 0),
                                            'device_events': (X.KeyReleaseMask, X.ButtonReleaseMask),
                                            'errors': (0, 0),
                                            'client_started': False,
                                            'client_died': False,
                                          }])
    self.disp.record_enable_context(ctx, lambda reply: self.event_handler(reply))
    self.disp.record_free_context(ctx)
    while True:
      event = root.display.next_event()


if __name__ == "__main__":
  Listener().run()

输出看起来像这样:
Currently pressed: Alt
Currently pressed:
Currently pressed: Alt
Currently pressed: Alt, Tab
Currently pressed: Alt
Currently pressed: Alt, Tab
Currently pressed: Alt
Currently pressed: Alt, Tab
Currently pressed: Alt
Currently pressed: Alt, Tab
Currently pressed: Alt
Currently pressed: Alt, Tab
Currently pressed: Alt
Currently pressed:
Currently pressed: Control
Currently pressed: Control, a
Currently pressed: Control
Currently pressed: Control, Shift
Currently pressed: Control, Shift, A
Currently pressed: Control, Shift

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接