Global hotkey on windows with tkinter - python

I want a python tkinter application to register a global hotkey (triggered even if the application has not the focus). I've found some pieces, but I can't find a way to put them together...
Basically, I can register the hotkey (with a call to the windows API RegisterHotKey), but it send a "WM_HOTKEY" message to the root windows that is under Tkinter mainloop management, and I cant find a way to bind on it...
tk.protocol() function seems to be there for it, but this message seems not to be understood (I can't find a complete list of the recognized messages)...
Here a non-working code example where I would like to print a message when "WIN-F3" is pressed...
import Tkinter
import ctypes
import win32con
class App(Tkinter.Tk):
def __init__(self):
Tkinter.Tk.__init__(self)
user32 = ctypes.windll.user32
if user32.RegisterHotKey (None, 1, win32con.MOD_WIN , win32con.VK_F3):
print("hotkey registered")
else:
print("Cannot register hotkey")
self.protocol("WM_HOTKEY", self.hotkey_received)
def hotkey_received(self):
print("hotkey")
if __name__ == "__main__":
app = App()
app.mainloop()
try:
app.destroy()
except:
pass
Thanks
EDIT --------------------------------
OK, I found a way by pushing the whole windows loop in a separate thread, independent from the tkinter mainloop.
It doesn't feel like a clean solution though as I have actually 2 loops running for basically the same kind of interactions with the OS, and it require a message queue to interact with the application, but it do the trick...
If someone has a better option, I would be happy to see it...

A little bit late but maybe it would be helpful for someone... Read this answer.

Related

Python Os function called from Tkinter does not work when converted to .exe

Need your help lads and lasses!
I made a very simple hangman game with a GUI in Tkinter and created a button event that calls the following function to restart the game. This works fine as a Python script but does not work when converting script to .exe with Pyinstaller. ('Window" here is the root Tkinter window)
def restart_func():
window.destroy()
os.startfile("main.py")
return
Any ideas why or alternative way of doing this would be much appreciated.
you should recreate your window and initialize it the same way you originially did.
def restart_func():
global window
window.destroy()
window = tk.Tk()
run_gui_initialization_function()
this is usually why some applications prefer OOP instead of functional programming, as there is usually no global state in OOP programming, while for functional programming, your run_gui_initialization_function should reinitialize all of your global state variables again.
an alternative way people do this is as follows
def main():
# do main app here
if __name__ == "__main__":
main()
this way you just need to run main() again after deleting your window if you ever need to restart your app.
Edit: okay, here's an easier to implement but too messy way to do this, you can start a detached version of your application from your executable ... but this is the wrong way to restart an application, and it might fail in some cases.
from subprocess import Popen
import sys
def restart_func():
window.destroy()
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
Popen(sys.executable,start_new_session=True) # if running as executable
else:
os.startfile("main.py") # if running as python script

Tkinter mainloop in background

I want to run the tkinter mainloop in the background because I´m following the MVC Pattern, so the Controller gets a Interfaceobject to handle all cares. So I tried to thread it. RuntimeError: Calling Tcl from different apartment occures. Saw no further answers on this topic.
Does anyone have a clue what to do?
Thanks, Max
I don´t have the solution you want for this problem. Maybe this question is can show you what happens.
Anyway you can try by defining all tkinter objects inside the function like this:
from tkinter import Tk
import threading
def mainloop():
root = Tk()
#Your tkinter objects goes here
root.mainloop()
t = threading.Thread(target=mainloop)
t.start()
or you can run the mainloop without threading and thread the Controller:
from tkinter import Tk
import threading
root = Tk()
root.mainloop()
def controler():
while True:
pass
t = threading.Thread(target=controler)
t.start()
thanks #Tomás Gomez Pizarro for one solution. I myself came up with a solution. My Controller etc are set up with a reference(Object) to my Gui. the Gui itself, so the mainloop function is not called until everything is ready. So in Runtime every GuiEvent is passed to the Controller via a thread so that the main loop is not interferred with.
Regards, Max

Tcl_AsyncDelete Error. Unable to terminate Tk

I am using Tkinter in a ROS node to create a GUI and publish the scale values to another ROS Node. I have accomplished this. The problem comes when I try to close this GUI and rerun the node. The log message that I get is as follows:
Exception RuntimeError: 'main thread is not in main loop' in <bound method DoubleVar.__del__ of <Tkinter.DoubleVar instance at 0x7f19ea0c3ab8>> ignored
Tcl_AsyncDelete: async handler deleted by the wrong thread
Aborted (core dumped)
According to this, I think I will have to terminate Tk from its own thread. But I do not know how to do this. My code is as follows:
#!/usr/bin/env python
import rospy
from std_msgs.msg import Float64MultiArray
from Tkinter import *
from calibration_camera_lidar.msg import Euler_val
import tkMessageBox
class slider():
def __init__(self):
rospy.loginfo("init")
rospy.init_node('slider', anonymous=True, disable_signals=True)
self.spub = rospy.Publisher('Slider_values', Euler_val, queue_size=10)
self.final_ev = Euler_val()
self.listener()
def listener(self):
rospy.Subscriber("Euler_values", Float64MultiArray, self.callback)
rospy.spin()
def callback(self, data):
self.eulerval = list(data.data)
self.final_ev.Euler_angles = [self.eulerval[0], self.eulerval[1], self.eulerval[2]]
self.spub.publish(self.final_ev)
rospy.loginfo(self.final_ev)
self.slider_value()
def callback_exit(self):
if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"):
self.root.destroy()
self.root.quit()
rospy.signal_shutdown("shutdown")
def slider_value(self):
self.root = Tk()
self.root.title("fine tune")
self.root.protocol("WM_DELETE_WINDOW", self.callback_exit)
self.y_var = DoubleVar()
self.y_scale = Scale( self.root, from_=self.eulerval[0]-1, to=self.eulerval[0]+1, length=300, label="yaw", resolution=0.0000000000001, variable = self.y_var, orient=HORIZONTAL, command=self.pub_y)
self.y_scale.set(self.eulerval[0])
self.y_scale.pack(anchor=CENTER)
self.label = Label(self.root)
self.label.pack()
self.root.mainloop()
def pub_y(self, val_y):
self.eulerval[0] = float(self.y_scale.get())
self.final_ev.Euler_angles = [self.eulerval[0], self.eulerval[1], self.eulerval[2]]
self.spub.publish(self.final_ev)
rospy.loginfo(self.final_ev)
if __name__ == '__main__':
try:
slider()
except:
rospy.loginfo("Node terminated.")
I would be grateful if you could help. Thanks!
The problem is that rospy is internally multithreaded yet Tk is very keen on only being used from a single thread. (Technically, it's possible to use Tk from multiple threads — by appropriate quarantining of window objects and so on — but it's really tricky to get right and you probably don't want that.)
The easiest approach in general is to make two classes, one that just handles Tk (with incoming and outgoing messages all queued) and the other which does the bridging into the rest of the code. Then, when you want the Tk GUI to appear you run a thread that just does that and then talk to that thread just by its queues. Which sounds like a lot more work, but you can't defeat Tk's internal awareness of threads other than by keeping it strictly on one thread.
However, it might be enough to change the shutdown sequence a bit to be like this.
def callback_exit(self):
if tkMessageBox.askokcancel("Quit", "Do you really wish to quit?"):
self.root.destroy()
rospy.signal_shutdown("shutdown")
sys.exit(0)
Assuming that you're in the correct thread. If not, you'll need a direct os._exit(0) instead and that's considered dangerous for good reason (yet it might be necessary).

Interacting with a gtk.container while gtk.main() is executing?

Experimenting with a battery monitor icon at the moment in Python using pygtk and egg.trayicon to create an icon to display a battery icon/tooltip.
I seem to be able to add the icon and the tooltip text, but when it then reaches the gtk.main() stage I need a way to modify these so it can then show the updated values.
I've tried gobject.idle_add() and gobject.timeout_add() without much luck, not sure where to go from this.
Anyone got any ideas?
EDIT: Perhaps not the clearest of questions.
I need to loop, fetching information from acpi while running and apply it to widgets inside the gtk container.
EDIT 2: Ok, it's properly down now. The issue was that I wasn't returning anything inside my callback. I just gave it "return 123" and now it's happily chugging away in my system tray, notifying me of my battery percentage :)
This example works for me:
# -*- Mode: Python -*-
# vi:si:et:sw=4:sts=4:ts=4
import gobject
import gtk
from egg import trayicon
label = gtk.Label("Over here")
def callback(widget, ev):
label.set_text("You found me")
def timeout():
label.set_text("What are you waiting for?")
tray = trayicon.TrayIcon("TrayIcon")
box = gtk.EventBox()
box.add(label)
tray.add(box)
tray.show_all()
box.connect("button-press-event", callback)
gobject.timeout_add(3000L, timeout)
gtk.main()
Without seeing your code, it's hard to tell what doesn't work.

Alert boxes in Python?

Is it possible to produce an alert similar to JavaScript's alert("message") in python, with an application running as a daemon.
This will be run in Windows, Most likely XP but 2000 and Vista are also very real possibilities.
Update:
This is intended to run in the background and alert the user when certain conditions are met, I figure that the easiest way to alert the user would be to produce a pop-up, as it needs to be handled immediately, and other options such as just logging, or sending an email are not efficient enough.
what about this:
import win32api
win32api.MessageBox(0, 'hello', 'title')
Additionally:
win32api.MessageBox(0, 'hello', 'title', 0x00001000)
will make the box appear on top of other windows, for urgent messages. See MessageBox function for other options.
For those of us looking for a purely Python option that doesn't interface with Windows and is platform independent, I went for the option listed on the following website:
https://pythonspot.com/tk-message-box/ (archived link: https://archive.ph/JNuvx)
# Python 3.x code
# Imports
import tkinter
from tkinter import messagebox
# This code is to hide the main tkinter window
root = tkinter.Tk()
root.withdraw()
# Message Box
messagebox.showinfo("Title", "Message")
You can choose to show various types of messagebox options for different scenarios:
showinfo()
showwarning()
showerror ()
askquestion()
askokcancel()
askyesno ()
askretrycancel ()
edited code per my comment below
You can use PyAutoGui to make alert boxes
First install pyautogui with pip:
pip install pyautogui
Then type this in python:
import pyautogui as pag
pag.alert(text="Hello World", title="The Hello World Box")
Here are more message boxes, stolen from Javascript:
confirm()
With Ok and Cancel Button
prompt()
With Text Input
password()
With Text Input, but typed characters will be appeared as *
GTK may be a better option, as it is cross-platform. It'll work great on Ubuntu, and should work just fine on Windows when GTK and Python bindings are installed.
from gi.repository import Gtk
dialog = Gtk.MessageDialog(None, 0, Gtk.MessageType.INFO,
Gtk.ButtonsType.OK, "This is an INFO MessageDialog")
dialog.format_secondary_text(
"And this is the secondary text that explains things.")
dialog.run()
print "INFO dialog closed"
You can see other examples here. (pdf)
The arguments passed should be the gtk.window parent (or None), DestroyWithParent, Message type, Message-buttons, title.
You can use win32 library in Python, this is classical example of OK or Cancel.
import win32api
import win32com.client
import pythoncom
result = win32api.MessageBox(None,"Do you want to open a file?", "title",1)
if result == 1:
print 'Ok'
elif result == 2:
print 'cancel'
The collection:
win32api.MessageBox(0,"msgbox", "title")
win32api.MessageBox(0,"ok cancel?", "title",1)
win32api.MessageBox(0,"abort retry ignore?", "title",2)
win32api.MessageBox(0,"yes no cancel?", "title",3)
Start an app as a background process that either has a TCP port bound to localhost, or communicates through a file -- your daemon has the file open, and then you echo "foo" > c:\your\file. After, say, 1 second of no activity, you display the message and truncate the file.

Categories