使用 Python gtk3 在 X 上进行全局键绑定

2023-12-07

我正在寻找一些可以与 gtk3 一起使用的 python xlib 全局键绑定示例,就像为 gtk2 所做的那样http://www.siafoo.net/snippet/239。这里的代码非常相似:

from Xlib.display import Display
from Xlib import X
import gtk.gdk
import threading
import gobject

class GlobalKeyBinding (gobject.GObject, threading.Thread):
    __gsignals__ = {
            'activate': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
        }


    def __init__ (self):
        gobject.GObject.__init__ (self)
        threading.Thread.__init__ (self)
        self.setDaemon (True)

        self.keymap = gtk.gdk.keymap_get_default ()
        self.display = Display ()
        self.screen = self.display.screen ()
        self.root = self.screen.root

        self.map_modifiers ()
        self.keybindings={}
        self.current_signal=None

    def map_modifiers (self):
        gdk_modifiers = (gtk.gdk.CONTROL_MASK, gtk.gdk.SHIFT_MASK, gtk.gdk.MOD1_MASK,
                         gtk.gdk.MOD3_MASK, gtk.gdk.MOD4_MASK, gtk.gdk.MOD5_MASK,
                         gtk.gdk.SUPER_MASK, gtk.gdk.HYPER_MASK)
        self.known_modifiers_mask = 0
        for modifier in gdk_modifiers:
            self.known_modifiers_mask |= modifier

    def add_grab_key(self,accelerator,signal):
        if not accelerator:
            return
        keyval,modifiers=gtk.accelerator_parse(accelerator)
        if not keyval or not modifiers:
            return
        keycode=self.keymap.get_entries_for_keyval(keyval)[0][0]
        self.keybindings[signal]=[accelerator,
            keycode,
            int (modifiers)]
                #grab_key operation forces X to exclusivelly send given keycode (like apostrophe char) to current X client (unless other X client grabbed it before).
                #`X.AnyModifier' parameter tells to register the keycode for all modifiers, thus Ctrl-', Alt-', Shift-', ' will all be sent to this X client.
                # given keyval is grabbed by current X client until `ungrab_key' is called.
        return self.root.grab_key (keycode, X.AnyModifier, True, X.GrabModeAsync, X.GrabModeSync)



    def ungrab (self):
        for signal in self.keybindings:
                    # ungrab_key ungrabs given keycode, that was grabbed by `grab_key'.
            self.root.ungrab_key (self.keybindings[signal][1],X.AnyModifier, self.root)
        self.keybindings={}

    def idle (self):
        if self.current_signal:
            gtk.gdk.threads_enter ()
            self.emit (self.current_signal)
            self.current_signal=None
            gtk.gdk.threads_leave ()
        return False

        #threading.Thread.start() method invokes this method.
    def run (self):
        self.running = True
        wait_for_release = False
        while self.running:
            event = self.display.next_event () # registered keycode(or probably rather event) has been received.
            if self.current_signal:
                self.display.allow_events (X.ReplayKeyboard, event.time)
                continue

            try:
                if not wait_for_release and event.type == X.KeyPress:
                    modifiers = event.state & self.known_modifiers_mask
                                        print modifiers, event.detail
                    for signal in self.keybindings:
                        if self.keybindings[signal][1] == event.detail and self.keybindings[signal][2] == modifiers:
                            self.this_signal=signal
                            this_keycode = self.keybindings[signal][1]
                            wait_for_release=True
                            break
                    if wait_for_release:
                        self.display.allow_events (X.AsyncKeyboard, event.time)
                    else:
                        self.display.allow_events (X.ReplayKeyboard, event.time)
                    continue
                elif wait_for_release and event.detail == this_keycode and event.type == X.KeyRelease:
                    wait_for_release = False
                    self.current_signal=self.this_signal
                    self.event_window=event.window
                    gobject.idle_add (self.idle)
                    self.display.allow_events (X.AsyncKeyboard, event.time)
                else:
                    self.display.allow_events (X.ReplayKeyboard, event.time)
            except:
                self.display.allow_events (X.ReplayKeyboard, event.time)
    def stop (self):
        print "stopping keybindings thread..."
        self.running = False
        self.ungrab ()
        self.display.close ()

