I'm building a GTK application with Python but I want to update labels at a specific time every hour.
I tried with schedule but apparently it doesn't work with pygobject. This is my sample code
application = Application()
application.connect("destroy", Gtk.main_quit)
application.show_all()
Gtk.main()
print('go here') # This line never run
schedule.every(30).seconds.do(application.update_code)
while 1:
schedule.run_pending()
time.sleep(1)
Update Thanks for the answer of JohnKoch, this solution was applied to my repo that works as TOTP Authenticator on Linux, You can visit and I will appreciate it very much if you leave a star or any issues about my repo.
Gtk.main()
Runs the main loop until Gtk.main_quit() is called.
So schedule below never has the chance to run. You need to hook a function to Gtk main loop, for example, by calling GLib.timeout_add.
Here is an example,
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import GLib, Gtk
import schedule
window = Gtk.Window(title="Hello World")
window.set_default_size(600, 400)
window.connect("destroy", Gtk.main_quit)
label = Gtk.Label(label="0")
window.add(label)
window.show_all()
count = 0
def update_label():
global count
count = count + 1
label.set_text(str(count))
schedule.every(2).seconds.do(update_label)
def run_schedule():
schedule.run_pending()
return True
GLib.timeout_add(1000, lambda: run_schedule())
Gtk.main()
Related
I am writing an app-indicator on Ubuntu 18.04. Getting started was the most difficult part. Docs don't help much. I found this blog and I have a POC that just shows a fixed text on my application bar like this -
What I have not been able to figure out is how to update this text periodically or dynamically to display actual information that I need for example: CPU frequency, temperature etc.
I have looked at the following places, however I think i am missing something.
https://lazka.github.io/pgi-docs/Gtk-3.0/classes/Label.html
https://askubuntu.com/questions/108035/writing-indicators-with-python-gir-and-gtk3
https://lazka.github.io/pgi-docs/AppIndicator3-0.1/classes/Indicator.html#AppIndicator3.Indicator.set_label
The working code that I have is -
import os
import signal
from gi.repository import Gtk as gtk
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myappindicator'
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, gtk.STOCK_INFO, appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
indicator.set_menu(build_menu())
indicator.set_label('world', '8.8')
gtk.main()
def build_label():
label = gtk.Label()
return label
def build_menu():
menu = gtk.Menu()
item_quit = gtk.MenuItem('Quit')
item_quit.connect('activate', quit)
menu.append(item_quit)
menu.show_all()
return menu
def quit(source):
gtk.main_quit()
if __name__ == "__main__":
signal.signal(signal.SIGINT, signal.SIG_DFL)
main()
EDIT:
Referring to this similar SO post, and this apparently working example, I tried adding timeout_add_seconds, and timeout_add however the text doesn't change at all, it displays only the first call. I inserted a print statement there too, and surprisingly, that also prints only once. Don't know why that is happening -
New code attempt-
import random
from gi.repository import Gtk, GLib
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myappindicator'
def cb_exit(w, data):
Gtk.main_quit()
def change_label(ind_app):
text = 'Hello World, what a great day'.split()
t = random.choice(text)
print(t)
ind_app.set_label(t , '')
ind_app = appindicator.Indicator.new(APPINDICATOR_ID, Gtk.STOCK_INFO, appindicator.IndicatorCategory.SYSTEM_SERVICES)
ind_app.set_status(appindicator.IndicatorStatus.ACTIVE)
# create a menu
menu = Gtk.Menu()
menu_items = Gtk.MenuItem("Exit")
menu.append(menu_items)
menu_items.connect("activate", cb_exit, '')
menu_items.show_all()
ind_app.set_menu(menu)
GLib.timeout_add(1000, change_label, ind_app)
Gtk.main()
I figured this out myself. I was using the timeout_add function incorrectly. The called function must return anything non false for the timer to continue. In my case it is returning nothing, hence the timer was destroying itself. The docs here and this SO post helped me figure this out. SO the code finally looks like this -
import random
from gi.repository import Gtk, GLib
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myappindicator'
def change_label(ind_app):
text = 'Hello world, what a beautiful day'.split()
t = random.choice(text)
print(t)
ind_app.set_label(t , '')
return True
def quit(source):
Gtk.main_quit()
ind_app = appindicator.Indicator.new(APPINDICATOR_ID, Gtk.STOCK_INFO, appindicator.IndicatorCategory.SYSTEM_SERVICES)
ind_app.set_status(appindicator.IndicatorStatus.ACTIVE)
# create a menu
menu = Gtk.Menu()
menu_items = Gtk.MenuItem("Exit")
menu.append(menu_items)
menu_items.connect("activate", quit)
menu_items.show_all()
ind_app.set_menu(menu)
GLib.timeout_add(1000, change_label, ind_app)
Gtk.main()
I have two callbacks installed for the clicked signal on the done button. Is there a way to take out (not execute) one of them e.g.
import threading
import time
from gi.repository import Gtk, GLib
class Test():
def __init__(self):
win = Gtk.Window()
win.set_title("XYZ")
win.set_border_width(10)
box = Gtk.VBox(spacing=10)
win.add(box)
done_button = Gtk.Button(label="DONE")
done_button.connect("clicked", self.callback1)
#remove callback ??? callback1 should not be called when button is clicked.
done_button.connect("clicked", self.callback2)
box.pack_end(done_button, False, False, 0)
win.show_all()
win.maximize()
win.connect("delete-event", Gtk.main_quit)
def callback1(self, widget):
print "callback1"
def callback2(self, widget):
print "callback2"
if __name__ == '__main__':
test = Test()
Gtk.main()
What can be done to remove callback1.
you need to get the id of the signal in order to be able to disconnect it, so change the connect to:
b_id = done_button.connect("clicked", self.callback1)
and then use the disconnect function of the GObject module:
GObject.signal_handler_disconnect(done_button, b_id)
or as suggested by elya5 (so you don't even have to import GObject):
done_button.disconnect(b_id)
Remember to import the GObject module first (not GLib)
from gi.repository import Gtk, GObject
see python-gtk-3-tutorial.readthedocs.io
If you have lost the “handler_id” for some reason (for example the handlers were installed using Gtk.Builder.connect_signals()), you can still disconnect a specific callback using the function disconnect_by_func():
widget.disconnect_by_func(callback)
I've got an application which makes something in background. To inform the user it update some widgets with its progress. That works.
But somethings there is an error or something else in this background operation so it has to display a dialog. This freeze my whole application although I handle everything with the Threading-Lock. An example of code with exactly my problem is this:
import threading, time
from gi.repository import Gtk, Gdk
def background(label, parent):
for t in range(5):
label.set_text(str(t))
time.sleep(1)
Gdk.threads_enter()
dlg = Gtk.MessageDialog(
type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK,
message_format="Time is gone.",
title="Info")
dlg.run()
dlg.destroy()
Gdk.threads_leave()
def main():
window = Gtk.Window()
window.connect("delete-event", Gtk.main_quit)
label = Gtk.Label()
window.add(label)
window.show_all()
thread = threading.Thread(target=background, args=(label, window))
Gdk.threads_init()
Gdk.threads_enter()
thread.start()
Gtk.main()
Gdk.threads_leave()
if __name__=="__main__":
main()
In gtk3, all gtk functions like adding/removing/changing widgets must be executed by the gtk thread (the thread that's running Gtk.main()).
The fixed code:
import threading, time
from gi.repository import Gtk, Gdk, GLib # need GLib for GLib.PRIORITY_DEFAULT
# a short utility function that I like to use.
# makes the Gtk thread execute the given callback.
def add_mainloop_task(callback, *args):
def cb(args):
args[0](*args[1:])
return False
args= [callback]+list(args)
Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT, cb, args)
def background(label, parent):
for t in range(5):
#~ label.set_text(str(t)) # let the gtk thread do this.
add_mainloop_task(label.set_text, str(t))
time.sleep(1)
#~ Gdk.threads_enter() # don't need this.
dlg = Gtk.MessageDialog(
type=Gtk.MessageType.INFO,
buttons=Gtk.ButtonsType.OK,
message_format="Time is gone.",
title="Info")
# put these two functions calls inside a little function, and let the gtk thread execute it.
def run_dialog(dlg):
dlg.run()
dlg.destroy()
add_mainloop_task(run_dialog, dlg)
#~ Gdk.threads_leave() # don't need this.
def main():
window = Gtk.Window()
window.connect("delete-event", Gtk.main_quit)
label = Gtk.Label()
window.add(label)
window.show_all()
thread = threading.Thread(target=background, args=(label, window))
Gdk.threads_init()
#~ Gdk.threads_enter() # don't need this.
thread.start()
Gtk.main()
#~ Gdk.threads_leave() # don't need this.
if __name__=="__main__":
main()
Using python3 and gi.repository, I want to have a Gtk.HeaderBar with a Gtk.Button that is replaced by a Gtk.Spinner as soon as you click on it. After a calculation the button should appear again.
Here is an example of how I think it should work but the Gtk.Spinner only shows up after the calculation (in this example sleep) for a very short time. How can I achieve that the spinner shows up for the whole calculation (or sleep)?
from gi.repository import Gtk
import time
class window:
def __init__(self):
self.w = Gtk.Window()
self.button = Gtk.Button('x')
self.button.connect('clicked', self.on_button_clicked)
self.spinner = Gtk.Spinner()
self.hb = Gtk.HeaderBar()
self.hb.props.show_close_button = True
self.hb.pack_start(self.button)
self.w.set_titlebar(self.hb)
self.w.connect('delete-event', Gtk.main_quit)
self.w.show_all()
def on_button_clicked(self, widget):
self.button.hide()
self.hb.pack_start(self.spinner)
self.spinner.show()
self.spinner.start()
time.sleep(5)
self.spinner.stop()
self.hb.remove(self.spinner)
self.button.show()
if __name__ == '__main__':
w = window()
Gtk.main()
GTK+ is an event driven system where the mainloop should be left free to update the UI and everything that takes time (like reading from a file, making a network connection, long calculations) should happen asynchronously.
In your case this would look something like this:
def on_button_clicked(self, widget):
self.button.hide()
self.spinner.show()
self.spinner.start()
GLib.timeout_add_seconds (5, self.processing_finished)
def processing_finished(self):
self.spinner.stop()
self.spinner.hide()
self.button.show()
Note that I removed the pack and remove calls: do those in __init__(). You'll want from gi.repository import GLib in there as well.
This way the main loop is free to update the UI as often as it wants. If you really want to use a blocking call like sleep(), then you'll need to do that in another thread, but my suggestion is to use libraries that are asychronous like that timeout_add_seconds() call.
The problem is time.sleep(): it is a blocking function.
def on_button_clicked(self, widget):
self.button.hide()
self.hb.pack_start(self.spinner)
self.spinner.show()
self.spinner.start()
t = time.time()
while time.time() - t < 5:
Gtk.main_iteration()
self.spinner.stop()
self.hb.remove(self.spinner)
self.button.show()
I think that's what you expect.
Edit: You may put a time.sleep(.1) inside while loop, for cpu saving, but don't forget Gtk.main_iteration(): that is the function that exit from while loop to main loop (show spinner, progress bar and so on).
In my PyGTK app, on button click I need to:
Fetch some html (can take some time)
Show it in new window
While fetching html, I want to keep GUI responsive, so I decided to do it in separate thread. I use WebKit to render html.
The problem is I get empty page in WebView when it is in separated thread.
This works:
import gtk
import webkit
webView = webkit.WebView()
webView.load_html_string('<h1>Hello Mars</h1>', 'file:///')
window = gtk.Window()
window.add(webView)
window.show_all()
gtk.mainloop()
This does not work, produces empty window:
import gtk
import webkit
import threading
def show_html():
webView = webkit.WebView()
webView.load_html_string('<h1>Hello Mars</h1>', 'file:///')
window = gtk.Window()
window.add(webView)
window.show_all()
thread = threading.Thread(target=show_html)
thread.setDaemon(True)
thread.start()
gtk.mainloop()
Is it because webkit is not thread-safe. Is there any workaround for this?
According to my experience, one of the things that sometimes doesn't work as you expect with gtk is the update of widgets in separate threads.
To workaround this problem, you can work with the data in threads, and use glib.idle_add to schedule the update of the widget in the main thread once the data has been processed.
The following code is an updated version of your example that works for me (the time.sleep is used to simulate the delay in getting the html in a real scenario):
import gtk, glib
import webkit
import threading
import time
# Use threads
gtk.gdk.threads_init()
class App(object):
def __init__(self):
window = gtk.Window()
webView = webkit.WebView()
window.add(webView)
window.show_all()
self.window = window
self.webView = webView
def run(self):
gtk.main()
def show_html(self):
# Get your html string
time.sleep(3)
html_str = '<h1>Hello Mars</h1>'
# Update widget in main thread
glib.idle_add(self.webView.load_html_string,
html_str, 'file:///')
app = App()
thread = threading.Thread(target=app.show_html)
thread.start()
app.run()
gtk.main()
I don't know anything about webkit inner workings, but maybe you can try it with multiple processes.