You are not logged in.

#1 2011-09-30 08:08:57

nsb
Member
From: Switzerland
Registered: 2008-03-26
Posts: 57

[SOLVED] PyQt4: Starting a loop after Application.exec_()

Hello

Since I want to use wifi-select insted of wicd, I wanted to create a little wifi monitor with pyqt. I thought about basically having a (infinite) loop, that reads the connection strenght (from /proc/net/wireless), updates the icon and waits some time. But I got stucked in one thing: I don't know, how to initiate this loop, I tried several things, but none worked out. Basically I would need a signal, that is emitted by QApplication.exec_() and connect it to the start of the loop or an event, that emits a signal, which in turn starts the loop. However I could not find anything. You can see the code here.

Thank you in advance!

nsb

Last edited by nsb (2011-09-30 14:43:28)

Offline

#2 2011-09-30 09:41:21

lunar
Member
Registered: 2010-10-04
Posts: 95

Re: [SOLVED] PyQt4: Starting a loop after Application.exec_()

You cannot run your own main loop besides the one of "QApplication".  Instead use a "QTimer" to periodically check the state of the wifi interface and emit signals accordingly.  Alternatively write a background thread based on "QThread", but in this case make sure that you never directly access the any part of the user interface from the thread.

Some remarks conerning your code: 

  • Don't mix old-style and new-style signals.  Use new-style API only, and avoid any use of "SIGNAL()" and "SLOT()". Thus "QtCore.QObject.connect(self.quitAction, QtCore.SIGNAL("triggered()"), QtGui.qApp, QtCore.SLOT("quit()"))" becomes "self.quitAction.triggered.connect(QtGui.qApp.quit)".

  • Use the "with" statement when dealing with files to make sure that the file is closed correctly.

  • Do not use "len()" to check for empty strings. "if len(l) != 0" is really bad style, and should simply be written as "if l".

  • Use more verbose names.  Writing "line" instead of "l" won't kill you, but makes your code easier to read, especially for other who have not seen the code before and are trying to help you now.

  • Use "os.path.join()" and string formatting instead of string concatentation to construct paths.  "conf.iconPath + "wifi_" + str(filePercentage) + ".svg"" becomes "os.path.join(conf.iconPath, 'wifi_{0}.svg'.format(filePercentage))".

  • You attempt to handle SIGINT is unlikely to work, because "sys.exit(0)" doesn't work inside the Qt event loop, as exceptions ("sys.exit()" merely raises SystemExit) don't make it through the event loop.  Try "QApplication.instance().quit()".

  • "sys.exit()" at the end of the program is really superfluous.  The interpreter exits at this point anyway.

Last edited by lunar (2011-09-30 09:42:13)

Offline

#3 2011-09-30 10:30:23

nsb
Member
From: Switzerland
Registered: 2008-03-26
Posts: 57

Re: [SOLVED] PyQt4: Starting a loop after Application.exec_()

Thanks for your tips! I wil implement them asap. But I have a little question:
Where is the best place to put the QTimer or the QThread? You can see in the code, that I experimented with them, but I had no success.
Should they go as a member into the WifiMonitor class and then started in the constructor?

Offline

#4 2011-09-30 10:45:48

lunar
Member
Registered: 2010-10-04
Posts: 95

Re: [SOLVED] PyQt4: Starting a loop after Application.exec_()

I'd split monitoring and display.  Implement "WifiMonitor" not as "QWidget", but instead merely as child class of "QObject".  In "WifiMonitor.__init__()", create a "QTimer" object with a sufficient interval, and connect its ".timeout()" signal to the ".get_status()" slot.  In this slot, parse the wifi state and emit signals according to the current state.

Create a separate "QWidget"-derived class, named for instance "WifiStatusWidget".  Create another separate class for the TrayIcon, e.g. "WifiStatusTrayIcon".  The constructor of both classes accept a "WifiMonitor" object, and connect to the signals of this monitors with slots that update the user interface accordingly.

In the "main" part of the module (after "if __name__ == '__main__'") setup a single WifiMonitor, and both the widget and the tray icon.  Pass the monitor to the widget and the tray icon, and show both.

