I'm new to object-oriented programming, Python and GTK+3, though I have a decent knowledge of procedural programming (mainly C).
I'm trying to build a simple Python + GTK+ 3 script to run pkexec apt-get update
under Linux.
I have a mainWindow
class (based on a Gtk.Window
class) which contains a button object named button
(based on a Gtk.Button
class) which triggers a new_update_window()
method defined in mainWindow
upon a clicked
event;
The new_update_window()
method initializes an updateWindow
object from an updateWindow
class (based on a Gtk.Window
class) which contains a label object named label
(based on a Gtk.Label
class) and calls the methods show_all()
and update()
defined in updateWindow
;
The update()
method is supposed to change label
, run pkexec apt-get update
and change label
again.
The problem is no matter what I do one of the following occurs:
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
directly, update.Window
is shown but label
is set immediately to the value it should be set only after pkexec apt-get update
has finished executing;subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
directly, update.Window
is not shown until pkexec apt-get update
has finished executing;import
ing threading
, defining a separate run_update()
method in updateWindow
and start the function in a separate thread using thread = threading.Thread(target=self.run_update)
, thread.start()
, thread.join()
, but still depending on which method I call in run_update()
(subprocess.call()
or subprocess.Popen
) the relative behavior described above exhibits.I'm at a loss to understand how to accomplish what I'm after, which is:
updateWindow
(Gtk.Window
)label
(Gtk.Label
) in updateWindow
pkexec apt-get update
label
in updateWindow
subprocess.Popen()
: update.Window
is shown but label
is set immediately to the value it should be set only after pkexec apt-get update
has finished executing;subprocess.call()
: update.Window
is not shown until pkexec apt-get update
has finished executing;Here's the code;
Not using a thread (case 1, in this case using subprocess.Popen()
):
#!/usr/bin/python3
from gi.repository import Gtk
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
self.label.set_text("Updated.")
def run_update(self):
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
Using a thread (case 3, in this case using subprocess.Popen()
):
#!/usr/bin/python3
from gi.repository import Gtk
import threading
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
thread = threading.Thread(target=self.run_update)
thread.start()
thread.join()
self.label.set_text("Updated.")
def run_update(self):
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
Instead of using Python's subprocess
module, you could use Gio.Subprocess
which integrates with GTK's main loop:
#!/usr/bin/python3
from gi.repository import Gtk, Gio
# ...
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
subprocess = Gio.Subprocess.new(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"], 0)
subprocess.wait_check_async(None, self._on_update_finished)
def _on_update_finished(self, subprocess, result):
subprocess.wait_check_finish(result)
self.label.set_text("Updated.")
You were almost there...
and the solution pretty simple :)
The issue you are having is that subprocess.call()
would freeze the GUI (loop), and thus prevent the window to appear, while subprocess.Popen()
would throw out the command and jump to self.label.set_text("Updated.")
You can simply solve it by running a separate thread, calling your command:
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
and move the label- change command
self.label.set_text("Updated.")
into the thread, positioned after the first command. The thread then does not freeze the interface, while label
is not changed too early, since subprocess.call()
will prevent that.
The code then becomes:
#!/usr/bin/python3
from gi.repository import Gtk
from threading import Thread
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
Thread(target = self.run_update).start()
def run_update(self):
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
self.label.set_text("Updated.")
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
If you'd like to avoid using Thread
, you could use Gtk.main_iteration()
to prevent the interface from freezing while the process runs:
#!/usr/bin/python3
from gi.repository import Gtk
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
self.label.set_text("Updating... Please wait.")
subprocess.Popen(["gedit"])
self.hold()
self.label.set_text("Updated.")
def run_update(self):
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
def hold(self):
while True:
Gtk.main_iteration()
try:
subprocess.check_output(["pgrep", "apt-get"]).decode("utf-8")
except subprocess.CalledProcessError:
break
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
Ongoing insight learned there is a better way to use threads then posted above in my answer.
You can use threads in a Gtk
GUI, using
GObject.threads_init()
Then, to update the interface from the thread, use
GObject.idle_add()
from this (slgihtly outdated) link:
...call gobject.threads_init()
at application initialization. Then you launch your threads normally, but make sure the threads never do any GUI tasks directly. Instead, you use gobject.idle_add
to schedule GUI task to executed in the main thread
When we replace gobject.threads_init()
by GObject.threads_init()
and gobject.idle_add
by GObject.idle_add()
, we pretty much have the updated version of how to run threads in a Gtk application.
Applied in your code (using second example, using threads):
#!/usr/bin/python3
from gi.repository import Gtk, GObject
from threading import Thread
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow()
update.show_all()
update.update()
class updateWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
def update(self):
# self.thread = threading.Thread(target=self.run_update)
thread = Thread(target=self.run_update)
thread.start()
def run_update(self):
GObject.idle_add(
self.label.set_text, "Updating... Please wait.",
priority=GObject.PRIORITY_DEFAULT
)
subprocess.call(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
GObject.idle_add(
self.label.set_text, "Updated.",
priority=GObject.PRIORITY_DEFAULT
)
GObject.threads_init()
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
In
def run_update(self):
subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
you are not waiting the process to terminate, try
def run_update(self):
proc = subprocess.Popen(["/usr/bin/pkexec", "/usr/bin/apt-get", "update"])
proc.wait()
instead. This should wait for the completion properly, but it won't help much since updateWindow.update
will be called from mainWindow.new_update_window
and GUI thread will be waiting for the process to complete.
Custom signals can be used to communicate when the process started by subprocess.Popen
or subprocess.call
completes:
#!/usr/bin/python3
from gi.repository import Gtk, GObject
import threading
import subprocess
class mainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Updater")
button = Gtk.Button()
button.set_label("Update")
button.connect("clicked", self.new_update_window)
self.add(button)
def new_update_window(self, button):
update = updateWindow(self)
update.show_all()
update.start_update()
class updateWindow(Gtk.Window):
def __init__(self, parent):
Gtk.Window.__init__(self, title = "Updating...")
self.label = Gtk.Label()
self.label.set_text("Idling...")
self.add(self.label)
self.parent = parent
GObject.signal_new('update_complete', self, GObject.SIGNAL_RUN_LAST,
None, (int,))
self.connect('update_complete', self.on_update_complete)
def on_update_complete(self, widget, rc):
self.label.set_text("Updated {:d}".format(rc))
# emit a signal to mainwindow if needed, self.parent.emit(...)
def start_update(self):
self.label.set_text("Updating... Please wait.")
thread = threading.Thread(target=self.run_update)
thread.start()
def run_update(self):
rc = subprocess.call(["/usr/bin/pkexec", "apt-get", "update"],
shell=False)
self.emit('update_complete', rc)
main = mainWindow()
main.connect("delete-event", Gtk.main_quit)
main.show_all()
Gtk.main()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With