# SAMPLE USAGE
def callback (keybinding):
    print 'Callback!'
    keybinding.stop()
    gtk.main_quit ()

def main():
    print "starting..."
    gtk.gdk.threads_init ()
    keybindings=GlobalKeyBinding()
    keybindings.add_grab_key('<Control>apostrophe','activate')
    keybindings.connect('activate',callback)
    keybindings.start () # let's thart the thread
    gtk.main ()

main()

不幸的是我没有找到,因此我决定重新实现它以使用gtk3(ubuntu 12.04)。下面是结果。它没有运行时错误,但不幸的是它没有获取任何输入。

from Xlib.display import Display
from Xlib import X
from gi.repository import Gtk, Gdk, GObject
import threading

class GlobalKeyBinding (GObject.GObject, threading.Thread):
    __gsignals__ = {
            'activate': (GObject.SignalFlags.RUN_LAST, None, ()),
        }


    def __init__ (self):
        GObject.GObject.__init__ (self)
        threading.Thread.__init__ (self)
        self.setDaemon (True)

        self.keymap = Gdk.Keymap.get_default()
        self.display = Display ()
        self.screen = self.display.screen ()
        self.root = self.screen.root

        self.map_modifiers ()
        self.keybindings={}
        self.current_signal=None

    def map_modifiers (self):
        gdk_modifiers = (Gdk.ModifierType.CONTROL_MASK, Gdk.ModifierType.SHIFT_MASK, Gdk.ModifierType.MOD1_MASK,
                         Gdk.ModifierType.MOD3_MASK, Gdk.ModifierType.MOD4_MASK, Gdk.ModifierType.MOD5_MASK,
                         Gdk.ModifierType.SUPER_MASK, Gdk.ModifierType.HYPER_MASK)
        self.known_modifiers_mask = 0
        for modifier in gdk_modifiers:
            #print modifier,modifier+0
            self.known_modifiers_mask |= modifier

    def add_grab_key(self,accelerator,signal):
        if not accelerator:
            return
        keyval,modifiers=Gtk.accelerator_parse(accelerator)
        if not keyval or not modifiers:
            return
        #keycode=self.keymap.get_entries_for_keyval(keyval)[0][0]
                success, entries = self.keymap.get_entries_for_keyval(keyval)
                entry = [(int(i.keycode), i.group, i.level) for i in entries]
                if not entry:
                    raise TypeError("Invalid key name")

                keycode=entry[0][0]
        self.keybindings[signal]=[accelerator,
            keycode,
            int (modifiers)]
        return self.root.grab_key (keycode, X.AnyModifier, True, X.GrabModeAsync, X.GrabModeSync)



    def ungrab (self):
        for signal in self.keybindings:
            self.root.ungrab_key (self.keybindings[signal][1],X.AnyModifier, self.root)
        self.keybindings={}

    def idle (self):
        if self.current_signal:
            Gdk.threads_enter ()
            self.emit (self.current_signal)
            self.current_signal=None
            Gdk.threads_leave ()
        return False

    def run (self):
        self.running = True
        wait_for_release = False
        while self.running:
            event = self.display.next_event ()
            if self.current_signal:
                self.display.allow_events (X.ReplayKeyboard, event.time)
                continue

            try:
                if not wait_for_release and event.type == X.KeyPress:
                    modifiers = event.get_state() & self.known_modifiers_mask
                    print modifiers,event.get_state()
                    for signal in self.keybindings:
                        if self.keybindings[signal][1] == event.detail and self.keybindings[signal][2] == modifiers:
                            self.this_signal=signal
                            this_keycode = self.keybindings[signal][1]
                            wait_for_release=True
                            break
                    if wait_for_release:
                        self.display.allow_events (X.AsyncKeyboard, event.time)
                    else:
                        self.display.allow_events (X.ReplayKeyboard, event.time)
                    continue
                elif wait_for_release and event.detail == this_keycode and event.type == X.KeyRelease:
                    wait_for_release = False
                    self.current_signal=self.this_signal
                    self.event_window=event.window
                    GObject.idle_add (self.idle)
                    self.display.allow_events (X.AsyncKeyboard, event.time)
                else:
                    self.display.allow_events (X.ReplayKeyboard, event.time)
            except:
                self.display.allow_events (X.ReplayKeyboard, event.time)
    def stop (self):
        self.running = False
        self.ungrab ()
        self.display.close ()

