'Close window' button wont work when using tkinter + gobject - linux

My tkinter application runs fine, but to implement dbus functionality I had to use gobject. Got it working and all, except that, running both tkinter's and gobject's mainloops makes the "close window" button from the standard window manager ('x' button in the window interface) not to work. :/ Everything else works fine, including resizing, minimizing/maximizing, restoring, and moving the window.
Any help is appreciated,
Thanks,
Little code snippet:
import dbus
from dbus.service import Object
from dbus.mainloop.glib import DBusGMainLoop
class TBOPlayerDBusInterface (Object):
tboplayer_instance = None
def __init__(self, tboplayer_instance):
self.tboplayer_instance = tboplayer_instance
dbus_loop = DBusGMainLoop()
bus_name = dbus.service.BusName("org.tboplayer.TBOPlayer", bus = dbus.SessionBus(mainloop = dbus_loop))
Object.__init__(self, bus_name, "/org/tboplayer/TBOPlayer")
#dbus.service.method('org.tboplayer.TBOPlayer', in_signature = 'as')
def openFiles(self, files):
self.tboplayer_instance._add_files(files)
# ***************************************
# MAIN
# ***************************************
if __name__ == "__main__":
datestring=" 28 Fev 2017"
dbusif_tboplayer = None
try:
bus = dbus.SessionBus()
bus_object = bus.get_object("org.tboplayer.TBOPlayer", "/org/tboplayer>/TBOPlayer", introspect = False)
dbusif_tboplayer = dbus.Interface(bus_object, "org.tboplayer.TBOPlayer")
except Exception, e:
print e
if dbusif_tboplayer is None:
tk.CallWrapper = ExceptionCatcher
bplayer = TBOPlayer()
TBOPlayerDBusInterface(bplayer)
def refresh_player():
bplayer.root.update()
return True
def run_gobject():
gobject.MainLoop().run()
gobject.idle_add(refresh_player)
bplayer.root.after(100, run_gobject)
bplayer.root.mainloop()
else:
if len(sys.argv[1:]) > 0:
dbusif_tboplayer.openFiles(sys.argv[1:])
exit()

I found the problem. For some reason, using tkinter's and gobject's mainloops interferes with the behavior of the WM_DELETE_WINDOW event, which I was using for saving some data before closing the program. Solved the problem by binding to the Configure event instead. And now the main method is as follows:
if __name__ == "__main__":
datestring=" 28 Fev 2017"
dbusif_tboplayer = None
try:
bus = dbus.SessionBus()
bus_object = bus.get_object("org.tboplayer.TBOPlayer", "/org/tboplayer/TBOPlayer", introspect = False)
dbusif_tboplayer = dbus.Interface(bus_object, "org.tboplayer.TBOPlayer")
except Exception, e:
print e
if dbusif_tboplayer is None:
tk.CallWrapper = ExceptionCatcher
bplayer = TBOPlayer()
TBOPlayerDBusInterface(bplayer)
gobject_loop = gobject.MainLoop()
def refresh_player():
try:
bplayer.root.update()
return True
except Exception, e:
bplayer.quit_omx()
gobject_loop.quit()
def run_gobject():
gobject_loop.run()
gobject.idle_add(refresh_player)
bplayer.root.after(100, run_gobject)
bplayer.root.mainloop()
else:
if len(sys.argv[1:]) > 0:
dbusif_tboplayer.openFiles(sys.argv[1:])
exit()

Related

PySide2: How to re-implement QFormLayout.takeRow()?