This separation of concerns makes it easier to test the monitoring independently (e.g. by writing a little command line utility first), and to exchange parts of the user interface with other (e.g. replace the tray icon with a status notifier icon for better integration into KDE).

And btw, don't use parenthesis around conditions in if statements. This is wholly superfluous, and somewhat strange.  I for my part haven't seen this in years of Python programming.  Strange too is your "get_conf", which returns an integer through by its name it is supposed to return a configuration object.  "0" looks like you're trying to return a "NULL" pointer just like you'd do in C++.  Of course, it's only a small thing, but trying to use C/C++ idioms in Python is dangerous thing to do.  No offense meant, but just a little word of warning smile

Offline

#5 2011-09-30 11:48:02

nsb
Member
From: Switzerland
Registered: 2008-03-26
Posts: 57

Re: [SOLVED] PyQt4: Starting a loop after Application.exec_()

Yeah, you got me: I am actually more a C and C++ coder wink

About the tray icon: Wouldn't it make more sense, if it would be a member of the WifiStatusWidget?

Offline

#6 2011-09-30 12:29:55

lunar
Member
Registered: 2010-10-04
Posts: 95

Re: [SOLVED] PyQt4: Starting a loop after Application.exec_()

Why do you want to couple the tray icon to the widget?  Don't you consider these two independent from each other?  Are you sure that you never ever want to use the status widget without a tray icon in some future version of your code?

Generally, the less coupling between objects, the better.  Don't bind to objects together without need.  If I was you, I'd keep the monitor, the status widget and the tray icon independent from each other.  I'd probably not even pass the monitor object to the widget and the icon, but rather only connect them by signals and slots.  To put everything together I'd probably create a separate class deriving from "QApplication".  I've roughly sketched my suggestions in the following code skeleton.  Of course it misses any concrete implementation, but hopefully serves as design guide and illustrates what I mean:

class WifiStatusTrayIcon(QSystemTrayIcon):

    def __init__(self, interface, parent=None):
        QSystemTrayIcon.__init__(self, parent)
        # interface is the wifi interface whose status to show
        self.interface = interface
        # setup the tray icon user interface (e.g. the context menu and the
        # symbol)

    def show_wifi_status(self, interface, signal_strength):
        if interface == self.interface:
            # show the new signal strength

class WifiStatusWidget(QWidget):

    def __init__(self, interfaces, parent=None):
        QWidget.__init__(self, parent)
        # interfaces is a list of wifi interfaces whose status to show
        self.interfaces = interfaces
        # setup the user interface

    def show_wifi_status(self, interface, signal_strengh):
        # update the status display for the given interface

class WifiMonitor(QObject):

      # signal for status updates
    wifiStatusChanged = pyqtSignal(unicode, float)

    def __init__(self, parent=None):
        self.timer = QTimer(self)
        self.timer.setInterface(500)
        self.timer.timeout.connect(self.update_status)

    def start(self):
        self.timer.start()

    def stop(self):
        self.timer.stop()

    def update_status(self):
        # parse the status file
        parsed_status = self.parse_status_file()
        for interface, signal_strength in parsed_status:
            self.wifiStatusChanged.emit(interface, signal_strength)


class WifiMonitorApplication(QApplication):
    def __init__(self, args):
        QApplication.__init__(self, args)
        self.wifi_monitor = WifiMonitor()
        self.status_widget = WifiStatusWidget()
        self.status_icon = WifiStatusIcon()
        for widget in (self.status_widget, self.status_icon):
            self.wifi_monitor.wifiStatusChanged.connect(widget.show_wifi_status)
            widget.show()

def main():
    app = WifiMonitorApplication(sys.argv)
    app.exec_()


if __name__ == '__main__':
    main()

Last edited by lunar (2011-09-30 12:30:35)

Offline

#7 2011-09-30 14:43:02

nsb
Member
From: Switzerland
Registered: 2008-03-26
Posts: 57

Re: [SOLVED] PyQt4: Starting a loop after Application.exec_()

So I did redid it according to your plan and it works now. I will still have to do some work, until it does everything I want.

Anyway: thank you!

Offline

Board footer

Powered by FluxBB