Monitor files for changes in a python3 Gtk application - python-3.x

I'm trying to monitor a directory, in order to detect when files are added to it and take action, in a Gtk application.
I've written the following Gio / Gtk snippet to experiment that, but no event get detected, if I create a file with something like echo tata > tutu or if I move a file, like mv tutu plop:
#!/usr/bin/env python3
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gio, Gtk
def directory_changed(monitor, f1, f2, evt):
print("Changed:", f1, f2, evt)
def add_monitor(directory):
gdir = Gio.File.new_for_path(directory)
monitor = gdir.monitor_directory(Gio.FileMonitorFlags.NONE, None)
monitor.connect("changed", directory_changed)
win = Gtk.Window()
win.connect("destroy", Gtk.main_quit)
add_monitor('.')
win.show_all()
Gtk.main()
If it matters, I'm using python3.7 on debian 11 (bullseye) and the python3-gi package version is 3.30.4-1.
Does anyone have an idea of what I'm doing wrong?

Just from reading the code, I would suggest your first code fails because the add_monitor() de-allocates all its variables when the function exits, unlike the second which keeps them in the object. Although you might want to use self.gdir for the same reason. But perhaps it is not necessary.

I solved my problem with the following snippet which is basically the same, but with a custom class, subclassing Gtk.Window:
#!/usr/bin/env python3
import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gio, Gtk
class DocCliWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title = "Document to clipboard")
def on_directory_changed(self, monitor, f1, f2, evt):
print("Changed", f1, f2, evt)
def add_monitor(self, directory):
gdir = Gio.File.new_for_path(directory)
self.monitor = gdir.monitor_directory(Gio.FileMonitorFlags.NONE, None)
self.monitor.connect("changed", self.on_directory_changed)
win = DocCliWindow()
win.connect("destroy", Gtk.main_quit)
win.add_monitor('.')
win.show_all()
Gtk.main()
But the problem is, I have absolutely no idea of why it works and the previous one doesn't :)

Related

Error Cannot create instance of abstract (non-instantiable) type `GtkBox' python3

I have error when run this code:
from gi.repository import Gtk
class MainWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="")
# box
self.box = Gtk.Box(spacing=10)
self.add(self.box)
# bacon button
self.bacon_button = Gtk.Button(label="Bacon")
self.bacon_button.connect("clicked", self.bacon_clicked)
self.box.pack_start(self.bacon_button, True, True,0)
# tuna button
self.tuna_button = Gtk.Button(label="Tuna")
self.tuna_button.connect("clicked", self.tuna_clicked)
self.box.pack_start(self.tuna_button, True, True,0)
def bacon_clicked(self, widget):
print("You clicked Bacon")
def tuna_clicked(self, widget):
print("You clicked Tuna")
window = MainWindow()
window.connect("delete-event", Gtk.main_quit)
window.show_all()
Gtk.main()
And error at output:
File "/Users/*********/Documents/Program/Tutorial/venv/lib/python3.11/site-packages/gi/overrides/__init__.py", line 319, in new_init
return super_init_func(self, **new_kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: cannot create instance of abstract (non-instantiable) type `GtkBox'
Process finished with exit code 1
I currently using python3 and already install PyGObject package in PyCharm
Your code is loading GTK2, instead of GTK3; in GTK 2.x, GtkBox is indeed an abstract type.
If you want to use GTK 3.x, you will need to add:
import gi
gi.require_version('Gtk', '3.0')
at the top, before importing the Gtk namespace.
Don't use Box directly. If you want a Box that grows horizontally with pack_start, use Gtk.HBox (documented here).
If you want it to grow vertically, use Gtk.VBox (documented here).
That being said, the more modern (GTK4) way of doing things is to use a Gtk.Grid for everything.

PyGtk Segmentation with signal "activate link" in a markup-text label