I've noticed that QFormLayout in Pyside2 does not have the takeRow method like its PyQt5 counterpart. I've attempted to subclass QFormLayout to incorporate a similar method, but I've run into Runtime Errors, as the removal behavor of the LabelRole item is different than the FieldRole item. Another issue being that the LabelRole item does not actually get taken off the row even when the row itself is removed.
The following is the test sample I've been working with using Python 3.8.6:
from PySide2.QtWidgets import *
import sys
class MyFormLayout(QFormLayout):
def __init__(self, *args, **kwargs):
super(MyFormLayout, self).__init__(*args, **kwargs)
self.cache = []
print(f"Formlayout's identity: {self=}\nwith parent {self.parent()=}")
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row) ## <-- This seems necessary to make the rowCount() decrement. Alternative?
label_item.widget().setParent(None) ## <-- Runtime Error Here?
field_item.layout().setParent(None)
self.cache.append(label_item.widget(), field_item)
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label_item, field_item
def restoreRow(self, insert_idx: int):
print(f"Called {self.restoreRow.__name__}")
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
to_insert = self.cache.pop()
self.insertRow(insert_idx, to_insert[0], to_insert[1])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
class MyWindow(QWidget):
def __init__(self):
super(MyWindow, self).__init__()
self.mainlay = MyFormLayout(self)
self.cmb = QComboBox()
self.cmb.addItems(["Placeholder", "Remove 1 and 2"])
self.cmb.currentTextChanged.connect(self.remove_rows_via_combo)
self.current_text = self.cmb.currentText()
self.hlay1, self.le1, self.btn1 = self.le_and_btn(placeholderText="1")
self.hlay2, self.le2, self.btn2 = self.le_and_btn(placeholderText="2")
self.hlay3, self.le3, self.btn3 = self.le_and_btn(placeholderText="3")
self.hlay4, self.le4, self.btn4 = self.le_and_btn(placeholderText="4")
self.remove_btn = QPushButton("Remove", clicked=self.remove_row_via_click)
self.restore_btn = QPushButton("Restore", clicked=self.restore_a_row_via_click)
self.mainlay.addRow("Combobox", self.cmb)
for ii, hlayout in zip(range(1, 5), [self.hlay1, self.hlay2, self.hlay3, self.hlay4]):
self.mainlay.addRow(f"Row {ii}", hlayout)
self.mainlay.addRow(self.remove_btn)
self.mainlay.addRow(self.restore_btn)
#staticmethod
def le_and_btn(**kwargs):
hlay, le, btn = QHBoxLayout(), QLineEdit(**kwargs), QPushButton()
hlay.addWidget(le)
hlay.addWidget(btn)
return hlay, le, btn
def remove_row_via_click(self):
self.mainlay.takeRow(1)
def restore_a_row_via_click(self):
self.mainlay.restoreRow(1)
def remove_rows_via_combo(self, text):
print(f"{self.remove_rows_via_combo.__name__} received the text: {text}")
if text == "Remove 1 and 2":
self.mainlay.takeRow(1)
self.mainlay.takeRow(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec_())
I would like to understand why the behavior of the role items is different and how the method may be properly re-implemented.
The problem is that the label was created internally by Qt from a string, rather than by explicitly creating a QLabel in Python. This means that when the row is removed, the last remaining reference is also removed, which deletes the label on the C++ side. After that, all that's left on the Python side is an empty PyQt wrapper - so when you try to call setParent on it, a RuntimeError will be raised, because the underlying C++ part no longer exists.
Your example can therefore be fixed by getting python references to the label/field objects before the layout-item is removed:
class MyFormLayout(QFormLayout):
...
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
# get refs before removal
label = label_item.widget()
field = field_item.layout() or field_item.widget()
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row)
label.setParent(None)
field.setParent(None)
self.cache.append((label, field))
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label, field

curses wrapper in multithread does not restore screen after quiting

