Global keybinding on X using Python gtk3
Asked Answered
P

1

2

I was looking for some example of python xlib global keybinding that would work with gtk3, just as it is done for gtk2 at http://www.siafoo.net/snippet/239. Very similar code here:

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()

Unfortunately I didn't find one, thus I decided to reimplement it to use gtk3 (ubuntu 12.04). Below is the result. It does not have runtime errors, but unfortunatelly it does not grab any input.

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()

Maybe You have some ideas how to make it work?

best regards, Paul

Pachyderm answered 17/5, 2012 at 17:34 Comment(0)
F
3

Hey i implemented same code and working great, you can try this. but there is no warranty. If you find missing parts please tell me.

# -*- 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()
Finalism answered 16/2, 2013 at 6:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.