The MWE below simply creates a label with a hyper-text in it. Most of the time (but not every time) it causes a Segmentation fault when I click on that link.
I assume that I missunderstand something about PyGtk and I use it the wrong way?
That is the error output:
Window._on_activate_link()
Fatal Python error: Segmentation fault
Current thread 0x00007fa2718a7740 (most recent call first):
File "/usr/lib/python3/dist-packages/gi/overrides/Gtk.py", line 1641 in main
File "./window.py", line 45 in <module>
Speicherzugriffsfehler
Here are informations about my system and versions
Linux-4.19.0-14-amd64-x86_64-with-debian-10.8
Python 3.7.3 CPython
Gtk 3.0
GIO 3.30.4
Cairo 1.16.2
This is the MWE
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import faulthandler; faulthandler.enable()
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class FeedybusWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self)
hbox = Gtk.Box(spacing=5)
self.add(hbox)
self._feed_label = Gtk.Label(label='')
self._feed_label.set_use_markup(True)
self._feed_label.set_markup('Click me')
self._feed_label.connect('activate-link', self._on_activate_link)
hbox.pack_start(self._feed_label, True, True, 20)
# EVENT: destroy
self.connect('delete-event', self._on_delete_event)
self.connect('destroy', self._on_destroy)
def _on_activate_link(self, label, uri):
print('Window._on_activate_link()')
if uri == 'renamelabel':
self._feed_label.set_markup('Click me AGAIN')
return True
return False
def _on_delete_event(self, window, event=None):
self.destroy()
def _on_destroy(self, caller):
Gtk.main_quit()
if __name__ == '__main__':
window = FeedybusWindow()
window.show_all()
Gtk.main()
EDIT: Of course I can use other GUI elements instead of a Gtk.Label. But this would be a workaround not a solution. The focus of my question is if I am using the Gtk package the wrong way or that there maybe is a bug in Gtk that I should report.
This must be a bug (maybe: https://gitlab.gnome.org/GNOME/gtk/-/issues/1498). I can't see any problems with your code, and I can reproduce the crash on my Ubuntu 20.04 system as well.
It seems like setting the label in the activate-link handler is the problem.
FWIW: A workaround is to set it outside the handler like this:
def _on_activate_link(self, label, uri):
print('Window._on_activate_link()')
if uri == 'renamelabel':
Gdk.threads_add_idle(GLib.PRIORITY_DEFAULT_IDLE, self._rename)
return False
def _rename(self):
self._feed_label.set_markup('Click me AGAIN')

Incorrect behaviour of print() when executed from within a QTDialog window in Spyder

I am working on a very simple interface to explore/graph csv files. My aim is ultimately to explore, not to build software as I am not a developer, more of a "desperate user" :-)
I am leveraging the code found in this example
These are my first steps both in Python and in GUI, so I tend to put print messages in my calls so that I can more or less track what is happening. And this is where I found a strange behavior if I run the code from within Spyder.
import sys
import os
from PyQt4 import QtGui
import pandas as pd
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
# QtGui.QDialog
class Window(QtGui.QDialog):
def __init__(self, parent=None):
super(Window, self).__init__(parent)
# a figure instance to plot on
self.figure = plt.figure()
# this is the Canvas Widget that displays the `figure`
# it takes the `figure` instance as a parameter to __init__
self.canvas = FigureCanvas(self.figure)
# this is the Navigation widget
# it takes the Canvas widget and a parent
self.toolbar = NavigationToolbar(self.canvas, self)
# Just some extra button to mess around
self.button= QtGui.QPushButton('Push Me')
self.button.clicked.connect(self.do_print)
# set the layout
layout = QtGui.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
def do_print(self):
print('Hello World!!')
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Window()
main.show()
sys.exit(app.exec_())
The strange behavior is that if I push the button once, nothing happens on the Ipython console. By the second time I push, then two "Hello World!" printouts appear.
If, on the other hand, I just launch my script from within a Windows Shell:
python my_simple_test.py
Then everything works as expected.
What am I then doing wrong from within Spyder?
Thanks,
Michele
IPython buffers stdout a bit differently from a terminal. When something is printed, it looks at how long it has been since it last flushed the buffer, and if it's longer than some threshold, it flushes it again. So the second time you click the button, it flushes stdout, and you see both outputs.
You can force it to flush immediately like this:
print('Hello World!!')
sys.stdout.flush()