I'm trying to develop a multithreaded program with a TUI interface . Basically I have a main Loop deciding what to do and some task (like the TUI or reading data from a queue and processing it ) are running in separate thread .
My TUI is using curses and is a thread derived class that look like this (i removed non essential code for clarity) :
import threading
from time import sleep
import curses
import logging
from curses.textpad import Textbox, rectangle
from datetime import datetime
import re
from curses import panel
import os
import sys
class GenericTUI(threading.Thread):
def __init__(self, logger=logging.getLogger()):
threading.Thread.__init__(self,name="genericTUI" + str(os.getpid()), daemon=True)
self.keyPressedList = list()
self.alive = True
self._myStdscr = None
self.title = ""
self.logger = logger
self.lock = threading.Lock()
def run(self):
curses.wrapper(self.main)
curses.nocbreak()
curses.echo()
curses.noraw()
sys.exit(0)
def main(self,stdscr):
self._myStdscr = stdscr
self._myStdscr.nodelay(True)
self._myStdscr.keypad(True)
self._myStdscr.box()
while self.alive:
sleep(0.4)
try :
key = self._myStdscr.getkey()
if re.match('[A-Z_\+\-\*/]', key):
self.keyPressedList.append(key)
except Exception as e:
## ignoring no key presssed
pass
try :
with self.lock :
self._myStdscr.clear()
self._myStdscr.addstr(1, 2, str(datetime.now())+" "+ sys.argv[0] +" "+self.title )
### printing other things
self._myStdscr.refresh()
except Exception as e:
self.logger.error(e, exc_info=True)
continue
self._myStdscr.clear()
self._myStdscr.keypad(0)
def getKeyPressed(self):
if self.keyPressedList :
return self.keyPressedList.pop()
else :
return None
def stop(self):
self.alive = False
def updateTitle(self,title):
with self.lock : self.title = title
if __name__ == "__main__":
## the main is used for some test when the lib is called directly
testGUI = GenericTUI()
alive = True
testGUI.logger.addHandler(logging.StreamHandler())
testGUI.logger.setLevel(logging.DEBUG)
testGUI.start()
while alive :
testGUI.updateTitle('title %s'%str(datetime.now() ))
k = testGUI.getKeyPressed()
if k is not None:
if k=='Q' :
alive = False
else :
testGUI.addMessage('unknown key %s'%k , maj=True)
sleep(0.1)
the main loop of my program instantiate and start a genericTUI object and get keypressed from it or set value to display.
But when i quit the program , my terminal is in a funny state even if I used the curses wrapper function or try to reset manually using curses.nocbreak() and others.
I can't figure what I did wrong ? Am i mistaken using curses inside a thread ??
I found the answer but puting it in the comment section makes it hard to read . So I also put it here :
the curses wapper does not like the thread in daemon mode :
so the following code works fine and restore the terminal in a correct state :
class GenericTUI(threading.Thread):
def __init__(self, logger=logging.getLogger()):
threading.Thread.__init__(self,name="genericTUI" + str(os.getpid()), daemon=False)
self.keyPressedList = list()
and in the stop function adding a curses.endwin() helps :
def stop(self):
curses.endwin()
self.alive = False
hope it helps other
#WesModes I use the stop to have a clean way to stop the TUI. The endwin is cleaning the screen .
example :
testGUI=GenericTUI()
alive = True
testGUI.start()
while alive:
try :
k = testGUI.getKeyPressed()
if k is not None:
if k== 'Q':
testGUI.stop()
alive = False

How to thread with tkinter in python3 using queue?

