Detect key press combination in Linux with Python? - python

I'm trying to capture key presses so that when a given combination is pressed I trigger an event.
I've searched around for tips on how to get started and the simplest code snippet I can find is in Python - I grabbed the code below for it from here. However, when I run this from a terminal and hit some keys, after the "Press a key..." statement nothing happens.
Am I being stupid? Can anyone explain why nothing happens, or suggest a better way of achieving this on Linux (any language considered!)?
import Tkinter as tk
def key(event):
if event.keysym == 'Escape':
root.destroy()
print event.char
root = tk.Tk()
print "Press a key (Escape key to exit):"
root.bind_all('<Key>', key)
# don't show the tk window
root.withdraw()
root.mainloop()

Tk does not seem to get it if you do not display the window. Try:
import Tkinter as tk
def key(event):
if event.keysym == 'Escape':
root.destroy()
print event.char
root = tk.Tk()
print "Press a key (Escape key to exit):"
root.bind_all('<Key>', key)
# don't show the tk window
# root.withdraw()
root.mainloop()
works for me...

What you're doing is reading /dev/tty in "raw" mode.
Normal Linux input is "cooked" -- backspaces and line endings have been handled for you.
To read a device like your keyboard in "raw" mode, you need to make direct Linux API calls to IOCTL.
Look at http://code.activestate.com/recipes/68397/ for some guidance on this. Yes, the recipe is in tcl, but it gives you a hint as to how to proceed.

Alternatively (a non-Python option) use XBindKeys.

Well, turns out there is a much simpler answer when using GNOME which doesn't involve any programming at all...
http://www.captain.at/howto-gnome-custom-hotkey-keyboard-shortcut.php
Archived on Wayback
Just create the script/executable to be triggered by the key combination and point the 'keybinding_commands' entry you create in gconf-editor at it.
Why didn't I think of that earlier?

tkinter 'bind' method only works when tkinter window is active.
If you want binding keystrokes combinations that works in all desktop (global key/mouse binding) you can use bindglobal (install with pip install bindglobal). It works exactly like standard tkinter 'bind'.
Example code:
import bindglobal
def callback(e):
print("CALLBACK event=" + str(e))
bg = bindglobal.BindGlobal()
bg.gbind("<Menu-1>",callback)
bg.start()

Related

Capture all keypresses of the system with Tkinter

I'm coding a little tool that displays the key presses on the screen with Tkinter, useful for screen recording.
Is there a way to get a listener for all key presses of the system globally with Tkinter? (for every keystroke including F1, CTRL, ..., even when the Tkinter window does not have the focus)
I currently know a solution with pyHook.HookManager(), pythoncom.PumpMessages(), and also solutions from Listen for a shortcut (like WIN+A) even if the Python script does not have the focus but is there a 100% tkinter solution?
Indeed, pyhook is only for Python 2, and pyhook3 seems to be abandoned, so I would prefer a built-in Python3 / Tkinter solution for Windows.
Solution 1: if you need to catch keyboard events in your current window, you can use:
from tkinter import *
def key_press(event):
key = event.char
print(f"'{key}' is pressed")
root = Tk()
root.geometry('640x480')
root.bind('<Key>', key_press)
mainloop()
Solution 2: if you want to capture keys regardless of which window has focus, you can use keyboard
As suggested in tkinter using two keys at the same time, you can detect all key pressed at the same time with the following:
history = []
def keyup(e):
print(e.keycode)
if e.keycode in history :
history.pop(history.index(e.keycode))
var.set(str(history))
def keydown(e):
if not e.keycode in history :
history.append(e.keycode)
var.set(str(history))
root = Tk()
root.bind("<KeyPress>", keydown)
root.bind("<KeyRelease>", keyup)

How can I close a Python program properly when in an IDE?

