Recently, I started working on a small user interact program, which collects user mouse click and does some action. I wonder how I can treat mouse as a device, alongside QT, and capture its left, right and middle click, and wrap up into this class.
class UserInputProcessor:
#abc.abstractmethod
def process_user_input(self):
pass
Here is what I did for a speech recognition for macrophone:
class SpeechProcessor(UserInputProcessor):
def __init__(self, json_file_path = "speech_processor.json", credentials = ".credentials.json"):
"""set up microphone """
self.process = True
def process_user_input(self):
with self.__microphone as source:
while self.process:
# Continuously wait for user input
self.__speech_converter_params["audio_data"] = None
while self.__speech_converter_params["audio_data"] == None:
try:
self.__speech_converter_params["audio_data"] = self.__recognizer.listen(
source,
phrase_time_limit = self.__config["phrase_time_limit"],
timeout = self.__config["timeout"])
except sr.WaitTimeoutError:
print("Timing out...")
yield self.__speech_converter_params["audio_data"]
# Process user speech with ASR
str = self.__speech_converter(**self.__speech_converter_params)
yield str
This is what I found for mouse input
selector = selectors.DefaultSelector()
mouse = evdev.InputDevice('/dev/input/event16')
keybd = evdev.InputDevice('/dev/input/event11')
# This works because InputDevice has a `fileno()` method.
selector.register(mouse, selectors.EVENT_READ)
selector.register(keybd, selectors.EVENT_READ)
while True:
for key, mask in selector.select():
device = key.fileobj
for event in device.read():
if event.value == 589825:
print ("left button")
elif event.value == 589827:
print ("middle button")
elif event.value == 589826:
print ("right button")
Related
I'm creating a script to monitor my mouse/keyboard activity. The intent is to update a timestamp whenever I move the mouse or press a button the keyboard. I've threaded the mouse and keyboard check methods and the main thread will check if we have passed the timeout/inactive duration and click at a specified location if we have.
Unfortunately, the keyboard monitoring is not working. The pynput.keyboard.Listener object seems to never join().
I'm not particularly comfortable with multithreading but I think I need it for this script. Please share a better way if there is one. I want to be able to run this script/class as a thread in another script later.
from pynput.keyboard import Listener
import pyautogui as gui
import threading, time
from datetime import datetime, timedelta
class activity(threading.Thread):
def __init__(self, timeout: int = 60):
self.stop_flag = False
self.timeout = timedelta(seconds=timeout)
self.last_timestamp = datetime.now()
def update_timestamp(self):
self.last_timestamp = datetime.now()
print('timestamp updated')
# For monitoring if the keyboard is active
def keybd_monitoring(self, lock: threading.Lock) -> None:
with Listener(on_release=lambda x: True) as listener:
listener.join()
lock.acquire()
self.update_timestamp()
lock.release()
print('Keyboard pressed')
if self.stop_flag:
return
# For monitoring if the mouse is active
def mouse_monitoring(self, lock: threading.Lock) -> None:
last_position = gui.position()
while not self.stop_flag:
time.sleep(3)
curr_position = gui.position()
if last_position != curr_position:
last_position = curr_position
lock.acquire()
self.update_timestamp()
lock.release()
print('Mouse Moved')
def stop(self):
self.stop_flag = True
# For monitoring if the mouse/keyboard have been used in the last TIMEOUT seconds
def run(self):
try:
width, height = gui.size()
lock = threading.Lock()
mouse = threading.Thread(target=self.mouse_monitoring, args=(lock,))
keybd = threading.Thread(target=self.keybd_monitoring, args=(lock,))
mouse.start()
keybd.start()
while not self.stop_flag:
time.sleep(.1)
if datetime.now() > self.last_timestamp + self.timeout:
curr_position = gui.position()
gui.click(int(width*.6),height)
gui.moveTo(curr_position)
finally:
self.stop()
if mouse.is_alive():
mouse.join()
if keybd.is_alive():
keybd.join()
if __name__ == '__main__':
act = activity()
act.run()
I've made it work without the monitoring functions being in a class. I'm still curious if it could work within a class.
from pynput.keyboard import Listener
import pyautogui as gui
import threading, time
from datetime import datetime, timedelta
stop_flag = False
timeout = timedelta(seconds=60)
last_timestamp = datetime.now()
lock = threading.Lock()
def update_timestamp(key=None):
lock.acquire()
global last_timestamp
last_timestamp = datetime.now()
lock.release()
return stop_flag
# For monitoring if the keyboard is active
def keybd_monitoring(lock: threading.Lock) -> None:
with Listener(on_release=update_timestamp) as listener:
listener.join()
# For monitoring if the mouse is active
def mouse_monitoring(lock: threading.Lock) -> None:
last_position = gui.position()
while not stop_flag:
time.sleep(3)
curr_position = gui.position()
if last_position != curr_position:
last_position = curr_position
update_timestamp()
def stop():
global stop_flag
stop_flag = True
# For monitoring if the mouse/keyboard have been used in the last TIMEOUT seconds
def activity():
try:
width, height = gui.size()
mouse = threading.Thread(target=mouse_monitoring, args=(lock,))
keybd = threading.Thread(target=keybd_monitoring, args=(lock,))
mouse.start()
keybd.start()
while not stop_flag:
time.sleep(1)
if datetime.now() > last_timestamp + timeout:
curr_position = gui.position()
gui.click(int(width*.6),height)
gui.moveTo(curr_position)
update_timestamp()
finally:
stop()
if mouse.is_alive():
mouse.join()
if keybd.is_alive():
keybd.join()
if __name__ == '__main__':
activity()
How to indeterminately record user's audio, if and only if when the user press ctrl key and shut down the recording loop when the user press ctrl+c keys? So far based on some online examples build this script:
from pynput import keyboard
import time, os
import pyaudio
import wave
import sched
import sys
from playsound import playsound
CHUNK = 8192
FORMAT = pyaudio.paInt16
CHANNELS = 2
RATE = 44100
WAVE_OUTPUT_FILENAME = "mic.wav"
p = pyaudio.PyAudio()
frames = []
def callback(in_data, frame_count, time_info, status):
frames.append(in_data)
return (in_data, pyaudio.paContinue)
class MyListener(keyboard.Listener):
def __init__(self):
super(MyListener, self).__init__(self.on_press, self.on_release)
self.key_pressed = None
self.wf = wave.open(WAVE_OUTPUT_FILENAME, 'wb')
self.wf.setnchannels(CHANNELS)
self.wf.setsampwidth(p.get_sample_size(FORMAT))
self.wf.setframerate(RATE)
def on_press(self, key):
try:
if key.ctrl:
self.key_pressed = True
return True
except AttributeError:
sys.exit()
def on_release(self, key):
if key.ctrl:
self.key_pressed = False
return True
listener = MyListener()
listener.start()
started = False
stream = None
def recorder():
global started, p, stream, frames
while True:
try:
if listener.key_pressed and not started:
# Start the recording
try:
stream = p.open(format=FORMAT,
channels=CHANNELS,
rate=RATE,
input=True,
frames_per_buffer=CHUNK,
stream_callback = callback)
print("Stream active:", stream.is_active())
started = True
print("start Stream")
except KeyboardInterrupt:
print('\nRecording finished: ' + repr(WAVE_OUTPUT_FILENAME))
quit()
elif not listener.key_pressed and started:
print("Stop recording")
listener.wf.writeframes(b''.join(frames))
listener.wf.close()
print("You should have a wav file in the current directory")
print('-> Playing recorded sound...')
playsound(str(os.getcwd())+'/mic.wav')
os.system('python "/Users/user/rec.py"')
except KeyboardInterrupt:
print('\nRecording finished: ' + repr(WAVE_OUTPUT_FILENAME))
quit()
except AttributeError:
quit()
print ("-> Press and hold the 'ctrl' key to record your audio")
print ("-> Release the 'ctrl' key to end recording")
recorder()
The problem is that it is really inefficient, for example the computer starts heating up. The only way I found to make the program keep running and recording different audio samples was with: os.system('python "/Users/user/rec.py"'). For finishing the program I tried to either catch the exception with:
except AttributeError:
sys.exit()
or with the user input:
if key.ctrl_c:
sys.exit()
Based on pyinput docs, I tried to make effective usage of the listeners. However, for this specific scenario which is the recommended way of using those listeners?
As to the primary concern of your computer seeming to work terribly hard, that's because you use a while loop to constantly check for when the record key is released. Within this loop, the computer will loop around as fast as it can without ever taking a break.
A better solution is to use event driven programming where you let the OS inform you of events periodically, and check if you want to do anything when they happen. This may sound complicated, but fortunately pynput does most of the hard work for you.
If you keep track of the state of the recording or playback, it is also fairly simple to start a new recording the next time a control key down event happens without needing the "hack" of calling an entire new process recursively for each new recording. The event loop inside the keyboard listener will keep on going until one of the callback functions returns False or raises self.stopException().
I have created a simple listener class similar to your initial attempt that calls on a recorder or player instance (which I'll get to later) to start and stop. I also have to agree with Anwarvic that <ctl-c> is supposed to be reserved as an emergency way of stopping a script, so I have changed the stop command to the letter q.
class listener(keyboard.Listener):
def __init__(self, recorder, player):
super().__init__(on_press = self.on_press, on_release = self.on_release)
self.recorder = recorder
self.player = player
def on_press(self, key):
if key is None: #unknown event
pass
elif isinstance(key, keyboard.Key): #special key event
if key.ctrl and self.player.playing == 0:
self.recorder.start()
elif isinstance(key, keyboard.KeyCode): #alphanumeric key event
if key.char == 'q': #press q to quit
if self.recorder.recording:
self.recorder.stop()
return False #this is how you stop the listener thread
if key.char == 'p' and not self.recorder.recording:
self.player.start()
def on_release(self, key):
if key is None: #unknown event
pass
elif isinstance(key, keyboard.Key): #special key event
if key.ctrl:
self.recorder.stop()
elif isinstance(key, keyboard.KeyCode): #alphanumeric key event
pass
if __name__ == '__main__':
r = recorder("mic.wav")
p = player("mic.wav")
l = listener(r, p)
print('hold ctrl to record, press p to playback, press q to quit')
l.start() #keyboard listener is a thread so we start it here
l.join() #wait for the tread to terminate so the program doesn't instantly close
With that structure, we then need a recorder class with a start and stop function which will not block (asynchronous) the listener thread from continuing to receive key events. The documentation for PyAudio gives a pretty good example for asynchronous output, so I simply applied it to an input. A little bit of re-arranging, and a flag to let our listener know when we're recording later, and we have a recorder class:
class recorder:
def __init__(self,
wavfile,
chunksize=8192,
dataformat=pyaudio.paInt16,
channels=2,
rate=44100):
self.filename = wavfile
self.chunksize = chunksize
self.dataformat = dataformat
self.channels = channels
self.rate = rate
self.recording = False
self.pa = pyaudio.PyAudio()
def start(self):
#we call start and stop from the keyboard listener, so we use the asynchronous
# version of pyaudio streaming. The keyboard listener must regain control to
# begin listening again for the key release.
if not self.recording:
self.wf = wave.open(self.filename, 'wb')
self.wf.setnchannels(self.channels)
self.wf.setsampwidth(self.pa.get_sample_size(self.dataformat))
self.wf.setframerate(self.rate)
def callback(in_data, frame_count, time_info, status):
#file write should be able to keep up with audio data stream (about 1378 Kbps)
self.wf.writeframes(in_data)
return (in_data, pyaudio.paContinue)
self.stream = self.pa.open(format = self.dataformat,
channels = self.channels,
rate = self.rate,
input = True,
stream_callback = callback)
self.stream.start_stream()
self.recording = True
print('recording started')
def stop(self):
if self.recording:
self.stream.stop_stream()
self.stream.close()
self.wf.close()
self.recording = False
print('recording finished')
Finally, we create an audio player for audio playback when you press p. I threw the PyAudio example into a thread which is created everytime you press the button so that multiple players could be created which overlap eachother. We also keep track of how many players are playing so we don't try to record while the file is already in use by a player. (I also have included my imports at the top)
from threading import Thread, Lock
from pynput import keyboard
import pyaudio
import wave
class player:
def __init__(self, wavfile):
self.wavfile = wavfile
self.playing = 0 #flag so we don't try to record while the wav file is in use
self.lock = Lock() #muutex so incrementing and decrementing self.playing is safe
#contents of the run function are processed in another thread so we use the blocking
# version of pyaudio play file example: http://people.csail.mit.edu/hubert/pyaudio/#play-wave-example
def run(self):
with self.lock:
self.playing += 1
with wave.open(self.wavfile, 'rb') as wf:
p = pyaudio.PyAudio()
stream = p.open(format=p.get_format_from_width(wf.getsampwidth()),
channels=wf.getnchannels(),
rate=wf.getframerate(),
output=True)
data = wf.readframes(8192)
while data != b'':
stream.write(data)
data = wf.readframes(8192)
stream.stop_stream()
stream.close()
p.terminate()
wf.close()
with self.lock:
self.playing -= 1
def start(self):
Thread(target=self.run).start()
I can't guarantee this is perfectly free of bugs, but if you have any questions on how it works / how to get it working, feel free to comment.
I really don't recommend using ctrl + c for anything rather than interrupting. Also, I don't recommend to keep pressing on a certain button to keep recording. What I suggest is to use a key to record and another to stop; That's why in the following code I've used s to start recording and q to quit. It's totally configurable and you can change based on your preference:
Install Dependencies
pip install pyaudio numpy scipy
sudo pip install keyboard
Recorder
A simple class to record from your mic:
import pyaudio
import keyboard
import numpy as np
from scipy.io import wavfile
class Recorder():
def __init__(self, filename):
self.audio_format = pyaudio.paInt16
self.channels = 1
self.sample_rate = 44100
self.chunk = int(0.03*self.sample_rate)
self.filename = filename
self.START_KEY = 's'
self.STOP_KEY = 'q'
def record(self):
recorded_data = []
p = pyaudio.PyAudio()
stream = p.open(format=self.audio_format, channels=self.channels,
rate=self.sample_rate, input=True,
frames_per_buffer=self.chunk)
while(True):
data = stream.read(self.chunk)
recorded_data.append(data)
if keyboard.is_pressed(self.STOP_KEY):
print("Stop recording")
# stop and close the stream
stream.stop_stream()
stream.close()
p.terminate()
#convert recorded data to numpy array
recorded_data = [np.frombuffer(frame, dtype=np.int16) for frame in recorded_data]
wav = np.concatenate(recorded_data, axis=0)
wavfile.write(self.filename, self.sample_rate, wav)
print("You should have a wav file in the current directory")
break
def listen(self):
print(f"Press `{self.START_KEY}` to start and `{self.STOP_KEY}` to quit!")
while True:
if keyboard.is_pressed(self.START_KEY):
self.record()
break
To use this class, simply call the listen() method like so:
recorder = Recorded("mic.wav") #name of output file
recorder.listen()
Try this:
import sys
try:
#Your code here
except KeyboardInterrupt:
sys.exit()
Hello,
I have the below code that corrects user input and I want to exit the blocking function keyboard.read_event when the control is returned from the correction thread.
The whole program works well but I cannot exit immediately after the corrector thread is finished (the program waits for key press).
I tried using a custom Exception for interrupting the keyboard.read_event function, but I didn't manage to make it work.
import keyboard
import threading
import time
class Interrupt_Custom_Exception(Exception):
"""Base class for other exceptions"""
pass
#########################################################
def delete_and_write(times_to_delete, word_to_write):
print("------------Deleting & Rewrite Started---")
time.sleep(2)
print("------------Deleting & Rewrite Ended---")
# simulate deletion and rewrite
#**here I tried the raise Interrupt_Custom_Exception and tried to catch it at the code in the class, but didn't work**
def write_the_suppressed_string(string):
keyboard.write(string)
#########################################################
class keyboard_monitor(threading.Thread):
def __init__(self,thread_name, threadID, word_typed, keyboard_suppress, counter_for_key_pressed):
threading.Thread.__init__(self)
self.name = thread_name
self.threaID = threadID
self.fstring = word_typed
self.counter_for_key_presses = counter_for_key_pressed
self.suppressed = keyboard_suppress
self.temp = ""
def stop(self):
self._is_running = False
def run(self):
if (self.suppressed is False):
while(True):
event = keyboard.read_event(suppress = self.suppressed)
if (event.event_type == keyboard.KEY_DOWN):
if (event.name == "space"):
suppressed_monitor = keyboard_monitor("suppressed_monitor", 2, self.fstring, True, self.counter_for_key_presses)
suppressed_monitor.start()
suppressed_monitor.join()
print("RETURNED TO MAIN MONITOR")
self.counter_for_key_presses = 0
self.fstring = ""
elif (event.name in "abcdefghijklmnopqrstuvwxyz"):
self.fstring = ''.join([self.fstring, event.name])
self.counter_for_key_presses += 1
elif (self.suppressed is True):
def listen_to_keyboard():
event = keyboard.read_event(suppress=self.suppressed)
# **here is where the program waits and don't continue when the correction thread is finished.**
if (event.event_type == keyboard.KEY_DOWN):
print("---KEYS PRESSED WHILE SUPPRESSED = {}---".format(event.name))
if (event.name in "abcdefghijklmnopqrstuvwxyz"):
self.fstring = ''.join([self.fstring, event.name])
self.counter_for_key_presses += 1
try:
#########################################################
# INITIALY CORRECTING THE WORD PASSED FROM THE NORMAL KEY MONITOR
self.temp = self.fstring
self.fstring = ""
thread_delete_and_rewrite = threading.Thread(
target = delete_and_write, args=(self.counter_for_key_presses, self.temp))
thread_delete_and_rewrite.start()
# raise Interrupt_Custom_Exception
#########################################################
print("-BEFORE WHILE LOOP-")
while(thread_delete_and_rewrite.is_alive() is True): # **this works ok but if the control enters the listen_to_keyboard function waits there until a key is pressed. I want somehow to stop this manually and continue the code after this while**
print("--ENTERING THE WHILE LOOP--")
listen_to_keyboard()
print("----EXITING THE WHILE LOOP----\n")
except Interrupt_Custom_Exception:
print("!!!!!!!!!!!!!!!!!CAUGHT IT!!!!!!!!!!!!!!!!!!!")
print("----EXITING THE WHILE LOOP----\n")
print("------BEFORE FINAL WRITE------")
if (self.fstring != ""):
thread_write = threading.Thread(
target = write_the_suppressed_string, args=(self.fstring, ))
thread_write.start()
thread_write.join()
print("SUPPRESSED ENDED")
self._is_running = False
if __name__ == "__main__":
kb_not_suppressed = keyboard_monitor("not_suppressed_monitor", 1, "", False, 0)
kb_not_suppressed.start()
kb_not_suppressed.join()
Any idea on how to exit this blocking function would be very very useful.
Thanks in advance.
It's not possible unless you find some keyboard.read_event that has a timeout, or does a non-blocking check if there's a event. I haven't found any of those in keyboard module ;/
A big workaround would be to keyboard.press in case you want to exit. Not sure if you can detect if it's not from the user. It's up to you if it's acceptable.
I use pyautogui to test much of the functionality in my python application. It seems to work fine for left mouse down, up, and right down and up, as well as key down, and keyup. However, when I am see no mouse events for the mouse move calls.
Why not?
How can I test the functionality of my software that has hooked into mouse move events?
import pyHook
import threading
import win32con
import pythoncom
import time
import pyautogui
class WindowsHooksWrapper(object):
"""
Provides a means to subscribe to keyboard and mouse events via Windows Hooks
It is important to note that:
* A thread specific hook (one that is not injected via dll injection) must be registered on the
same thread with the windows msg pump or it will not work and no indication of error is given
"""
def __init__(self):
self.consuming_keyboard_events = False
self.consuming_mouse_events = False
self.hook_manager = None
self.started = False
self.thread = threading.Thread(target=self.thread_proc)
def __del__(self):
self.stop()
def start(self):
if self.started:
self.stop()
self.started = True
self.thread.start()
def stop(self):
if not self.started:
return
self.started = False
self.thread.join()
def consume_mouse_events(self, should_consume_events):
"""
Tell the windows hooks wrapper to consume mouse events or not.
Consumed events will not be passed to other hooks or the process they were intended for.
Injected events will be passed on.
:param should_consume_events: set to True to consume mouse events. Otherwise, False
"""
if should_consume_events:
print 'Consuming mouse events'
else:
print 'No longer consuming mouse events'
self.consuming_mouse_events = should_consume_events
def consume_keyboard_events(self, should_consume_events):
"""
Tell the windows hooks wrapper to consume keyboard events or not.
Consumed events will not be passed to other hooks or the process they were intended for.
Injected events will be passed on.
:param should_consume_events: set to True to consume keyboard events. Otherwise, False
"""
if should_consume_events:
print 'Consuming keyboard events'
else:
print 'No longer consuming keyboard events'
self.consuming_keyboard_events = should_consume_events
def on_keyboard_event(self, event):
"""
Called back from pyHooks library on a keyboard event
:param event: event passed from pyHooks
:return: True if we are to pass the event on to other hooks and the process it was intended
for. False to consume the event.
"""
# Provide a means to stop consuming events while we are consuming all input
if event.KeyID == win32con.VK_ESCAPE:
self.consuming_keyboard_events = False
self.consuming_mouse_events = False
# Consume the event
print 'Escape key hit. Turning input blocking off.'
return False
if not self.consuming_keyboard_events or event.Injected:
print 'MessageName:', event.MessageName
print 'Message:', event.Message
print 'Time:', event.Time
print 'Window:', event.Window
print 'WindowName:', event.WindowName
print 'Ascii:', event.Ascii, chr(event.Ascii)
print 'Key:', event.Key
print 'KeyID:', event.KeyID
print 'ScanCode:', event.ScanCode
print 'Extended:', event.Extended
print 'Injected:', event.Injected
print 'Alt', event.Alt
print 'Transition', event.Transition
print '---'
# Send the event to other handlers and its target
return True
else:
# Consume the event. Any other hooks will not receive the event, nor will the process
# the event was intended for.
print 'Consumed keyboard event'
return False
def on_mouse_event(self, event):
"""
Called back from pyHooks library on a mouse event
:param event: event passed from pyHooks
:return: True if we are to pass the event on to other hooks and the process it was intended
for. False to consume the event.
"""
if not self.consuming_mouse_events or event.Injected:
# Send the event to pub sub
print 'MessageName:', event.MessageName
print 'Message:', event.Message
print 'Time:', event.Time
print 'Window:', event.Window
print 'WindowName:', event.WindowName
print 'Position:', event.Position
print 'Wheel:', event.Wheel
print 'Injected:', event.Injected
print '---'
# Send the event to other handlers and its target
return True
else:
# Consume the event. Any other hooks will not receive the event, nor will the process
# the event was intended for.
print 'Consumed mouse event'
return False
def thread_proc(self):
print "Thread started"
# Evidently, the hook must be registered on the same thread with the windows msg pump or
# it will not work and no indication of error is seen
# Also note that for exception safety, when the hook manager goes out of scope, the
# documentation says that it unregisters all outstanding hooks
self.hook_manager = pyHook.HookManager()
self.hook_manager.KeyAll = self.on_keyboard_event
self.hook_manager.HookKeyboard()
self.hook_manager.MouseAll = self.on_mouse_event
self.hook_manager.HookMouse()
while self.started:
pythoncom.PumpWaitingMessages()
print "Thread exiting..."
self.hook_manager.UnhookKeyboard()
self.hook_manager.UnhookMouse()
self.hook_manager = None
def main():
hook_wrapper = WindowsHooksWrapper()
hook_wrapper.start()
hook_wrapper.consume_keyboard_events(True)
hook_wrapper.consume_mouse_events(True)
pyautogui.moveTo(100, 50)
pyautogui.moveTo(200, 200)
time.sleep(30)
hook_wrapper.stop()
if __name__ == "__main__":
main()
Expected to see mouse move print out in the output.
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
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 <ozcanesen#gmail.com>
# Copyright (C) 2008 Luca Bruno <lethalman88#gmail.com>
#
# 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()