I'm trying to thread definitions in tkinter using queue in specifically python3
I've had similar code in python2 work great using a similar method without queue but in python3 from what i've read tkinter doesn't allow for multithreading with gui. I found some examples that uses Queue process. They outline i'm suppose to create an Queue object, a new thread with access to that queue and check for input in the main thread
#!/usr/bin/python3
from tkinter import *
import time
import threading
import queue
import subprocess
def temp_sensor(queue_list):
warning = 0
while True:
var = "cat /sys/class/thermal/thermal_zone*/temp"
temp_control = subprocess.check_output([var], shell=True)
temp_length = len(temp_control)
temp_control = temp_control[35:]
temp_control = temp_control[:-4]
temp_control = int(temp_control)
degree_sign= u'\N{DEGREE SIGN}'
displayed_temp = "Tempature: " + str(temp_control) + degree_sign + "C"
if temp_control > 79:
warning = warning + 1
if warning == 3:
print ("Warning Core Tempature HOT!")
warning = 0
if temp_control > 90:
time.sleep(3)
print ("Warning EXTREMLY to HOT!!!")
queue_list.put(displayed_temp)
time.sleep(1)
class Gui(object):
def __init__(self, queue_list):
self.queue_list = queue_list
self.root = Tk()
self.root.geometry("485x100+750+475")
main_tempature_status = StringVar(self.root)
Ts = Entry(self.root, textvariable=main_tempature_status)
Ts.pack()
Ts.place(x=331, y=70, width=160, height=25)
Ts.config(state=DISABLED, disabledforeground="Black")
self.root.after(1000, self.read_queue)
def read_queue(self):
try:
temp = self.queue.get_nowait()
self.main_tempature_status.set(temp)
except queue_list.Empty:
pass
self.root.after(1000, self.read_queue)
if __name__ == "__main__":
queue_list = queue.Queue()
gui = Gui(queue_list)
t1 = threading.Thread(target=temp_sensor, args=(queue_list,))
t1.start()
gui.root.mainloop()
My desired result is to run a some of these definitions to do various tasks and display their variables in the tkinter entry using python3.
when i run my code it gives me the variable from the queue but it won't post to the GUI. please forgive my less then pythonic code.
Change you Gui class to this:
class Gui(object):
def __init__(self, queue_list):
self.queue_list = queue_list
self.root = Tk()
self.root.geometry("485x100+750+475")
self.main_tempature_status = StringVar(self.root)
self.Ts = Entry(self.root, textvariable=self.main_tempature_status)
self.Ts.pack()
self.Ts.place(x=331, y=70, width=160, height=25)
self.Ts.config(state=DISABLED, disabledforeground="Black")
self.root.after(1000, self.read_queue)
def read_queue(self):
try:
temp = self.queue_list.get_nowait()
self.Ts.config(state=NORMAL)
self.main_tempature_status.set(temp)
self.Ts.config(state=DISABLED)
except queue.Empty:
pass
self.root.after(1000, self.read_queue)
Explanation:
variable main_temperature_status is used in function read_queue as class variable, but not defined as class variable.
You cannot show the change in value of Entry widget if it is always disabled, so enabling it before value change in read_queue.

Python watchdog module duplicate events (edit: was not an watchdog issue)

I am creating a python script that will identify changes to a log file and print some data from the new logs.
I use watchdog to create an event handler and everything seems to work fine except from that, I get duplicate events every time I modify the file. I checked creation and delete, they both work as expected and trigger one time.
I have read the similar question which explains having a created and a modified event when I save a file but this is not my case. I just get two modification events.
Here is my code:
import os, sys, time
import subprocess
import threading
import win32print
from tkinter import filedialog
from tkinter import *
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
class Handler(FileSystemEventHandler):
# docstring for FileSystemEventHandler
def __init__(self, observer, filename, dirname):
# super(Handler, FileSystemEventHandler).__init__(self,)
self.observer = observer
self.filename = filename
self.dirname = dirname
print("Handler filename = " , self.filename)
print("Handler dirname = " , self.dirname)
def on_modified(self, event):
if self.filename == event.src_path:
print("The file was modified")
print (event.src_path)
# go get the last line and print the data
# try:
# hJob = win32print.StartDocPrinter (hPrinter, 1, ("test of raw data", None, "RAW"))
# try:
# win32print.StartPagePrinter (hPrinter)
# win32print.WritePrinter (hPrinter, raw_data)
# win32print.EndPagePrinter (hPrinter)
# finally:
# win32print.EndDocPrinter (hPrinter)
# finally:
# win32print.ClosePrinter (hPrinter)
def on_created(self, event):
print("A file was created (", event.src_path, ")")
def on_deleted(self, event):
print("A file was deleted (", event.src_path, ")")
if __name__ == "__main__":
Flags=2
Name=None
Level=1
printers = win32print.EnumPrinters(Flags, Name, Level)
print("\nChoose a printer to use:")
i=1
for p in printers:
print(i,')' , p[2])
i = i+1
if sys.version_info >= (3,):
raw_data = bytes ("This is a test", "utf-8")
else:
raw_data = "This is a test"
printer = int(input())
printer_name = printers[printer-1][2] #win32print.GetDefaultPrinter ()
print("You chose ", printer_name, "\nI will now print from the specified file with this printer")
hPrinter = win32print.OpenPrinter (printer_name)
# root = Tk()
# root.filename = filedialog.askopenfilename(initialdir = "/Desktop",title = "Select file",filetypes = (("log files","*.log"),("all files","*.*")))
file_path = "some_file_path" # root.filename
file_directory = os.path.dirname(file_path)
# print (file_path)
print (file_directory)
observer = Observer()
event_handler = Handler(observer, file_path, file_directory)
observer.schedule(event_handler, path=file_directory, recursive=False)
observer.start()
observer.join()
any ideas would be appreciated
EDIT:
After some debugging I found out that Windows10 is changing the file modification time twice every time I save it.
The proof of concept code is this:
prev_modification_time = os.path.getmtime(file_path)
while True:
current_mod_time = os.path.getmtime(file_path)
if prev_modification_time != current_mod_time :
print ("the file was modified, last modification time is: ", current_mod_time)
prev_modification_time = current_mod_time
pass
Final edit:
After testing my code on linux (Debian Stretch to be exact) it worked like a charm. So this combined with the previous edit probably shows that watchdog works fine and it is windows10 that has some issue. Should I post it on a different question or here?