From what I understand, using sys.exit is not compatible with PyCharm and other IDEs, yet other exit methods that don't do garbage collection are not recommended. If I'm developing in PyCharm how can I properly shut down the program?
In the below example I am attempting to use a hot key to exit the program, yet the test statement is never reached. Is this an incorrect syntax or related to using the wrong exit command?
#Import modules
from tkinter import * #For images
from PIL import Image, ImageTk #For images
import os #For working directory
from pynput import keyboard #For hotkeys
#Set default directory
os.chdir('C:\\Users\\UserName\\Desktop\\Python\\SomeFolderName')
#Display Menu
root = Tk()
image = Image.open('menu.png')
display = ImageTk.PhotoImage(image)
root.overrideredirect(True) #Remove toolbar
label = Label(root, image=display)
label.pack()
root.mainloop()
#Functions
def ExitProgram():
print('test')
sys.exit(0)
#Define hotkeys
with keyboard.GlobalHotKeys({
'<ctrl>+w': ExitProgram,
'<ctrl>+0': ExitProgram}) as h:
h.join()
Use the quit() function to exit the program. This is a built-in function from Python in order to exit the program.
Happy Coding!
Nothing past root.mainloop() will execute because it never returns. Move the with keyboard... code before root.mainloop().
This answer from a similar question might be a useful answer for you.
Python exit commands - why so many and when should each be used?
I actually use VS code and sys.exit(0) is fine, but can also use quit() which is also fine.
Use quit() or exit() to terminate your program.
quit() and exit() are synonymous and do the exact same thing.
In addition to calling sys.exit(), using quit() does some other internal cleanup, such as closing input streams.
They are briefly discussed in the official documentation here: https://docs.python.org/3/library/constants.html#quit

How to auto-activate a tkinter simpledialog pop-up window?

I have this function inside one of my python scripts which throws up a Tkinter simple dialog screen to ask for some simple user-input. The function works. However, there are 2 problems with it.
It opens up two windows, while all I need is one. But if I remove the master = Tk() I get the error:
AttributeError: 'NoneType' object has no attribute 'winfo_viewable'
It would be nice to at one point figure that one out, but my main problem however is the second one:
Whenever the simple dialog screen turns up, I have to click it first before it gets activated, which is annoying. To fix it I tried the solutions offered here and here but they do not work. The first link didn't do anything for me at all, the second link helped me to lift the master.Tk() window to the front, but that is not what I need. I need the simple dialog window to become the topmost window and I need it to be auto-activated so that when I run my code and the screen pops-up I can automatically type in it without having to click on it first.
Any help would be greatly appreciated!
My code:
def getUser():
master = Tk()
newList2=str(newList).replace(", ","\n")
for ch in ['[',']',"'"]:
if ch in newList2:
newList5=newList2.replace(ch,"")
userNr=simpledialog.askinteger("Enter user number", newList2)
chosenUsernr= userNr - 1
global chosenUsernrdef
chosenUsernrdef = chosenUsernr
master.destroy()
I don't think there is a way to lift/give focus to it but askinteger is merely a combination of couple widgets so you can easily recreate it yourself.
import tkinter as tk
from tkinter import messagebox
class CustomAskInteger(tk.Tk):
def __init__(self, numbers):
tk.Tk.__init__(self)
self.value = None
self.label = tk.Label(self, text=", ".join(map(str, numbers))).pack(fill="both", expand=True)
self.entry = tk.Entry(self)
self.button = tk.Button(self, text="Ok", command=self.get_number)
self.entry.pack()
self.button.pack()
def get_number(self):
"""
You can customize these error handlings as you like to
"""
if self.entry.get():
try:
int(self.entry.get())
self.value = self.entry.get()
self.destroy()
except ValueError:
messagebox.showwarning("Illegal Value", "Not an integer.\nPlease try again.")
else:
messagebox.showwarning("Illegal Value", "Not an integer.\nPlease try again.")
To use this in your code, you can do
def getUser():
newList2=str(newList).replace(", ","\n")
askInteger = CustomAskInteger("Enter user number", newList2)
#since it is a Tk() instance, you can do lift/focus/grab_set etc. on this
askInteger.lift()
askInteger.mainloop()
userNr = askInteger.value
First, credits to Lafexlos for showing a solution of how to apply .lift() and similar commands on a Tkinter simpledialog.askinteger() window by recreating such a window as a Tk() instance.
For those however looking how to automatically activate a Tk-window (so you do not have to click on it before being able to type in it), there appear to be multiple options.
The most common solution seems to be to use .focus() or .force_focus() as seen implemented here and here. However over here it seems those options may not work on (at least some versions of) Windows OS. This question shows a possible solution for those systems. Also, the previous solutions appear not to work on multiple versions of OS X. Based on the solution offered here, using Apple's osascript, I was able to solve my problem.
The working code eventually looks like this:
def getUser():
master = Tk()
newList2=str(newList).replace(", ","\n")
for ch in ['[',']',"'"]:
if ch in newList2:
newList2=newList2.replace(ch,"")
cmd = """osascript -e 'tell app "Finder" to set frontmost of process "Python" to true'"""
def stupidtrick():
os.system(cmd)
master.withdraw()
userNr=simpledialog.askinteger("Enter user number", newList2)
global chosenUsernrdef
chosenUsernr= userNr - 1
chosenUsernrdef = chosenUsernr
stupidtrick()
master.destroy()
Simplified / general solution:
import os
from tkinter import Tk
from tkinter import simpledialog
def get_user():
root = Tk()
cmd = """osascript -e 'tell app "Finder" to set frontmost of process "Python" to true'"""
def stupid_trick():
os.system(cmd)
root.withdraw()
new_window=simpledialog.askinteger("Title of window", "Text to show above entry field")
stupid_trick()
root.destroy()
get_user()
EDIT: Now I am figuring out what to look for the solution appears to be found already by multiple posts. For those on OS X wanting to activate a specific Tkinter window when multiple instances of Tkinter and/or python are running simultaneously, you might want to look here.

