I have two Python scripts that i need to communicate with each another. The first is a GUI made in PySide2. The GUI consists of simple controls for controlling a bluetooth audio device (play, pause, next, previous, etc...). These commands operate with a second python script that i found. The second script is a loop that waits for these commands to be entered and responds once those commands are executed. I'm pretty new to programming, i'm guessing this is essentially connecting a front end with a back end, but its something i've never done before.
I've written a simplified version of my GUI to only display the controls i need. The "back-end" is also below but can originally be found here: https://scribles.net/controlling-bluetooth-audio-on-raspberry-pi/
I've previously asked a similar question and was given a solid and working answer by #eyllanesc here: Execute command to a Python script from separate Python script? However, using the QProcess method i could not work out how to get the print outputs from the back-end into the front-end script. The error messages print correctly however. I have tried playing around with sys.stdout in the back-end, variants of process.read, and QByteArrays but can't seem to get anything going.
The other issue i'm having is that the script will only work if a bluetooth device is connected prior to starting the script. If i disconnect while it is running and try to reconnect, it will no longer accept commands. If there is also a way to monitor whether a device is playing/paused so that the play/pause button can update depending on the devices state, that would also be useful, but its not important at this stage.
Theres a number of ways that it can be done, but i feel that ultimately it would be better for me to have both scripts integrated into one, however i'm open to any solution that works. If anyone has any advice or can get me started i'd be very appreciative!
Front-end:
import sys
from PySide2.QtWidgets import *
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.playbtn = QPushButton("Play")
self.nextbtn = QPushButton("Next")
self.prevbtn = QPushButton("Prev")
layout = QVBoxLayout()
layout.addWidget(self.playbtn)
layout.addWidget(self.nextbtn)
layout.addWidget(self.prevbtn)
self.setLayout(layout)
self.playbtn.released.connect(self.btnplay)
self.nextbtn.released.connect(self.btnnext)
self.prevbtn.released.connect(self.btnprev)
def btnplay(self): #play button turns into pause button upon being pressed
status = self.playbtn.text()
if status == "Play":
self.playbtn.setText("Pause")
print("Play Pressed")
elif status == "Pause":
self.playbtn.setText("Play")
print("Pause pressed")
def btnnext(self):
print("Next pressed")
def btnprev(self):
print("Prev pressed")
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
Back-end:
import dbus, dbus.mainloop.glib, sys
from gi.repository import GLib
def on_property_changed(interface, changed, invalidated):
if interface != 'org.bluez.MediaPlayer1':
return
for prop, value in changed.items():
if prop == 'Status':
print('Playback Status: {}'.format(value))
elif prop == 'Track':
print('Music Info:')
for key in ('Title', 'Artist', 'Album'):
print(' {}: {}'.format(key, value.get(key, '')))
def on_playback_control(fd, condition):
str = fd.readline()
if str.startswith('play'):
player_iface.Play()
elif str.startswith('pause'):
player_iface.Pause()
elif str.startswith('next'):
player_iface.Next()
elif str.startswith('prev'):
player_iface.Previous()
elif str.startswith('vol'):
vol = int(str.split()[1])
if vol not in range(0, 128):
print('Possible Values: 0-127')
return True
transport_prop_iface.Set(
'org.bluez.MediaTransport1',
'Volume',
dbus.UInt16(vol))
return True
if __name__ == '__main__':
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()
obj = bus.get_object('org.bluez', "/")
mgr = dbus.Interface(obj, 'org.freedesktop.DBus.ObjectManager')
player_iface = None
transport_prop_iface = None
for path, ifaces in mgr.GetManagedObjects().items():
if 'org.bluez.MediaPlayer1' in ifaces:
player_iface = dbus.Interface(
bus.get_object('org.bluez', path),
'org.bluez.MediaPlayer1')
elif 'org.bluez.MediaTransport1' in ifaces:
transport_prop_iface = dbus.Interface(
bus.get_object('org.bluez', path),
'org.freedesktop.DBus.Properties')
if not player_iface:
sys.exit('Error: Media Player not found.')
if not transport_prop_iface:
sys.exit('Error: DBus.Properties iface not found.')
bus.add_signal_receiver(
on_property_changed,
bus_name='org.bluez',
signal_name='PropertiesChanged',
dbus_interface='org.freedesktop.DBus.Properties')
GLib.io_add_watch(sys.stdin, GLib.IO_IN, on_playback_control)
GLib.MainLoop().run()
UPDATE 31/10/2020:
I've been playing around with the QProcess class suggested in my earlier question linked above. By using it on the button press functions and adding sys.exit after the command has been executed, it eliminates the need for a device to always be connected, but i still can't find a way to recieve the print outputs from back-end script. It also feels like a really dirty way of working. It also retains the issue with the play/pause state not automatically updating. If anyone has any suggestions i would be very grateful!
import sys
import os.path
from PySide2.QtCore import *
from PySide2.QtWidgets import *
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
class MainWindow(QWidget):
def __init__(self):
QWidget.__init__(self)
self.playbtn = QPushButton("Play")
self.nextbtn = QPushButton("Next")
self.prevbtn = QPushButton("Prev")
layout = QVBoxLayout()
layout.addWidget(self.playbtn)
layout.addWidget(self.nextbtn)
layout.addWidget(self.prevbtn)
self.setLayout(layout)
self.playbtn.released.connect(self.btnplay)
self.nextbtn.released.connect(self.btnnext)
self.prevbtn.released.connect(self.btnprev)
def btnplay(self):
self.process = QProcess()
self.process.readyReadStandardError.connect(self.handle_readyReadStandardError)
self.process.readyReadStandardOutput.connect(self.handle_readyReadStandardOutput)
self.process.setProgram(sys.executable)
script_path = os.path.join(CURRENT_DIR, "test2.py")
self.process.setArguments([script_path])
self.process.start()
status = self.playbtn.text()
if status == "Play":
command = "play"
self.playbtn.setText("Pause")
print("play pressed")
elif status == "Pause":
command = "pause"
self.playbtn.setText("Play")
print("pause pressed")
msg = "{}\n".format(command)
self.process.write(msg.encode())
def btnnext(self):
self.process = QProcess()
self.process.readyReadStandardError.connect(self.handle_readyReadStandardError)
self.process.readyReadStandardOutput.connect(self.handle_readyReadStandardOutput)
self.process.setProgram(sys.executable)
script_path = os.path.join(CURRENT_DIR, "test2.py")
self.process.setArguments([script_path])
self.process.start()
command = "next"
msg = "{}\n".format(command)
self.process.write(msg.encode())
print("next pressed")
def btnprev(self):
self.process = QProcess()
self.process.readyReadStandardError.connect(self.handle_readyReadStandardError)
self.process.readyReadStandardOutput.connect(self.handle_readyReadStandardOutput)
self.process.setProgram(sys.executable)
script_path = os.path.join(CURRENT_DIR, "test2.py")
self.process.setArguments([script_path])
self.process.start()
command = "prev"
msg = "{}\n".format(command)
self.process.write(msg.encode())
print("prev pressed")
def handle_readyReadStandardError(self):
print(self.process.readAllStandardError().data().decode())
def handle_readyReadStandardOutput(self):
print(self.process.readAllStandardOutput().data().decode())
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
IMHO the OP has an XY problem that adds unnecessary complexity to the application since the dbus eventloop can coexist with the Qt one as I show in the following example:
import sys
import dbus
import dbus.mainloop.glib
from PyQt5 import QtCore, QtWidgets
class AudioManager(QtCore.QObject):
statusChanged = QtCore.pyqtSignal(str)
infoChanged = QtCore.pyqtSignal(dict)
def __init__(self, parent=None):
super().__init__(parent)
self._player_iface = None
self._transport_prop_iface = None
def initialize(self):
bus = dbus.SystemBus()
obj = bus.get_object("org.bluez", "/")
mgr = dbus.Interface(obj, "org.freedesktop.DBus.ObjectManager")
player_iface = None
transport_prop_iface = None
for path, ifaces in mgr.GetManagedObjects().items():
if "org.bluez.MediaPlayer1" in ifaces:
player_iface = dbus.Interface(
bus.get_object("org.bluez", path), "org.bluez.MediaPlayer1"
)
elif "org.bluez.MediaTransport1" in ifaces:
transport_prop_iface = dbus.Interface(
bus.get_object("org.bluez", path), "org.freedesktop.DBus.Properties"
)
if not player_iface:
raise Exception("Error: Media Player not found.")
if not transport_prop_iface:
raise Exception("Error: DBus.Properties iface not found.")
self._player_iface = player_iface
self._transport_prop_iface = transport_prop_iface
bus.add_signal_receiver(
self.handle_property_changed,
bus_name="org.bluez",
signal_name="PropertiesChanged",
dbus_interface="org.freedesktop.DBus.Properties",
)
def play(self):
self._player_iface.Play()
def pause(self):
self._player_iface.Pause()
def next(self):
self._player_iface.Next()
def previous(self):
self._player_iface.Previous()
def set_volume(self, Volume):
if Volume not in range(0, 128):
raise ValueError("Possible Values: 0-127")
self._transport_prop_iface.Set(
"org.bluez.MediaTransport1", "Volume", dbus.UInt16(vol)
)
def handle_property_changed(self, interface, changed, invalidated):
if interface != "org.bluez.MediaPlayer1":
return
for prop, value in changed.items():
if prop == "Status":
self.statusChanged.emit(value)
elif prop == "Track":
info = dict()
for key in ("Title", "Artist", "Album"):
info[key] = str(value.get(key, ""))
self.infoChanged.emit(info)
class MainWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self._manager = AudioManager()
self._manager.infoChanged.connect(self.handle_info_changed)
self._manager.initialize()
self.playbtn = QtWidgets.QPushButton("Play")
self.nextbtn = QtWidgets.QPushButton("Next")
self.prevbtn = QtWidgets.QPushButton("Prev")
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.playbtn)
layout.addWidget(self.nextbtn)
layout.addWidget(self.prevbtn)
self.playbtn.released.connect(self._manager.play)
self.nextbtn.released.connect(self._manager.next)
self.prevbtn.released.connect(self._manager.previous)
def handle_info_changed(self, info):
print(info)
if __name__ == "__main__":
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec_()
im not familiar with what you're using on your backend, but it should be as simple as this:
# in your frontend
import NAME_OF_BACKEND_PY_FILE_HERE as backend
def btnplay(self): #play button turns into pause button upon being pressed
status = self.playbtn.text()
if status == "Play":
self.playbtn.setText("Pause")
print("Play Pressed")
backend.on_playback_control("play")
elif status == "Pause":
self.playbtn.setText("Play")
print("Pause pressed")
backend.on_playback_control("pause")
def btnnext(self):
print("Next pressed")
backend.on_playback_control("next")
def btnprev(self):
print("Prev pressed")
backend.on_playback_control("prev")
in your backend you should also remove this line: if __name__ == '__main__':
and unindent all the code below it. im not sure how that function should be called normally or what the second variable 'condition' is for. but that's what I can come up with
Related
I tried to implement a windows hook to grab foreground window event. This code is probably bad because I didnt understand much.
Can you tell me if that is better than a simple while loop that checks the foreground window every 0.1s for a change?
Is this code okay or just horrible?
How can I stop this "listener" when I want to close the app?
"""
Script using the Windows API to register for window focus changes and print the
titles of newly focused windows.
"""
#https://github.com/Danesprite/windows-fun/blob/master/window%20change%20listener.py
import sys
import time
import ctypes
import ctypes.wintypes
import threading
import six
import win32gui
class ObservableWindowChange(object):
def __init__(self):
self.__observers = []
def register_observer(self, observer):
self.__observers.append(observer)
def notify_observers(self, *args, **kwargs):
win_title = ''.join(args)
if win_title == '':
return ''
for observer in self.__observers:
observer.notify(win_title)
def start_event_listener(self):
# Create a WindowChangeEventListener object with this instance of
# ObservableWindowChange as a parameter (self)
listener = WindowChangeEventListener(self)
listener.listen_forever()
class IWindowChangeObserver(object):
"""
Base class for observing window changes
"""
def __init__(self, observable, interface=None):
observable.register_observer(self)
self.interface = interface
def notify(self, win_title):
raise NotImplementedError
class WindowChangeEventListener(object):
"""
WindowChangeEventListener
"""
def __init__(self, observable):
self.observable = observable
def listen_forever(self):
# This is to fix a problem with ascii encoding (windows with Unicode in
# their titles)
if six.PY2:
reload(sys)
sys.setdefaultencoding('utf8')
# Look here for DWORD event constants:
# http://stackoverflow.com/questions/15927262/convert-dword-event-constant-from-wineventproc-to-name-in-c-sharp
# Don't worry, they work for python too.
EVENT_SYSTEM_DIALOGSTART = 0x0010
WINEVENT_OUTOFCONTEXT = 0x0000
EVENT_SYSTEM_FOREGROUND = 0x0003
WINEVENT_SKIPOWNPROCESS = 0x0002
user32 = ctypes.windll.user32
ole32 = ctypes.windll.ole32
EnumWindows = ctypes.windll.user32.EnumWindows
EnumWindowsProc = ctypes.WINFUNCTYPE(ctypes.c_bool,
ctypes.POINTER(ctypes.c_int),
ctypes.POINTER(ctypes.c_int))
GetWindowText = ctypes.windll.user32.GetWindowTextW
GetForegroundWindow = ctypes.windll.user32.GetForegroundWindow
GetWindowTextLength = ctypes.windll.user32.GetWindowTextLengthW
IsWindowVisible = ctypes.windll.user32.IsWindowVisible
ole32.CoInitialize(0)
WinEventProcType = ctypes.WINFUNCTYPE(
None,
ctypes.wintypes.HANDLE,
ctypes.wintypes.DWORD,
ctypes.wintypes.HWND,
ctypes.wintypes.LONG,
ctypes.wintypes.LONG,
ctypes.wintypes.DWORD,
ctypes.wintypes.DWORD
)
def callback(hWinEventHook, event, hwnd, idObject, idChild, dwEventThread,
dwmsEventTime):
length = GetWindowTextLength(hwnd)
buff = ctypes.create_unicode_buffer(length + 1)
GetWindowText(hwnd, buff, length + 1)
# hwnd = GetForegroundWindow()
# Notify observers
self.observable.notify_observers(buff.value)
WinEventProc = WinEventProcType(callback)
user32.SetWinEventHook.restype = ctypes.wintypes.HANDLE
hook = user32.SetWinEventHook(
EVENT_SYSTEM_FOREGROUND,
EVENT_SYSTEM_FOREGROUND,
0,
WinEventProc,
0,
0,
WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS
)
if hook == 0:
print('SetWinEventHook failed')
exit(1)
msg = ctypes.wintypes.MSG()
while user32.GetMessageW(ctypes.byref(msg), 0, 0, 0) != 0:
user32.TranslateMessageW(msg)
user32.DispatchMessageW(msg)
# Stopped receiving events, so clear up the winevent hook and uninitialise.
print('Stopped receiving new window change events. Exiting...')
user32.UnhookWinEvent(hook)
ole32.CoUninitialize()
class WindowObserver(IWindowChangeObserver):
def notify(self, win_text):
time.sleep(0.3)
win_hwnd = win32gui.GetForegroundWindow()
# print("Window '%s' focused" % win_text)
if self.interface:
self.interface.update_perso_and_visibility(win_hwnd)
def run(interface=None):
# Create an observable and an observer observing it
subject = ObservableWindowChange()
observer = WindowObserver(subject, interface=interface)
# Listen for window changes
subject.start_event_listener()
class ThreadListener(threading.Thread):
def __init__(self, interface=None):
threading.Thread.__init__(self)
self.interface = interface
self.start()
def run(self):
run(self.interface)
def kill(self):
print("kill")
if __name__ == '__main__':
# Start the 'run' method in a daemonized thread.
# t = threading.Thread(target=run)
# t.setDaemon(True)
# t.start()
t = ThreadListener(None)
# Keep the main thread running in a sleep loop until ctrl+c (SIGINT) is caught.
# Once the main thread terminates, all daemon threads will automatically
# terminate.
cpt=0
while True:
try:
time.sleep(0.1)
cpt+=1
if cpt>10:
t.kill()
except KeyboardInterrupt:
break
This is supposed to be a windows hook that detects the foreground window change. When the change occurs, the interface given as a parameter is updated.
It seems to work but sometimes the detection doesn't happen.
Thanks for your help
The context:
I'm building a Graphical Interface with Qt creator and the "behaviour" file in python. A test version of my GUI is:
The expected behaviour:
I am running 2 different threads which are referred to the same function with different input arguments. With the SELECTOR button I can assign the value of 1 or 2 to a variable (and display it)
The button Start thread enables the correct thread to start (the first time).
The loop should be turned off by the stop button by modifying the global running variable.
This is my code
# -*- coding: utf-8 -*-
from PyQt4 import QtCore, QtGui, uic
import sys
import threading
import time
import Queue
running = False
first_thread = None
second_thread = None
form_class = uic.loadUiType("simple2.ui")[0]
q = Queue.Queue()
select = 0
def action(string, queue): #function called by threads
global running
while(running):
phrase = string
if queue.qsize() < 10:
queue.put(phrase)
#else:
# print queue.qsize()
class MyWindowClass(QtGui.QMainWindow, form_class):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.setupUi(self)
#buttons
self.startButton.clicked.connect(self.start_clicked)
self.stopButton.clicked.connect(self.stop_clicked)
self.selector.clicked.connect(self.sel_click)
#variables
self.first = False
self.second = False
#queue
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.update_phrase)
self.timer.start(1)
def start_clicked(self): #start button callback
global select
if select > 0:
global running
running = True
print "started"
if (not self.first) & (select == 1):
first_thread.start()
self.first = True
if (not self.second) & (select == 2):
second_thread.start()
self.second = True
self.startButton.setEnabled(False)
self.startButton.setText('Starting...')
def stop_clicked(self): #stop button callback
global running
running = False
print "stopped"
self.startButton.setEnabled(True)
self.startButton.setText('Start Thread')
def sel_click(self): #selector button callback
global select
if select < 2:
select = select + 1
else:
select = 1
self.thread_counter.setText(str(select))
def update_phrase(self): #looping function
global running
if (not q.empty()) & running:
self.startButton.setText('Thread on')
abc = q.get()
print abc
def closeEvent(self, event):
global running
running = False
if __name__ == "__main__":
first_thread = threading.Thread(target=action, args = ("first", q))
second_thread = threading.Thread(target=action, args = ("second", q))
app = QtGui.QApplication(sys.argv)
w = MyWindowClass(None)
w.setWindowTitle('Multiple threads test in python')
w.show()
app.exec_()
For now, each thread should simple print on terminal their arguments ("First" or "Second").
If threads are started for the first time, my code works. But I would like to switch between threads infinite times.
Since threads cannot be stopped, is there a way to "pause" them?
I cannot find a solution, I hope someone will help me also with a piece of code. Thank you in advance
You can use Lock class to do that, a simple example would be:
import threading
lock = threading.Lock()
//here it will be lock
lock.acquire() # will block if lock is already held
...
then in other side do
//this will wake up
lock.release()
you can read more here http://effbot.org/zone/thread-synchronization.htm
I'm currently learning both Python and GTK 3+ and I've a problem when synchronizing threads. I'll try to be quick and clear:
I have to make a Social Network client. As the purpose is to learn how to create a GUI the "access to the social network API" will be simulated, but I have to "wait" for network responses with time.sleep(). Calling time.sleep() in the main thread freezes the GUI (it stops the execution of Gtk.Main()) so I have to make all my connections in a separate thread.
And here lies my problem. When I'm authenticating a user (verifying_credentials) I need to wait from that thread to finish to continue the execution of the main program. If I try a Thread.join GUI freezes. I've tried using queues, but queue.get is also blocking Gtk.main().
I've tried emitting a signal when my thread is finished , but the handler starts in the same thread, so when I try to modify the GUI (which I need) , program crashes (you're not supposed to touch the GUI from anywhere except main thread).
My solution ? I do busy-waiting / active-waiting , which is by definition an antipattern. I keep asking if the thread has finished and forcing cicles of Gtk.main()
There has to be another way, a more elegant / efficient way than mine.
I don't know if I can signal another thread, or there is a way of using queues without blocking the main thread. Any help will be very much appreciated.
The python code :
from os.path import abspath, dirname, join
import gettext
import threading
import math
import time
import random
import locale
from gi.repository import Gtk, Gdk, GLib, GObject
import list
APP = "UDC_Social_API"
user_list = {
"user": "password",
"admin": "admin",
"carnotan": "1234"
}
class UDC_Social_API:
def on_applicationwindow1_key_press_event(self, widget, event):
# keyname = Gdk.keyval_name(event.keyval)
# print (_("Key %(name)s (%(val)d) was pressed" )%{"name":keyname, "val":event.keyval})
if event.keyval == 65293:
self.on_login_button_clicked()
def delay(self):
if self.delay_option.get_active():
time.sleep(math.exp(random.random()*5))
else:
pass
def active_waiting(self):
while self.finished is False:
Gtk.main_iteration_do(False)
self.finished = False
self.z_handler(None)
def verify_credentials(self, user, password):
GLib.idle_add(self.active_waiting)
self.delay()
if user in user_list:
if password == user_list.get(user):
self.authentication = True
self.finished = True
else:
self.authentication = False
self.finished = True
else:
self.authentication = False
self.finished = True
def on_login_button_clicked(self, data=None):
user = self.user_entry.get_text()
password = self.password_entry.get_text()
thread = threading.Thread(target=self.verify_credentials, args=(user, password))
thread.daemon = True
thread.start()
def z_handler(self, data=None):
if self.authentication is False:
self.message_dialog.set_markup(_("User/Password incorrect\nPlease, verify login information"))
self.message_dialog.run()
self.message_dialog.hide()
return False
else:
self.window.hide()
print ("Success!")
def on_applicationwindow1_destroy(self, data=None):
Gtk.main_quit()
def on_gtk_about_activate(self, menuitem, data=None):
self.aboutdialog.run()
self.aboutdialog.hide()
def on_gtk_cut_activate(self, widget):
# Get the bounds of the selected text
bounds = self.focus.get_selection_bounds()
# if the bounds of the selection are not an empty tuple,
# put the selection in the variable chars
# and copy it to the clipboard
# (get_selection_bounds returns an empty tuple if there is no selection)
# then delete the selection
if bounds:
chars = self.focus.get_chars(*bounds)
self.clipboard.set_text(chars, -1)
self.focus.delete_text(bounds[0], bounds[1])
else:
pass
def on_gtk_copy_activate(self, widget):
# Get the bounds of the selected text
bounds = self.focus.get_selection_bounds()
# if the bounds of the selection are not an empty tuple,
# put the selection in the variable chars
# and copy it to the clipboard
# (get_selection_bounds returns an empty tuple if there is no selection)
if bounds:
chars = self.focus.get_chars(*bounds)
self.clipboard.set_text(chars, -1)
else:
pass
def on_gtk_paste_activate(self, widget):
# Get the text from the clipboard
text = self.clipboard.wait_for_text()
if text is not None:
# If there's text selected in the target
# delete it and paste the contents of the clipboard
bounds = self.focus.get_selection_bounds()
if bounds:
self.focus.delete_text(bounds[0], bounds[1])
self.focus.insert_text(text, bounds[0])
# else insert the text in the current position of the cursor in the target
else:
pos = self.focus.get_position()
self.focus.insert_text(text, pos)
else:
pass
def on_entry_focus(self, widget, event):
self.focus = widget
def create_menubar(self):
self.file_menu=self.builder.get_object("menuitem1")
self.edit_menu=self.builder.get_object("menuitem2")
self.options_menu=self.builder.get_object("option")
self.help_menu=self.builder.get_object("menuitem4")
self.languages_menu=self.builder.get_object("menuitem3")
self.delay_option = self.builder.get_object("delay_option")
self.gtk_quit_menu=self.builder.get_object("gtk_quit_menu")
self.gtk_cut_menu=self.builder.get_object("gtk_cut_menu")
self.gtk_copy_menu=self.builder.get_object("gtk_copy_menu")
self.gtk_paste_menu=self.builder.get_object("gtk_paste_menu")
self.gtk_about_menu=self.builder.get_object("gtk_about_menu")
self.galician_option=self.builder.get_object("radiomenuitem1")
self.spanish_option=self.builder.get_object("radiomenuitem2")
self.english_option=self.builder.get_object("radiomenuitem3")
def set_menubar_names(self):
self.file_menu.set_label(_("_File"))
self.edit_menu.set_label(_("_Edit"))
self.options_menu.set_label(_("_Options"))
self.help_menu.set_label(_("_Help"))
self.languages_menu.set_label(_("_Languages"))
self.delay_option.set_label(_("_Delay"))
self.gtk_quit_menu.set_label(_("Quit"))
self.gtk_copy_menu.set_label(_("Copy"))
self.gtk_cut_menu.set_label(_("Cut"))
self.gtk_paste_menu.set_label(_("Paste"))
self.gtk_about_menu.set_label(_("About"))
self.galician_option.set_label(_("_Galician"))
self.spanish_option.set_label(_("_Spanish"))
self.english_option.set_label(_("_English"))
def create_login_box(self):
self.user_entry = self.builder.get_object("user_entry")
self.password_entry = self.builder.get_object("password_entry")
self.user_label=self.builder.get_object("user_label")
self.password_label=self.builder.get_object("password_label")
self.login_button=self.builder.get_object("login_button")
def set_login_box_names(self):
self.user_entry.set_placeholder_text(_("user"))
self.password_entry.set_placeholder_text(_("password"))
self.user_label.set_label(_("User"))
self.password_label.set_label(_("Password"))
self.login_button.set_label(_("Login"))
def create_about_dialog(self):
self.aboutdialog = self.builder.get_object("aboutdialog1")
self.aboutdialog.set_transient_for(self.window)
def set_about_dialog(self):
self.aboutdialog.set_comments(_("Developed for GTK 3+ and Python 3.4"))
def reset_names(self):
self.set_menubar_names()
self.set_login_box_names()
def on_radiomenuitem1_toggled(self, widget):
if widget.get_active():
self.lang_gl_ES.install()
self.reset_names()
self.window.queue_draw()
else:
pass
def on_radiomenuitem2_toggled(self, widget):
if widget.get_active():
self.lang_es_ES.install()
self.reset_names()
self.window.queue_draw()
else:
pass
def on_radiomenuitem3_toggled(self,widget):
if widget.get_active():
self.lang_en_US.install()
self.set_menubar_names()
self.window.queue_draw()
else:
pass
def set_languages(self):
WHERE_AM_I = abspath(dirname(__file__))
locale.setlocale(locale.LC_ALL, '')
locale.bindtextdomain(APP, WHERE_AM_I)
locale_path = WHERE_AM_I +'/'
self.builder.set_translation_domain(APP)
gettext.find(APP,localedir=locale_path,languages=['gl_ES'])
gettext.find(APP,localedir=locale_path,languages=['es_ES'])
gettext.find(APP,localedir=locale_path,languages=['en_US'])
gettext.install(APP,locale_path)
gettext.textdomain(APP)
gettext.bindtextdomain(APP,locale_path)
self.lang_gl_ES=gettext.translation(APP,localedir=locale_path, languages=['gl_ES'])
self.lang_es_ES=gettext.translation(APP,localedir=locale_path, languages=['es_ES'])
self.lang_en_US=gettext.translation(APP,localedir=locale_path, languages=['en_US'])
def set_signals(self):
handlers = {
"on_applicationwindow1_destroy": self.on_applicationwindow1_destroy,
"on_gtk_about_activate": self.on_gtk_about_activate,
"on_login_button_clicked": self.on_login_button_clicked,
"on_applicationwindow1_key_press_event": self.on_applicationwindow1_key_press_event,
"on_entry_focus": self.on_entry_focus,
"on_gtk_cut_activate": self.on_gtk_cut_activate,
"on_gtk_copy_activate": self.on_gtk_copy_activate,
"on_gtk_paste_activate": self.on_gtk_paste_activate,
"on_radiomenuitem1_toggled": self.on_radiomenuitem1_toggled,
"on_radiomenuitem2_toggled": self.on_radiomenuitem2_toggled,
"on_radiomenuitem3_toggled": self.on_radiomenuitem3_toggled
}
self.builder.connect_signals(handlers)
def __init__(self):
# GObject.signal_new("z_signal", Gtk.ApplicationWindow, GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, ())
self.builder = Gtk.Builder()
self.builder.add_from_file("p1.glade")
self.window = self.builder.get_object("applicationwindow1")
self.set_languages()
self.create_menubar()
self.create_login_box()
self.create_about_dialog()
self.reset_names()
self.set_signals()
self.focus = None
self.finished = False
self.authentication = False
# self.statusbar = self.builder.get_object("statusbar1")
# self.context_id = self.statusbar.get_context_id("status")
# self.status_count = 0
self.message_dialog = self.builder.get_object("messagedialog1")
self.message_dialog.set_transient_for(self.window)
self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
self.window.show_all()
if __name__ == "__main__":
GObject.threads_init()
main = UDC_Social_API()
Gtk.main()
Glade file is in pastebin, because it will exceed post size limit.
http://pastebin.com/8S3k7f6J
Thanks in advance for any help you could provide.
You can use GLib.idle_add to schedule a callback to be executed by the event loop in the main thread of your program. This means it provides a safe way to schedule a GUI update from a background thread. So, you can just let your background thread run normally, let the main thread return control to the event loop, and then make the appropriate GUI updates from the background thread via GLib.idle_add once it's done:
def verify_credentials(self, user, password):
self.delay()
if user in user_list:
if password == user_list.get(user):
self.authentication = True
else:
self.authentication = False
else:
self.authentication = False
# Schedule z_handler to be called by the event loop in the main thread.
GLib.idle_add(z_handler, None)
def z_handler(self, data=None):
if not self.authentication:
self.message_dialog.set_markup(_("User/Password incorrect\nPlease, verify login information"))
self.message_dialog.run()
self.message_dialog.hide()
return False
else:
self.window.hide()
print ("Success!")
You're actually pretty close to using this same method, you're just doing it in an awkward way - you're scheduling active_waiting to run in the main thread, which waits until the background thread is done, and then calls z_handler. Scheduling z_handler directly, after the background thread is done with its work, is much simpler.
I'm trying to build a PyQt app which (among other things) has the ability via a QTextEdit Box to function like a serial terminal program (HyperTerminal, TeraTerm, etc.) I've read through a few examples from the PySerial page and I think I've managed to get the receive data thread working properly but maybe not as efficiently as possible.
My problem is how do I take the last typed character in the QTextEdit box and send that out the serial connection? I've tried using the textChanged signal that QTextEdit emits, but that then sends everything that I type AND that it receives. I've tried setting up an eventFilter in my main GUI class, but I can't figure out how to get that over to the serial function in another file. Do I want to have a separate thread that listens for a signal emitted from the eventFilter? How do I do that? Is there a more elegant way to do this?
I'm sure I've just managed to overthink this and the solution is simple, but I'm somewhat struggling with it. I'll attach the relevant code snippets (not a full code set) and perhaps somebody can point me in the right direction. If anybody also thinks that the threading that I'm doing could be done in a more efficient manner, then please relay that to me as well!
Thanks for any help that anybody can provide!
Main File:
import sys
from PyQt4 import QtGui
from MainGUI import TestGUI
from SerialClasses import *
from SerialMiniterm import *
class StartMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(StartMainWindow, self).__init__(parent)
self.ui = TestGUI()
self.ui.setupUi(self)
self.ui.serialTextEditBox.installEventFilter(self)
def eventFilter(self, source, event):
if (event.type() == QtCore.QEvent.KeyPress and source is self.ui.serialTextEditBox):
# print some debug statements to console
if (event.key() == QtCore.Qt.Key_Tab):
print ('Tab pressed')
print ('key pressed: %s' % event.text())
print ('code pressed: %d' % event.key())
# do i emit a signal here? how do i catch it in thread?
self.emit(QtCore.SIGNAL('transmitSerialData(QString)'), event.key())
return True
return QtGui.QTextEdit.eventFilter(self, source, event)
def serialConnectCallback(self):
self.miniterm = SerialMiniterm(self.ui, self.SerialSettings)
self.miniterm.start()
temp = self.SerialSettings.Port + 1
self.ui.serialLabel.setText("<font color = green>Serial Terminal Connected on COM%d" % temp)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
app.setStyle("Cleanlooks")
myapp = StartMainWindow()
myapp.show()
sys.exit(app.exec_())
SerialMiniterm.py:
import serial
from PyQt4 import QtGui, QtCore
def character(b):
return b
class SerialMiniterm(object):
def __init__(self, ui, SerialSettings):
self.SerialSettings = SerialSettings
self.ui = ui
self.serial = serial.Serial(self.SerialSettings.Port, self.SerialSettings.BaudRate, parity=self.SerialSettings.Parity, rtscts=self.SerialSettings.RTS_CTS, xonxoff=self.SerialSettings.Xon_Xoff, timeout=1)
self.repr_mode = self.SerialSettings.RxMode
self.convert_outgoing = self.SerialSettings.NewlineMode
self.newline = NEWLINE_CONVERISON_MAP[self.convert_outgoing]
self.dtr_state = True
self.rts_state = True
self.break_state = False
def _start_reader(self):
"""Start reader thread"""
self._reader_alive = True
self.receiver_thread = ReaderThread(self.alive, self._reader_alive, self.repr_mode, self.convert_outgoing, self.serial)
self.receiver_thread.connect(self.receiver_thread, QtCore.SIGNAL("updateSerialTextBox(QString)"), self.updateTextBox)
self.receiver_thread.start()
def _stop_reader(self):
"""Stop reader thread only, wait for clean exit of thread"""
self._reader_alive = False
self.receiver_thread.join()
def updateTextBox(self, q):
self.ui.serialTextEditBox.insertPlainText(q)
self.ui.serialTextEditBox.moveCursor(QtGui.QTextCursor.End)
#print "got here with value %s..." % q
def start(self):
self.alive = True
self._start_reader()
# how do i handle transmitter thread?
def stop(self):
self.alive = False
def join(self, transmit_only=False):
self.transmitter_thread.join()
if not transmit_only:
self.receiver_thread.join()
class ReaderThread(QtCore.QThread):
def __init__(self, alive, _reader_alive, repr_mode, convert_outgoing, serial, parent=None):
QtCore.QThread.__init__(self, parent)
self.alive = alive
self._reader_alive = _reader_alive
self.repr_mode = repr_mode
self.convert_outgoing = convert_outgoing
self.serial = serial
def __del__(self):
self.wait()
def run(self):
"""loop and copy serial->console"""
while self.alive and self._reader_alive:
data = self.serial.read(self.serial.inWaiting())
if data: #check if not timeout
q = data
self.emit(QtCore.SIGNAL('updateSerialTextBox(QString)'), q)
Something like this?
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Terminal(QtGui.QPlainTextEdit):
def keyPressEvent(self, event):
print event.text()
return QtGui.QPlainTextEdit.keyPressEvent(self, event)
term = Terminal()
term.show()
I'm trying to change the state of a Gtk status icon from a thread as specified in MailThread.run() below, but I don't know how to reach the status icon object from the method in order to change set_visible to either True or False.
Basically I would like to know what to write in place of "# set status icon visible off/on".
#!/usr/bin/env python
import gtk, sys, pynotify, imaplib, time, threading
from email import parser
class Mail:
def check_mail(self):
obj = imaplib.IMAP4_SSL('imap.gmail.com','993')
acc = 'email'
pwrd = 'pass'
obj.login(acc, pwrd)
obj.select()
num = str(len(obj.search(None,'UnSeen')[1][0].split()))
return acc, num
class MailThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
gtk.gdk.threads_init()
def run(self):
while True:
print "hello"
mail = Mail()
num = mail.check_mail()[1]
if num < 1:
# set status icon visible off
else:
# set status icon visible on
time.sleep(60)
class StatusIcon:
# activate callback
def activate( self, widget, data=None):
mail = Mail()
acc, num = mail.check_mail()
pynotify.init("myapp")
n = pynotify.Notification(acc, "You have " + num + " unread e-mails.", "emblem-mail")
n.show()
# Show_Hide callback
def show_hide(self, widget,response_id, data= None):
if response_id == gtk.RESPONSE_YES:
widget.hide()
else:
widget.hide()
# destroyer callback
def destroyer(self, widget,response_id, data= None):
if response_id == gtk.RESPONSE_OK:
gtk.main_quit()
else:
widget.hide()
# popup callback
def popup(self, button, widget, data=None):
dialog = gtk.MessageDialog(
parent = None,
flags = gtk.DIALOG_DESTROY_WITH_PARENT,
type = gtk.MESSAGE_INFO,
buttons = gtk.BUTTONS_OK_CANCEL,
message_format = "Do you want to close e-mail notifications?")
dialog.set_title('Exit')
dialog.connect('response', self.destroyer)
dialog.show()
def __init__(self):
# create a new Status Icon
self.staticon = gtk.StatusIcon()
self.staticon.set_from_icon_name("emblem-mail")
self.staticon.connect("activate", self.activate)
self.staticon.connect("popup_menu", self.popup)
self.staticon.set_visible(True)
# starting thread
thread = MailThread()
thread.setDaemon(True)
thread.start()
# invoking the main()
gtk.main()
if __name__ == "__main__":
# status icon
statusicon = StatusIcon()
You can accept the status icon in the thread's __init__():
class MailThread(threading.Thread):
def __init__(self, status_icon = None):
threading.Thread.__init__(self)
gtk.gdk.threads_init()
self.status_icon = status_icon
And then you can use it in run().
Additionally, you need to do all the GUI work from the main thread. The main thread has a queue maintained by GTK you can use to tell it to go do some GUI work. This is how it works:
def run(self):
# <...>
if num < 1:
gobject.idle_add(self.set_status_icon, False)
else:
gobject.idle_add(self.set_status_icon, True)
# <...>
def set_status_icon(self, state = False):
# code that changes icon state goes here
pass
idle_add basically means "add that to the queue and do it when you have some free time".