PySide, pySerial and threading causes segfault

I'm trying to use PySide and pySerial to make a cross-platform app that interacts with the serial port. I originally used Qtimers to poll the serial for data, but this put a large load on the cpu. So I've been attempting to use threads instead.
unfortunately using threads causes either Qt or pySerial to Segfault.
I've tried both python threads and QThreads, same problem, it happens on OSX, windows 8 and Ubuntu 12.04. using python 2.7 and Qt4
This question seemed to have a similar problem, here:
this thread also seems to be a similar problem
below is a small app that recreates the problem
#! /usr/bin/python
import sys
import serial
import threading
from PySide import QtCore, QtGui, QtUiTools
class serial_port_class(object):
def __init__(self, ui):
self.ui = ui
self.connected = False
def __del__(self):
self.disconnect_port()
def connect_port(self):
try:
self.serial_port = serial.Serial("/dev/tty.usbmodem1451", 9600, timeout = None)
self.connected = True
except serial.SerialException, e:
self.connected = False
if self.connected:
self.serial_thread = threading.Thread(target=self.recieve_port, args=([self.ui]))
self.serial_thread.start()
def disconnect_port(self):
self.connected = False
self.serial_thread.join()
self.serial_port.close()
def recieve_port(self, ui):
while self.connected:
try:
text = self.serial_port.read(1)
if text != '':
ui.plain_edit.appendPlainText(text)
except serial.SerialException, e:
connected = False
class KeyPressEater(QtCore.QObject):
def eventFilter(self, obj, event):
global serial_port
if event.type() == QtCore.QEvent.KeyPress:
ch = event.text().encode('utf-8')
if serial_port.connected == True:
serial_port.serial_port.write(ch)
return QtCore.QObject.eventFilter(self, obj, event)
def main():
global serial_port
app = QtGui.QApplication(sys.argv)
ui = QtGui.QWidget()
ui.plain_edit = QtGui.QPlainTextEdit(ui)
keyFilter = KeyPressEater(ui)
ui.plain_edit.installEventFilter(keyFilter)
serial_port = serial_port_class(ui)
serial_port.connect_port()
ui.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
sometimes it takes a bit of data IO to trigger the segfault, sometime a single character will trigger it...
I'm not really sure how I would go about debugging this further....
All help is appreciated!!!!!!
UPDATE
I've recreated the the problem with different hard, and have asked this same question on the Qt community forum
Qt objects are not thread safe, so calling
ui.plain_edit.appendPlainText(text)
from another thread was bad.
a way around this is to use QThreads and the inbuilt thread safe messageing and events system
something like:
class receivePort(QThread):
message = QtCore.Signal(str)
def __init__(self):
self.connected = False
QThread.__init__(self)
def run(self):
while self.connected:
try:
text = self.serial_port.read(1)
if text != '':
self.message.emit(text)
except serial.SerialException, e:
connected = False
and the following to connect it up:
serial_thread = receivePort()
serial_thread.message.connect(write_terminal, QtCore.Qt.QueuedConnection)
serial_thread.start()
where write_terminal has a signature of:
def write_terminal(text):
ui.plain_edit.appendPlainText(text)

Resources