2025, Oct 08 07:17

Super+V в Linux Mint 22.1: почему не срабатывает и как ловить через XRecord

Почему Super+V в Linux Mint 22.1 не срабатывает с первого раза в Python‑приложениях с Keybinder, и как слушать сочетание через XRecord без захвата. Надёжно.

Глобальные горячие клавиши в настольных окружениях Linux работают как надо — пока не появляется клавиша Super. Типичный сценарий: привязка Super+V в Python‑приложении должна вызывать обработчик, но вместо этого первый нажим вводит обычную «v» в активное текстовое поле. Другие сочетания, вроде Alt+F3, ведут себя предсказуемо. Ниже — разбор, почему так происходит в Linux Mint 22.1 Cinnamon и как надежно отлавливать Super+V, не отбирая захват у рабочего окружения.

Минимальный пример, который демонстрирует проблему

Код ниже привязывает Super+V через gi.repository Keybinder и держит Gtk‑приложение запущенным. При первом нажатии Super+V в активной области появляется строчная «v»; лишь последующее одиночное «v» запускает обработчик. Alt+F3 и похожие сочетания так себя не ведут.

import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Keybinder', '3.0')
from gi.repository import Keybinder, Gtk
class ClipService(Gtk.Application):
    def __init__(self):
        super().__init__(application_id="org.sample.keydemo", flags=0)
        self.connect("activate", self._on_activate)
        self.connect("startup", self._on_start)
    def _on_start(self, app):
        print("Application started.")
        self.hold()
    def _on_activate(self, app):
        pass
    def on_hotkey(self, user_data=None):
        print("Key was pressed!")
if __name__ == "__main__":
    svc = ClipService()
    Keybinder.init()
    Keybinder.bind("<Super>V", svc.on_hotkey)
    svc.run(None)

Что происходит с Super

Только один клиент может владеть глобальным захватом клавиш в каждый момент. В Linux Mint меню рабочего стола обычно использует Super (часто через Mod4) и забирает её в захват. Когда другая программа пытается захватить то же сочетание, X‑сервер отклоняет запрос — при прямом захвате через Xlib grab_key это заканчивается Xlib.error.BadAccess. Это согласуется с наблюдением: если отключить действие «показать меню», привязанное к Super_L, первый нажим Super+V уже срабатывает; а если назначить Super+V в Параметры клавиатуры → Сочетания, то первый нажим тоже проходит. Иными словами, захват уже у рабочего стола; ваш процесс ведёт себя непоследовательно, потому что он не владелец захвата для сочетаний с Super.

Другие модификаторы, вроде Alt+F3, обычно работают, потому что их не забирает меню, захват проходит, и колбэк вызывается как ожидается.

Практичный способ слушать без захвата: XRecord

Если другой компонент уже забрал Super, конкурировать с ним бессмысленно. Вместо этого слушайте пассивно. Расширение XRecord «зеркалит» события клавиатуры в ваш процесс без владения захватом, то есть вы можете обнаруживать Super+V даже когда окружение удерживает свои привязки. Так поступают зрелые инструменты вроде AutoKey, и это позволяет дополнительным слушателям сосуществовать.

Пример ниже использует python‑xlib с XRecord, чтобы определять Super+V и различать, какая Super нажата (левая или правая). Он сообщает о KeyPress и KeyRelease и продолжает работать, когда Super_L назначена на меню или когда Super+V привязан к функции в AutoKey. Ничего из этого не блокируется, потому что XRecord — это зеркало, а не захват.

from Xlib import X, XK, display
from Xlib.ext import record
from Xlib.protocol import rq
# открываем два соединения: одно для локальных запросов, второе — для рекордера
x_disp = display.Display()
x_rec = display.Display()
# целевые клавиши и маски
ks_v = XK.string_to_keysym('v')
kc_v = x_disp.keysym_to_keycode(ks_v)
mask_mod4 = X.Mod4Mask
# поиск кодов левой/правой Super
ks_super_l = XK.string_to_keysym("Super_L")
ks_super_r = XK.string_to_keysym("Super_R")
kc_super_l = x_disp.keysym_to_keycode(ks_super_l)
kc_super_r = x_disp.keysym_to_keycode(ks_super_r)
# парсер бинарного события
evt_field = rq.EventField(None)
def on_record(reply):
    if reply.category != record.FromServer:
        return
    if reply.client_swapped:
        return
    if not reply.data:
        return
    evt, _ = evt_field.parse_binary_value(reply.data, x_disp.display, None, None)
    if evt.type == X.KeyPress:
        if evt.detail == kc_v and (evt.state & mask_mod4):
            print("Super+V pressed")
        if evt.detail == kc_super_l:
            print("Super_L pressed")
        elif evt.detail == kc_super_r:
            print("Super_R pressed")
    if evt.type == X.KeyRelease:
        if evt.detail == kc_v and (evt.state & mask_mod4):
            print("Super+V released")
        if evt.detail == kc_super_l:
            print("Super_L released")
        elif evt.detail == kc_super_r:
            print("Super_R released")
# создаём и включаем контекст записи без захвата клавиш
ctx = x_rec.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.KeyPress, X.KeyRelease),
        'errors': (0, 0),
        'client_started': False,
        'client_died': False,
    }]
)
print("Listening for Super+V without grabbing...")
x_rec.record_enable_context(ctx, on_record)
x_rec.record_free_context(ctx)

Это было проверено на Linux Mint 22.1 (Xia) с Mate 1.26.2 и Python 3.13.

Важный нюанс: прослушивание не блокирует

Подход с XRecord лишь зеркалит события; он их не отменяет. Если фокус в текстовом поле, «v» всё равно будет доставлена системой, а ваш процесс увидит зеркальный Super+V. Так задумано и соответствует цели — сосуществовать с привязками рабочего стола. Если вам нужен эксклюзивный обработчик, придётся использовать путь на основе захвата, который конфликтует с тем, кто уже владеет тем же захватом.

Почему это важно

Интеграции с глобальной клавиатурой в Linux подчиняются политике окружения: оно забирает некоторые клавиши. Понимание разницы между API на основе захвата и «зеркальным» прослушиванием экономит время и избавляет от хрупких костылей. Это также объясняет, почему одни сочетания срабатывают сразу, а на Super — нет, и почему отключение или переназначение привязки Super для меню меняет поведение.

Итоги

Если Super+V уже занято окружением, не боритесь за захват. Используйте XRecord для пассивного прослушивания и сохраните отзывчивость рабочего стола. Если нужно срабатывать с первого нажатия с Super, перенастройте окружение так, чтобы оно не перехватывало ту же комбинацию, или задайте ярлык на уровне рабочего стола. Инструменты вроде AutoKey показывают, что зеркалирование через XRecord — практичный и надёжный способ наблюдать клавиши с модификатором Super, не вмешиваясь в работу системы.

Статья основана на вопросе со StackOverflow от AlxPav Chr и ответе от furas.