# SAMPLE USAGE
def callback (keybinding):
    print 'Callback!'
    keybinding.stop()
    Gtk.main_quit ()

def main():
    print "starting..."
    Gdk.threads_init ()
    keybindings=GlobalKeyBinding()
    keybindings.add_grab_key('<Control>apostrophe','activate')
    keybindings.connect('activate',callback)
    print "keybindings go"
    keybindings.start () # let's thart the thread
    print "gtk go"
    Gtk.main ()

main()

也许您有一些想法如何让它发挥作用?

此致, 保罗


嘿,我实现了相同的代码并且工作得很好,你可以试试这个。但没有保修。如果您发现缺少零件,请告诉我。

# -*- coding: utf-8; -*-
# Copyright (C) 2013  Özcan Esen <[email protected]>
# Copyright (C) 2008  Luca Bruno <[email protected]>
#
# This a slightly modified version of the globalkeybinding.py file which is part of FreeSpeak.
#   
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell   
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#   
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#   
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,  
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER    
# DEALINGS IN THE SOFTWARE.

from Xlib.display import Display
from Xlib import X, error
#import GObject
#import gtk.gdk
from gi.repository import Gtk, Gdk, GObject, GLib
import threading
from config import ConfigManager

class GlobalKeyBinding(GObject.GObject, threading.Thread):
    __gsignals__ = {
        'activate':(GObject.SIGNAL_RUN_LAST, None,()),
        }

    def __init__(self):
        GObject.GObject.__init__(self)
        threading.Thread.__init__(self)
        self.setDaemon(True)

        self.keymap = Gdk.Keymap.get_default()
        self.display = Display()
        self.screen = self.display.screen()
        self.root = self.screen.root
        self.ignored_masks = self.get_mask_combinations(X.LockMask | X.Mod2Mask | X.Mod5Mask)
        self.map_modifiers()

    def map_modifiers(self):
        gdk_modifiers =(Gdk.ModifierType.CONTROL_MASK, Gdk.ModifierType.SHIFT_MASK, Gdk.ModifierType.MOD1_MASK,
                         Gdk.ModifierType.MOD2_MASK, Gdk.ModifierType.MOD3_MASK, Gdk.ModifierType.MOD4_MASK, Gdk.ModifierType.MOD5_MASK,
                         Gdk.ModifierType.SUPER_MASK, Gdk.ModifierType.HYPER_MASK)
        self.known_modifiers_mask = 0
        for modifier in gdk_modifiers:
            if "Mod" not in Gtk.accelerator_name(0, modifier):
                self.known_modifiers_mask |= modifier

    def grab(self):
        Gdk.threads_enter()
        accelerator = ConfigManager.get_conf('global-key')
        Gdk.threads_leave()
        keyval, modifiers = Gtk.accelerator_parse(accelerator)
        if not accelerator or(not keyval and not modifiers):
            self.keycode = None
            self.modifiers = None
            return

        self.keycode= self.keymap.get_entries_for_keyval(keyval)[1][0].keycode
        self.modifiers = int(modifiers)

        catch = error.CatchError(error.BadAccess)
        for ignored_mask in self.ignored_masks:
            mod = modifiers | ignored_mask
            result = self.root.grab_key(self.keycode, mod, True, X.GrabModeAsync, X.GrabModeSync, onerror=catch)
        self.display.sync()
        if catch.get_error():
            return False
        return True

    def ungrab(self):
        if self.keycode:
            self.root.ungrab_key(self.keycode, X.AnyModifier, self.root)

    def get_mask_combinations(self, mask):
        return [x for x in xrange(mask+1) if not (x & ~mask)]

    def idle(self):
        Gdk.threads_enter()
        self.emit("activate")
        Gdk.threads_leave()
        return False

    def run(self):
        self.running = True
        wait_for_release = False
        while self.running:
            event = self.display.next_event()
            self.current_event_time = event.time
            if event.detail == self.keycode and event.type == X.KeyPress and not wait_for_release:
                modifiers = event.state & self.known_modifiers_mask
                if modifiers == self.modifiers:
                    wait_for_release = True
                    self.display.allow_events(X.AsyncKeyboard, event.time)
                else:
                    self.display.allow_events(X.ReplayKeyboard, event.time)
            elif event.detail == self.keycode and wait_for_release:
                if event.type == X.KeyRelease:
                    wait_for_release = False
                    GLib.idle_add(self.idle)
                self.display.allow_events(X.AsyncKeyboard, event.time)
            else:
                self.display.allow_events(X.ReplayKeyboard, event.time)

    def stop(self):
        self.running = False
        self.ungrab()
        self.display.close()
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 Python gtk3 在 X 上进行全局键绑定 的相关文章

  • Flask+Nginx+uWSGI:导入错误:没有名为站点的模块

    我安装为http www reinbach com uwsgi nginx flask virtualenv mac os x html http www reinbach com uwsgi nginx flask virtualenv
  • 如何使用 pyinstaller 包含文件?

    我也使用 tkinter 使用 python 3 7 编写了一个程序 由于我使用的是外部图片 因此当我将所有内容编译为一个 exe 时 我需要包含它们 我试过做 add data bg png files 但我仍然收到此错误 tkinter
  • JavaScript 相当于 Python 的参数化 string.format() 函数

    这是 Python 示例 gt gt gt Coordinates latitude longitude format latitude 37 24N longitude 115 81W Coordinates 37 24N 115 81W
  • 希伯来语中的稀疏句子标记化错误

    尝试对希伯来语使用稀疏句子标记 import spacy nlp spacy load he doc nlp text sents list doc sents I get Warning no model found for he Onl
  • 将 numpy 数组写入文本文件的速度

    我需要将一个非常 高 的两列数组写入文本文件 而且速度非常慢 我发现如果我将数组改造成更宽的数组 写入速度会快得多 例如 import time import numpy as np dataMat1 np random rand 1000
  • Tweepy StreamListener 到 CSV

    我是 python 新手 我正在尝试开发一个应用程序 使用 Tweepy 和 Streaming API 从 Twitter 检索数据并将数据转换为 CSV 文件 问题是此代码不会创建输出 CSV 文件 也许是因为我应该将代码设置为在实现例
  • 当我在 Pandas 中使用 df.corr 时,我的一些列丢失了

    这是我的代码 import numpy as np import pandas as pd import seaborn as sns import matplotlib pyplot as plt data pd read csv dea
  • 根据开始列和结束列扩展数据框(速度)

    我有一个pandas DataFrame含有start and end列 加上几个附加列 我想将此数据框扩展为一个时间序列 从start值并结束于end值 但复制我的其他专栏 到目前为止 我想出了以下内容 import pandas as
  • 更改 Altair 中的构面标题位置?

    如何将方面标题 在本例中为年份 移动到每个图的上方 默认值似乎位于图表的一侧 这可以轻易改变吗 import altair as alt from vega datasets import data df data seattle weat
  • 如何将 self 传递给装饰器?

    我该如何通过self key下面进入装饰器 class CacheMix object def init self args kwargs super CacheMix self init args kwargs key func Cons
  • Python Fabric - 未找到主机。请指定用于连接的(单个)主机字符串:

    如何获取 找不到主机 请指定用于连接的 单个 主机字符串 面料如何解决 def bootstrap host ec2 54 xxx xxx xxx compute 1 amazonaws com env hosts host env use
  • Apache Spark 中的高效字符串匹配

    我使用 OCR 工具从屏幕截图中提取文本 每个大约 1 5 句话 然而 当手动验证提取的文本时 我注意到时不时会出现一些错误 鉴于文本 你好 我真的很喜欢 Spark 我注意到 1 像 I 和 l 这样的字母被 替换 2 表情符号未被正确提
  • Pandas 滚动窗口 Spearman 相关性

    我想使用滚动窗口计算 DataFrame 两列之间的 Spearman 和 或 Pearson 相关性 我努力了df corr df col1 rolling P corr df col2 P为窗口尺寸 但我似乎无法定义该方法 添加meth
  • 在Python中计算内存碎片

    我有一个长时间运行的进程 不断分配和释放对象 尽管正在释放对象 但 RSS 内存使用量会随着时间的推移而增加 如何计算发生了多少碎片 一种可能性是计算 RSS sum of allocations 并将其作为指标 即便如此 我该如何计算分母
  • 如何使用 paramiko 查看(日志)文件传输进度?

    我正在使用 Paramiko 的 SFTPClient 在主机之间传输文件 我希望我的脚本打印文件传输进度 类似于使用 scp 看到的输出 scp my file user host user host password my file 1
  • 使用 numpy 在 python 中执行最大方差旋转

    我正在研究矩阵的主成分分析 我已经找到了如下所示的组件矩阵 A np array 0 73465832 0 24819766 0 32045055 0 3728976 0 58628043 0 63433607 0 72617152 0 5
  • Unix 中的访问时间是多少

    我想知道访问时间是多少 我在网上搜索但得到了相同的定义 读 被改变 我知道与touch我们可以改变它 谁能用一个例子来解释一下它是如何改变的 有没有办法在unix中获取创建日期 时间 stat结构 The stat 2 结构跟踪所有文件日期
  • 如何将回溯/sys.exc_info() 值保存在变量中?

    我想将错误名称和回溯详细信息保存到变量中 这是我的尝试 import sys try try print x except Exception ex raise NameError except Exception er print 0 s
  • 处理大文件的最快方法?

    我有多个 3 GB 制表符分隔文件 每个文件中有 2000 万行 所有行都必须独立处理 任何两行之间没有关系 我的问题是 什么会更快 逐行阅读 with open as infile for line in infile 将文件分块读入内存
  • 缓存 Flask-登录 user_loader

    我有这个 login manager user loader def load user id None return User query get id 在我引入 Flask Principal 之前它运行得很好 identity loa