QFileDialog: no signals emitted, wrong starting directory

I'm trying to show a QFileDialog using the following piece of code:
import os, sys
from PyQt4.QtGui import *
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self._button = QPushButton('Test button')
self._button.clicked.connect(self._onButtonClicked)
self._layout = QHBoxLayout()
self._layout.addWidget(self._button)
self.setLayout(self._layout)
def _onButtonClicked(self):
self._dialog = QFileDialog(self, 'Select directory')
self._dialog.setDirectory(os.getenv('HOME'))
self._dialog.setFileMode(QFileDialog.Directory)
self._dialog.directoryEntered.connect(self._onDirEntered)
self._dialog.exec_()
def _onDirEntered(self, directory):
print("Entered directory: %s" % (directory))
if __name__ == "__main__":
app = QApplication(sys.argv)
mw = MainWindow()
mw.show()
app.exec_()
Two problems here:
The directoryEntered signal is never emitted, at least I don't get any output from the script (except for some KDE warnings about Samba support, etc.); actually no signal from the QFileDialog class I tried to connect to gets emitted in the example. What am I doing wrong?
In this example I set the starting directory to $HOME, but the dialog will start in /home instead and have my home directory selected in the listing instead of starting directly in my home directory. Can I change this behaviour somehow?
I'm using Python 3.4.0 with PyQt 4.10.4-2.
Both problems do not occur when using a non-native dialog as suggested by #ekhumoro.

Cairo introspection errors in Eclipse with PyDev `TypeError: Couldn't find conversion for foreign struct 'cairo.Context'`