Tkinter - How to get Keypress anywhere on window?

I am trying to get keypresses in Python (2.7.10), bit I had no luck with getch(), as ord(getch()) was returning 255 constantly, so I am now using Tkinter. (I believe that Tkinter is also cross-platform so that should help as i plan to run this script on a Linux device).
I need to be able to get keypresses, even if they are not pressed while the Tkinter window is not active.
Here is my code:
from Tkinter import *
import time, threading
x = "Hi!"
def callback(event):
x = "key: " + event.char
print(x)
def doTk():
root = Tk()
root.bind_all("<Key>", callback)
root.withdraw()
root.mainloop()
thread1 = threading.Thread(target=doTk)
thread1.deamon = True
thread1.start()
I am not reveiving any errors, it is not not registering keypresses. I have also tried this without using threading, but it still does not work.
Please also note that I cannot use raw_input() as I need this to be able to run in the background and still get keypresses.
I am aware that this does not produce a frame, I do not want it to.
Thanks in advance for any help :)
PS: I have looked to other answers on StackOverflow and other sites, but they all either don't work or give solutions where keypresses are only registered when the tkinter frame is active.

Tkinter international bind

Is there a way in Tkinter to bind a combination of keys that will work in all keyboard layouts? (bind by scancode)
For example, I need 'Control-Z' binding that will work with the same physical key in the lower left corner of the keyboard in all layouts, such as:
* Russian layout,
* Greek layout, etc.
Here's what I tried:
from Tkinter import *
root=Tk()
def f(event):
print 'undo'
button1=Button(root, text=u'Button')
button1.pack()
button1.bind('<Control-z>', f)
root.mainloop()
It doesn't work for Russian and Greek keyboard layouts.
Update-2:
I did some more experiments with Windows and now the general rule is like that:
1) If the language is based on latin character set, keys are mapped "by value" (German, French, Dvorak) so that the same action is mapped to different physical keys.
2) If it is not (eg Russian, Greek), then all major accelerators are mapped "by position" (to match the corresponding English letter usually marked on the same key).
Only the second case needs special attention. Any ideas if this is implemented in some lib already?
Update-3
It is simply reproduced even without Russian keyboard or Russian Windows.
1) Start->Control Panel->Regional and Language Options
2) Language->Details
3) Add Russian language.
That's it. Now Alt-Shift will switch you to Russian and you'll be able to type the following funny symbols:
another Alt-Shift will switch you back.
Forget what Wikipedia says about phonetic Russian layouts. They are not used these days. At least inside Russia.
All Windows applications (including wxPython ones) use Ctrl-я for undo, Ctrl-ч for cut, Ctrl-с for copy and so on.
Thanks to #acw1668 for help!
You need to do something like this to use hotkeys with any language layout (the callback from this example is running when Control key is pressed, and prints the key that is pressed in the same time with Control:
from tkinter import *
def callback(event):
if (event.state & 4 > 0):
print("Ctrl-%s pressed" % chr(event.keycode))
root = Tk()
root.bind("<Key>", callback)
root.mainloop()
PS: This example was checked on Windows 10 when English, Ukrainian, Russian, Arabic, Amharic, Armenian, Greek, Georgian, French, Chinese, Japanese and other language layouts were used.
What I'm primarily interested in is Russian layout in Windows.
The quick and dirty workaround I currently use is:
import Tkinter
def copy(event):
print 'Ctrl-C'
root = Tkinter.Tk()
root.bind('<Control-ntilde>', copy)
root.mainloop()
which could potentially lead to a conflict with <Ctrl + actual ntilde> in some other language.
It could be overcome if I could determine which layout is currently active, thus second question: Tkinter determine keyboard layout.
Another drawback is that due to 'universal' treatment of modifier keys it also fires when I press Ctrl-Alt-V, but that's another story as it applies to English layout as well.
I have a partial and rather ugly solution for this. In the code below I have a window with Text widget, which have some "in-box" connection between standard Ctrl+C keyboard events and their proper handling. However, if I simply change the keyboard layout to, say, Russian, these functions do not work anymore. To solve the problem I re-wrote implementation for these events, and now everything works fine. But I feel slightly frustrated about such a solution. Does anyone have any better ideas?.. For instance, is there a way to trigger (or mimic) "normal" key press in python tkinter?
import tkinter
root = tkinter.Tk()
class MyWind (tkinter.Frame):
def __init__(self, parent):
tkinter.Frame.__init__(self, parent)
self.create_UI()
def create_UI(self):
text_field = tkinter.Text(self)
text_field.insert(tkinter.END, "Hello world")
text_field.pack()
def print_event(event):
print ("Event character code <char>: '%s'" % event.char)
print (" Event key symbol <keysym>: '%s'" % event.keysym)
print (" Event key code <keycode>: '%s'" % event.keycode)
def work_out_event(event): # Here is the solution
widget_type = type(event.widget)
if widget_type == tkinter.Text:
content = event.widget.selection_get()
print ("Selected content = '%s'" % content)
root.clipboard_clear()
root.clipboard_append(content)
def lurker1(event):
print ("Crtl + C (english) pressed!")
print_event(event)
def lurker2(event):
print ("Crtl + C (russian) pressed!")
print_event(event)
work_out_event(event)
root.bind("<Control-c>", lurker1) # "C" character with the english keyboard layout
root.bind("<Control-ntilde>", lurker2) # "C" character with the russian keyboard layout
root.app = MyWind(root)
root.app.pack()
root.mainloop()
Another option already suggested in the old 1999 is to switch from Tkinter to wxPython where accelerators handling is done for all types of keyboard layouts automatically (eg Editor example here: http://wiki.wxpython.org/AnotherTutorial).
def copy(event):
print 'Ctrl-C'
master.clipboard_append('text')
and it works!

Categories

Resources