Destroying old callback for button and installing new one - python

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)

Related

Run a schedule with python GTK

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

Is it possible to add an Animation with AppIndicator3?

I'm trying to create a simple AppIndicator with Python.
The code is pretty similar with:
import gi
from gi.repository import Gtk
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myapp'
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, '/usr/share/myapp/images/icon.svg', appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
Gtk.main()
It works very well, but I would like to introduce an animated icon. A blinking icon for example.
Not a static one.
I tried to convert the SVG file to GIF, but it does not work as well.
Is there a way to create a GdkPixbuf.PixbufAnimation (example) within AppIndicator3?
If yes, How can I do it?
I don't know if my answer will work for you but I see you are only importing libraries, assigning a variable, and defining some code. You may want to have the main(): code loop by typing:
This will give you the option to add return True or return False:
import gi
from gi.repository import Gtk
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myapp'
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, '/usr/share/myapp/images/icon.svg', appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
Gtk.main()
while main():
pass
Or if you want the code to keep going until you click the stop button:
import gi
from gi.repository import Gtk
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myapp'
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, '/usr/share/myapp/images/icon.svg', appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
Gtk.main()
main()
I found a workaround for this. Specially, if you are trying to configure a GIF.
For me, create a manual GIF worked. This solution requires a better approach related to concurrency but the prototype is here:
import gi
import time
import signal
import threading
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import Gtk, GLib
from gi.repository import AppIndicator3 as appindicator
APPINDICATOR_ID = 'myapp'
finished = False
def change_icon(indicator, index=5):
indicator.set_icon("spinner-%s.svg" % str(index))
def render_icon(indicator):
i = 0
while True:
# I have 10 SVG images
index = (i % 10)
GLib.idle_add(change_icon, indicator, index)
time.sleep(0.5)
if finished:
break
i += 1
def on_click(widget):
global finished
finished = True
Gtk.main_quit()
def main():
indicator = appindicator.Indicator.new(APPINDICATOR_ID, 'spinner-0.svg', appindicator.IndicatorCategory.SYSTEM_SERVICES)
indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
menu = Gtk.Menu()
quit = Gtk.MenuItem('Quit')
quit.connect('activate', on_click)
menu.append(quit)
menu.show_all()
indicator.set_menu(menu)
thread = threading.Thread(target=render_icon, args=(indicator,))
thread.daemon = True
thread.start()
signal.signal(signal.SIGINT, signal.SIG_DFL)
Gtk.main()
main()
I created a spinner with 10 different frames and I can iterate using a Thread and set_icon + GLib.iddle_add().
Again, it requires a better solution in terms of Thread concorrency, but it works as a starting point.

python-vlc will not embed gtk widget into window, but open a new window instead

I'm working on a gtk3 front end for libvlc written in python using python-vlc. I'm following the gtk3 example from the python-vlc github page, but am experiencing strange behavior. I have a widget that looks like that:
import gi
import sys
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class PlayerWidget(Gtk.DrawingArea):
__gtype_name__ = 'VLCWidget'
def __init__(self, instance):
Gtk.DrawingArea.__init__(self)
self.player = instance.media_player_new()
def handle_embed(*args):
if sys.platform == 'win32':
self.player.set_hwnd(self.get_window().get_handle())
else:
self.player.set_xwindow(self.get_window().get_xid())
return True
self.connect("realize", handle_embed)
self.set_size_request(320, 200)
I embed it here:
import vlc
import sys
from widgets.player import PlayerWidget
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class VideoPlayer(Gtk.Window):
CONST_APP_NAME = "video player"
def __init__(self):
Gtk.Window.__init__(self)
if 'linux' in sys.platform:
self.instance = vlc.Instance("--no-xlib")
else:
self.instance = vlc.Instance()
self.set_default_size(800, 600)
header = Gtk.HeaderBar(title=self.CONST_APP_NAME)
header.set_subtitle("Filename.mp4")
header.set_show_close_button(True) # this one is the troublemaker
self.set_titlebar(header)
self.connect("destroy", Gtk.main_quit)
self.player_widget = PlayerWidget(self.instance)
self.add(self.player_widget)
def show_window(self):
self.show_all()
Gtk.main()
def set_media(self, fname):
self.player_widget.player.set_media(self.instance.media_new(fname))
def play(self):
self.player_widget.play()
if not len(sys.argv) > 0:
print('Please provide a filename')
sys.exit(1)
p = VideoPlayer()
p.set_media(sys.argv[1])
p.play()
p.show_window()
p.instance.release()
It works fine if I embed it into an empty Gtk.window. If, however, I add a HeaderBar to that window as well and then add a close button to that HeaderBar using set_show_close_button(True) it stops working as expected. The PlayerWidget will not be shown embedded anymore, but instead a new (second) window will be opened where the video is played. If I do not add the close button to the HeaderBar the widget gets embedded just fine.
A warning is thrown to the console: xcb_window window error: X server failure
I first thought it could be because I use gnome under wayland, but it occurs on X as well as on wayland.
Any help is appreciated.
Update 1: Added full code example. When I ran it today, the first time it actually worked as expected, but after that the same bug as described above occured again. Very weird.
As #mtz and #stovfl correctly pointed out, the problem was that I started the video playback (p.play()) before creating the window (p.show_window()).
As suggested I used GLib.idle_add(p.play) to let the window start the playback once it's ready. The GLib module can be imported using from gi.repository import GLib.

How to periodically update Gtk3 Label text?

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

PyGTK, Threads and WebKit

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.

Categories

Resources