I am working on adding a printer interface to some home-brewed Python3 code with a Gtk3 UI, using (mostly) Eclipse Indigo with the PyDev plugin.
While developing the PrintOperation callbacks I found a problem where apparently the gi-introspection fails to find the right underlying library struct for the Cairo Context. The error reported in the console is:
Traceback (most recent call last):
File "/home/bob/Projects/MovieList/src/MovieList/MovieListIO.py", line 203, in on_printDialog_draw_page
cr = context.get_cairo_context()
File "/usr/lib/python3/dist-packages/gi/types.py", line 43, in function
return info.invoke(*args, **kwargs)
TypeError: Couldn't find conversion for foreign struct 'cairo.Context'
At first I thought this was something to do with Eclipse and/or PyDev, because I could run the program within Idle without any error messages. But then I found that when the program was packaged for deployment with the built-in command-line Python tools, the installed version also gave the error. So, I wrote a couple of test scripts abstracting the printer functionality to try to isolate what was going on. In both cases, the key line is in the on_printOperation_draw_page() callback (marked with comments).
Here is the first test script (Script 1, printTestPdf.py), which loads a pdf file using Poppler, and prints it using the system print dialog:
#!/usr/bin/env python3
import os
from gi.repository import Gtk, Poppler
testFile = 'file://' + os.path.join(os.getcwd(), 'printTestPdf.pdf')
pdfDocument = Poppler.Document.new_from_file(testFile, None)
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.set_title("Print Pdf Test")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
printButton = Gtk.Button('Press Me')
self.add(printButton)
printButton.connect('clicked', self.on_printButton_clicked)
self.show_all()
def on_printButton_clicked(self, widget):
"""
Handler for the button click.
"""
printOperation = Gtk.PrintOperation()
printOperation.connect('draw-page', self.on_printOperation_draw_page)
printOperation.set_job_name('Print Pdf Test')
printOperation.set_n_pages(pdfDocument.get_n_pages())
printOperation.run(Gtk.PrintOperationAction.PRINT_DIALOG,
parent=self)
def on_printOperation_draw_page(self, printOperation, context, pageNo):
"""
Handler for the draw-page signal from the printOperation.
"""
cr = context.get_cairo_context() # <-- THIS IS THE LINE
page = pdfDocument.get_page(pageNo)
page.render_for_printing(cr)
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
This is the second script (Script 2, printTestHtml.py), which is almost identical, except it loads an HTML file for printing using weasyprint:
#!/usr/bin/env python3
import os
from gi.repository import Gtk
from weasyprint import HTML
testFile = os.path.join(os.getcwd(), 'printTestHtml.html')
pdfDocument = HTML(filename=testFile).render()
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.set_title("Print Html Test")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
printButton = Gtk.Button('Press Me')
self.add(printButton)
printButton.connect('clicked', self.on_printButton_clicked)
self.show_all()
def on_printButton_clicked(self, widget):
"""
Handler for the button click.
"""
printOperation = Gtk.PrintOperation()
printOperation.connect('begin-print', self.on_printOperation_begin_print)
printOperation.connect('draw-page', self.on_printOperation_draw_page)
printOperation.set_job_name('Print HTML Test')
printOperation.set_n_pages(len(pdfDocument.pages))
printOperation.run(Gtk.PrintOperationAction.PRINT_DIALOG,
parent=self)
def on_printOperation_draw_page(self, printOperation, context, pageNo):
"""
Handler for the draw-page signal from the printOperation.
"""
cr = context.get_cairo_context() # <-- THIS IS THE LINE
page = pdfDocument.pages[pageNo]
page.paint(cr) # <-- there is a separate issue here
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
Both scripts generate an internal pdf document, which is used to render each page on request via the PrintOperation draw_page callback.
Now, whether and how the scripts succeed or fail depends on the context in which they are run. Script 1 always works, except if it is run after a failure of Script 2 in Idle. Script 2 always generates the error message as reported above when run in Eclipse. In Idle, Script 2's behaviour is complex. Sometimes it fails due to a second problem (marked), and does not exhibit the first failure. However, for reasons I have yet to establish, every so often it generates the original error, and when it does, it keeps doing it and Script 1 show the error too, until Idle is re-booted. Running directly from the command line matches the behaviour in Eclipse. I have tried to summarise this behaviour below:
* Eclipse
- Script 1: Always OK
- Script 2: Always Fails
* Command line
- Script 1: Always OK
- Script 2: Always Fails
* Idle
- Script 1: OK, except after failure of Script 2
- Script 2: Intermittent Fail. Knock-on to future runs (up to next error)
This pattern of failure may help determine what the root problem is, but it is beyond me to understand it.
Ignoring the bizarre behaviour in Idle, it is possible the difference between Script 1 and Script 2 holds a clue to my original problem. Why does Script 1 run successfully, while Script 2 generates the introspection error?
If you can offer any suggestions as to what is going wrong I would be most grateful. If you can come up with a solution I will be delighted!
In view of the lack of response I have come up with the following workaround, which uses WebKit instead of weasyprint to do the parsing, rendering and administration of the printing from html:
#!/usr/bin/env python3
import os
from gi.repository import Gtk, WebKit
testFile = 'file://' + os.path.join(os.getcwd(), 'printTestHtml.html')
class Example(Gtk.Window):
def __init__(self):
super(Example, self).__init__()
self.init_ui()
def init_ui(self):
self.set_title("Print Html WebKit Test")
self.resize(230, 150)
self.set_position(Gtk.WindowPosition.CENTER)
self.connect("delete-event", Gtk.main_quit)
printButton = Gtk.Button('Press Me')
self.add(printButton)
printButton.connect('clicked', self.on_printButton_clicked)
self.show_all()
def on_printButton_clicked(self, widget):
webView = WebKit.WebView()
webView.load_uri(testFile)
webFrame = webView.get_main_frame()
webFrame.print_full(Gtk.PrintOperation(),
Gtk.PrintOperationAction.PRINT_DIALOG)
def main():
app = Example()
Gtk.main()
if __name__ == "__main__":
main()
What I think is going on is that somehow weasyprint interferes with the introspection process. I have raised this as a bug on the weasyprint home page on github.
Just in case it helps I was looking for a solution to the cairo error.
Had this cairo error happen on my RPi3 with Pandas and Matplotlib. The plot window was showing up blank.
I had to run sudo apt-get install python-gobject-cairo
Based on this: https://github.com/rbgirshick/py-faster-rcnn/issues/221

Resources