随机推荐

  • 隐藏 NULL 行组 JasperReports

    有没有办法在 Crosstab 中隐藏具有 NULL 值的行组 None
  • 是否有相当于 data.table::rleid 的 dplyr?

    data table提供了很好的便利功能 rleid对于游程编码 library data table DT data table grp rep c A B C A B c 2 2 3 1 2 value 1 10 rleid DT gr
  • 如何在 Weblogic 12c (12.1.3) 上部署 Spring Boot 应用程序?

    我正在尝试在 Weblogic 12c 12 1 3 上部署一个简单的 Spring Boot 应用程序 在开发人员环境中它确实可以完美运行 然而 当需要在战争中重新包装它并将其附加到耳朵上时 这似乎无法正确部署 这是堆栈跟踪
  • MySQL |如何让每个类别只选择一条记录?

    我有一个表 其中包含带有数据的记录 每个记录都属于一个类别 假设我有以下表格 ID Category Title Date 1 Cat 1 Ttl 1 2013 02 18 2 Cat 2 Ttl 2 2013 02 18 3 Cat 1
  • Laravel Pusher array_merge:期望参数 2 是一个数组,给定 null

    我正在按照 Pusher 的教程在网站上显示通知 一切都符合教程 但是当我尝试访问通知时 出现了这个特定的错误localhost 8000 test我不知道如何解决它 错误信息 预期结果 通知发送消息 输出 array merge 错误 相
  • SQL 视图中的动态架构名称

    我有两个数据集 一个是关于狗的数据 我的数据 第二个是匹配键的查找表 我无法控制这些数据 匹配的键定期更新 我想创建 Dog 数据集的视图 或实现相同目的的东西 它总是连接最近的匹配的键 此外 我需要能够内联引用它 就好像它是一张桌子一样
  • 请求期间出现意外异常

    我在用着apache cassandra 2 2 4 我有一个 4 四 节点集群 DC1 中的复制因子为 3 DC2 中的复制因子 1 其中 DC1 包含 3 三 个节点 DC2 包含 1 一 个节点 该集群中之前还有一些节点 但由于某种原
  • 使用SAF(存储访问框架)的Android SD卡写入权限

    经过大量关于如何在 SD 卡 android 5 及更高版本 中写入 和重命名 文件的发现后 我认为 android 提供的新 SAF 将需要获得用户的许可才能写入 SD 卡文件 我在这个文件管理器应用程序中看到ES文件浏览器最初它会按照以
  • 监视调用 Jest 中另一个函数的导入函数

    我试图监视由另一个函数调用的函数 这两个函数都驻留在外部文件中并导入 Funcs spec js import as Funcs from Funcs describe funcA gt it calls funcB gt jest spy
  • 如何制作这样的回归树?

    我想制作一棵如图所示的回归树 这棵树是用 Cubist 完成的 但我没有那个程序 我确实使用 R 和 Python 它似乎与 R 包 rpart 或 tree 不同 因为末端节点是线性公式而不仅仅是平均值 有什么办法可以使用 R 或其他一些
  • ASP.NET LinkBut​​ton OnClick 事件在主页上不起作用

    我有一个用户控件来处理用户登录到我的网站的情况 该用户控件作为快速登录框放置在所有页面的右上角 我遇到的问题是 在我的生产服务器上 我为登录和重置提供的 LinkBut ton 单击事件在回发后不会触发 OnClick 事件 就像它只是忘记
  • 使用 python 正则表达式删除括号之间的内容

    我有一个文本文件 例如 a abc b c d 我想删除这些括号之间的内容 and 所以输出应该是 abc 我删除了括号之间的内容 但无法删除这之间的内容 我试过下面的代码 import re with open data txt as f
  • Mac 上的 VBA (Excel) 词典?

    我有一个 Excel VBA 项目 该项目大量使用 Windows 脚本字典对象 我最近有一位用户尝试在 Mac 上使用它并收到以下错误 Compile Error Can t find project or library 这是使用的结果
  • 在网页上加载 javascript 时输入意外结束

    我的 输入意外结束 的情况与我迄今为止在 SO 和其他论坛中发现的所有情况有所不同 坦白说 我被困住了 我在 CentOS 机器上安装了 JBoss EAP 6 4 尝试在 Windows 7 机器上打开管理控制台 MS 的所有最新补丁 都
  • 为什么 emplace_back() 会有这样的行为?

    为什么 Base 在调用 emplace back 之后立即被调用 为什么在析构函数调用后可以访问 sayHello 为什么 Base 再次被调用 include
  • IE11 Selenium WebDriverException:无法导航。 (org.openqa.selenium.WebDriverException ...IWebBrowser2::Navigate2() 失败

    我是自动化测试新手 虽然我的 Selenium 测试在 Chrome 和 Firefox 上运行 但它们没有在 IE11 上运行 我做了下面详细说明的所有检查 但我不断遇到此错误 org openqa selenium WebDriverE
  • 如何一次只打开 1 个手风琴

    accordion on click accordion control function e e preventDefault this next accordion panel not animated slideToggle ul c
  • 将 SMB 添加到 Windows,这有多安全?

    我遇到了一个小黑客 它声称它可以在 Windows 上启用 smb 抱怨是这样的事情 a href text a 没工作 虽然您确实可以在 url 中使用 file 但用户希望使用 smb 以便它是跨平台的 黑客攻击的过程如下 1 创建这个
  • MySQL IFNULL“N/A”产生“在集合中找不到项目”错误

    我一直在使用 ISNULL 函数在 mySQL 查询中将 NULL 值转换为零 如下所示 SELECT IFNULL mem comment count 0 FROM members 这很好用 我现在尝试使用 IFNULL 函数将 NULL
  • 使用 Python gtk3 在 X 上进行全局键绑定

    我正在寻找一些可以与 gtk3 一起使用的 python xlib 全局键绑定示例 就像为 gtk2 所做的那样http www siafoo net snippet 239 这里的代码非常相似 from Xlib display impo