Home Forums Wiki Doc Install Extras Screenshots Source Code Projects Blog Users Groups Register
Glx-Dock / Cairo-Dock List of forums Technical discussions | Discussions techniques Cannot get a new applet to work with multiple instances
The latest stable release is the *3.4.0* : How to install it here.
Note: We just switched from BZR to Git on Github! (only to host the code and your future pull requests)
Technical discussions | Discussions techniques

Subjects Author Language Messages Last message
[Locked] Cannot get a new applet to work with multiple instances
lonavera English 7 Eduardo Mucelli [Read]
03 May 2012 à 14:12

lonavera, Friday 09 December 2011 à 21:38


Subscription date : 09 December 2011
Messages : 2
Hello,

thanks for your great dock. I love it:)

I tried to extend it with a little applet. The applet should start an given script and should use the xml output to display the status. I adapted it from the extra applet DiskFree. Mostly it works right now, but I cannot get it work that the applet can be instanciated multiple times. I mean, it can but only one instance is useable:(

If someone could please take a look at my applet and tell me what my mistake is would really be great.

I thank you very much in advance for any help
lonavera

since i have not found any way to upload my archive i have appended the source code in the text

auto-load.conf
[Register]

author = lonavera
description = Run Custom External Scripts To Monitor The System
category = 6
version = 1.0.0
multi-instance = true


CairoDockPlugin.py
# -*- coding: utf-8 -*-

# DiskFree, plugin for Cairo-Dock. View the available disk space.
# Copyright 2010 Xavier Nayrac
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import os.path
import dbus
from dbus.mainloop.glib import DBusGMainLoop
import sys
import gobject

CAIRO_SERVICE = 'org.cairodock.CairoDock'
CAIRO_APPLET = 'org.cairodock.CairoDock.applet'
CAIRO_PATH = '/org/cairodock/CairoDock/'

class CairoDockPlugin(object):
    """
    I'm a basic Cairo-Dock plugin.
    I mainly set the D-bus stuff.
    """
    def __init__(self):
        self.__debugMode = False
        self.__name = os.path.basename(os.path.abspath("."))
        self.__path = CAIRO_PATH + self.__name
        self.__plugin = None # Je sert d'interface avec D-bus.
        self.__programLoop = None
        self.__initPlugin()
        
    def __initPlugin(self):
        self.__initDbus()
        self.__initCallbacks()
        self.__initLoop()
    
    def __initDbus(self):
        DBusGMainLoop(set_as_default = True)
        bus = dbus.SessionBus()
        try:
            dbusObject = bus.get_object(CAIRO_SERVICE, self.__path)
        except dbus.DBusException:
            print "<%s can't be found on the bus, exit>" % (self.__name)
            sys.exit(1)
        self.__plugin = dbus.Interface(dbusObject, CAIRO_APPLET)
    
    def __initCallbacks(self):
        self.__plugin.connect_to_signal("on_click", self.onClick)
        self.__plugin.connect_to_signal("on_middle_click", self.onMiddleClick)
        self.__plugin.connect_to_signal("on_stop_module", self.onStop)
        self.__plugin.connect_to_signal("on_reload_module", self.onReload)
    
    def __initLoop(self):
        self.__programLoop = gobject.MainLoop()
    
    def name(self):
        return self.__name
        
    def run(self):
        """
        I'm connecting to the Cairo-Dock's program loop.
        To 'run' the plugin, call me when all initializations are done.
        """
        self.__messageDebug('run')
        self.__programLoop.run()

    def debug(self):
        """
        Call me one time in the beginning of your script. If you are running Cairo-Dock
        from a console window, you'll be able to see what I'm doing.
        """
        self.__debugMode = True
    
    def onClick(self, iState):
        """
        I react to left click on my icon.
        """
        self.__messageDebug('left clic %d' % iState)
        
    def onMiddleClick(self):
        """
        I react to middle click on my icon.
        """
        self.__messageDebug('middle clic')
        
    def onStop(self):
        """
        Time to quit.
        """
        self.__messageDebug('stop')
        self.__programLoop.quit()
    
    def onReload(self, bConfigHasChanged):
        """
        I just was reloaded. Check for a new configuration.
        """
        self.__messageDebug('reloaded')
        if bConfigHasChanged:
            self.__messageDebug('new configuration')

    def __messageDebug(self, message):
        """
        I write message to console if I have permission to do this.
        """
        if self.__debugMode:
            print '<%s : %s>' % (self.__name, message)

    def setLabel(self, label):
        """
        I update my icon's label.
        """
        self.__plugin.SetLabel(label)

    def setQuickInfo(self, info):
        """
        I update my icon's QuickInfo.
        """
        self.__plugin.SetQuickInfo(info)
        
    def setIcon(self, name):
        self.__plugin.SetIcon(name)


Configuration.py
# -*- coding: utf-8 -*-

# DiskFree, plugin for Cairo-Dock. View the available disk space.
# Copyright 2010 Xavier Nayrac
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from ConfigParser import RawConfigParser
from os.path import isfile
from os.path import expanduser
import sys

CAIRO_CONF_PATH = "~/.config/cairo-dock/current_theme/plug-ins"

class Configuration(RawConfigParser):
    """
    I manage the configuration's file of a Cairo-Dock plugin.
    """
    def __init__(self, nameOfPlugin):
        RawConfigParser.__init__(self)
        self.nameOfPlugin = ''
        self.__setFile(nameOfPlugin)

    def __setFile(self, nameOfPlugin):
        """
        I set the name of the configuration's file, with the help of plugin's name.
        Then I read the configuration.
        """
        nameOfPlugin = "%s/%s/%s.conf" % (CAIRO_CONF_PATH, nameOfPlugin, nameOfPlugin)
        nameOfPlugin = expanduser(nameOfPlugin)
        if isfile(nameOfPlugin):
            self.nameOfPlugin = nameOfPlugin
            self.refresh()
        else:
            print "%s n'existe pas ! Fin du programme." % (nameOfPlugin)
            sys.exit(1)
    
    def refresh(self):
        """
        I read the configuration's file.
        """
        self.readfp(open(self.nameOfPlugin))


CustomStatusMonitor
#!/usr/bin/python
# -*- coding: utf-8 -*-

# DiskFree, plugin for Cairo-Dock. View the available disk space.
# Copyright 2010 Xavier Nayrac
# Based on demo_bash, from Nochka85, modified by matttbe, based on
# demo.py by Fabounet.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from CustomStatusMonitorPlugin import CustomStatusMonitorPlugin

plugin = CustomStatusMonitorPlugin()
# Uncomment the following line to trace my actions in the console.
#plugin.debug()
plugin.run()


CustomStatusMonitor.conf
#1.0.2

#[gtk-about]
[Icon]
#F[Applet]
frame_maininfo=

#d Name of the dock it belongs to:
dock name =

#s Name of the icon as it will appear in its label in the dock :
name =

#F[Display]
frame_display=

#S+ Image's filename :
#{Let empty to use the default one.}
icon =

#j+[0;128] Desired icon size for this applet
#{Set to 0 to use the default applet size}
icon size = 0;0

order=

#F[Applet's Handbook]
frame_hand=
#A
handbook=DiskFree

#[gtk-convert]
[Desklet]

#X[Position]
frame_pos =

#b Lock position?
#{If locked, the desklet cannot be moved by simply dragging it with the left mouse button. It can still be moved with ALT + left-click.}
locked = false

#j+[24;512] Desklet dimensions (width x height):
#{Depending on your WindowManager, you may be able to resize this with ALT + middle-click or ALT + left-click.}
size = 96;96

#i[-2048;2048] Desklet position (x, y):
#{Depending on your WindowManager, you may be able to move this with ALT + left-click.. Negative values are counted from the right/bottom of the screen}
x position=0
#i[-2048;2048] ...
y position=0

#I[-180;180] Rotation:
#{You can quickly rotate the desklet with the mouse, by dragging the little buttons on its left and top sides.}
rotation = 0

#X[Visibility]
frame_visi =

#b Is detached from the dock
initially detached=false
#l[Normal;Keep above;Keep below;Keep on widget layer;Reserve space] Visibility:
#{for CompizFusion's "widget layer", set behaviour in Compiz to: (class=Cairo-dock & type=Utility)}
accessibility=0
#b Should be visible on all desktops?
sticky=true

#F[Decorations;gtk-orientation-portrait]
frame_deco=

#o+ Choose a decoration theme for this desklet:
#{Choose 'Custom decorations' to define your own decorations below.}
decorations = default

#v
sep_deco =

#S+ Background image:
#{Image to be displayed below drawings, e.g. a frame. Leave empty for no image.}
bg desklet =
#e+[0;1] Background transparency:
bg alpha = 1
#i+[0;256] Left offset:
#{in pixels. Use this to adjust the left position of drawings.}
left offset = 0
#i+[0;256] Top offset:
#{in pixels. Use this to adjust the top position of drawings.}
top offset = 0
#i+[0;256] Right offset:
#{in pixels. Use this to adjust the right position of drawings.}
right offset = 0
#i+[0;256] Bottom offset:
#{in pixels. Use this to adjust the bottom position of drawings.}
bottom offset = 0
#S+ Foreground image:
#{Image to be displayed above the drawings, e.g. a reflection. Leave empty for no image.}
fg desklet =
#e+[0;1] Foreground tansparency:
fg alpha = 1

#[gtk-preferences]
[Configuration]

#s The command to call to determine the current status:
statusCommand =

#f Time between two status calls in floating point seconds:
statusInterval = 300.0


CustomStatusMonitorPlugin.py
# -*- coding: utf-8 -*-

# DiskFree, plugin for Cairo-Dock. View the available disk space.
# Copyright 2010 Xavier Nayrac
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

from CairoDockPlugin import CairoDockPlugin
import os
import gobject
import random
import subprocess
import traceback
from xml.etree import ElementTree
from Configuration import Configuration

class CustomStatusMonitorPlugin(CairoDockPlugin):
    """
    I print the available file system's space on my label.
    You can choose to view the result in Go or Mo. You can set the ellapsed
    time between two checks.
    """
    def __init__(self):
        super(CustomStatusMonitorPlugin, self).__init__()
        self.__statusInterval = 300.0 # 5 minutes (in seconds)
        self.__statusCommand = ''
        self.__config = Configuration(self.name())
        self.__timerId = None
        self.__onClickCmd = None
                    
    def onTimer(self):
        try:
            self.updateState()
        except:
            traceback.print_exc()
        return True
        
    def updateState(self):
        self.debugOut('starting child...')
        prc = subprocess.Popen(self.__statusCommand, stdout=subprocess.PIPE)
        self.debugOut('reading result...')
        result = prc.stdout.read()
        exitCode = prc.wait()
        self.debugOut('waiting for process...')
        if exitCode != 0:
            raise Exception('script "%s" failed with exit code %i' % (self.__statusCommand, exitCode))
        self.debugOut('good exit')
        self.debugOut('parsing result (%i bytes)' % len(result))
        tree = ElementTree.fromstring(result)
        for child in tree:
            if child.tag == 'IconFile':
                self.setIcon(child.text if child.text else '')
            elif child.tag == 'IconText':
                self.setQuickInfo(child.text if child.text else '')
            elif child.tag == 'OnClick':
                self.__onClickCmd = child.text if child.text else ''
            elif child.tag == 'TooltipText':
                self.setLabel(child.text if child.text else '')
            else:
                raise Exception('unknown tag "%s" in answer "%s" from script "%s"' % (child.tag, result, self.__statusCommand))
        
        self.debugOut('finished')
        return
            
    def startUpdateMgr(self):
        subprocess.Popen(('update-manager'))
        
    def onClick(self, iState):
        """
        I set my label to the available space.
        """
        super(CustomStatusMonitorPlugin, self).onClick(iState)
        if self.__onClickCmd:
            subprocess.Popen(self.__onClickCmd)
        return True
        
    def onMiddleClick(self):
        super(CustomStatusMonitorPlugin, self).onMiddleClick()
        self.updateState()
        return True
    
    def onReload(self, bConfigHasChanged):
        """
        Je recharge la configuration si besoin.
        """
        super(CustomStatusMonitorPlugin, self).onReload(bConfigHasChanged)
        if bConfigHasChanged:
            self.__setConfiguration()
        
    def __setConfiguration(self):
        """
        I reload the configuration.
        """
        self.__config.refresh()
        self.__statusInterval = float(self.__config.get('Configuration', 'statusInterval'))
        self.__statusCommand = self.__config.get('Configuration', 'statusCommand')
        self.__setTimer()        
    
    def __setTimer(self):
        """
        I set the time between two checks.
        """
        self.__removeTimer()
        self.__timerId = gobject.timeout_add(int(self.__statusInterval * 1000), lambda x: self.onTimer(), 0)
        
    def __removeTimer(self):
        """
        I properly remove the timer.
        """
        if self.__timerId != None:
            gobject.source_remove(self.__timerId)

    def run(self):
        """
        Call me when you are ready 'to launch' the plugin's loop.
        """
        self.debugOut('incarnation started')
        self.__setConfiguration()
        self.debugOut('update state')
        self.updateState()
        self.debugOut('after update state')
        super(CustomStatusMonitorPlugin, self).run()
        self.__removeTimer()
        
    def debugOut(self, text):
        print os.getpid(), text
        

fabounet, Tuesday 20 December 2011 à 13:27


Subscription date : 30 November 2007
Messages : 17118
the multi-instance feature is enabled in the auto-load.conf file, I think you did it correctly (multi-instance = true)
are you able to multi-instanciate another python applet ?

is there any output in the terminal where you launch the dock ?

lonavera, Wednesday 21 December 2011 à 08:51


Subscription date : 09 December 2011
Messages : 2
Thank you for your anwser.

I am currently not at home, but here are some more details as far as I remember:
- I saw no error output on the console
- I made some test output in my code which was printed for each instance seperatly. This proves that the code was executed multiple times for multiple instances
- All actions that the code mades (text or icon changes) are seem to be executed to the same dock item. There is a space for each instance, but only one of them changes
- I think that there is some problem with the dbus handling. I have not written the communication module. It is the original module from the DiskFree applet

Thanks in advance for any help

fabounet, Wednesday 21 December 2011 à 13:52


Subscription date : 30 November 2007
Messages : 17118
oh ok, I'm sorry I missed this point ! indeed this applet is not well coded, actually there is a Python API that wraps the entire communication with the dock.
probably DiskFree is an old applet, it should be ported.

see the online doc, it also has an example, or just look at the demo applets in the cairo-dock-plug-ins package sources (in cairo-dock-plug-ins/Dbus/demos), or any other recent applets (Pidgin, screensaver, etc)

Guest, Wednesday 25 April 2012 à 16:03

I know this is an older thread but I was wondering if this is still an issue. What this looks like to me is multi-process not really multi-instance. The single plugin is trying to monitor the results of multiple threads running a script. I am doing something similar with a youtubedl plugin. If the original poster is still interested in something like this I have some information that will help. I have used the python multiprocess module to start a background process that sites waiting on a queue for urls and when one is dropped on my plugin icon the background process takes that url and downloads it reporting the status back through a queue. I just got the basics working last night so still have much refining to do. What I found in the process: You can't start a thread or background process from within your plugin file (i.e. inside the CustomStatusMonitorPlugin.py); it must be started in the top level file before the plugin is called (i.e. in the CustomStatusMonitor file before the plugin = CustomStatusMonitorPlugin()section); you must also create the queues there and pass them to both. In the case where you want to create a new thread to launch onClick command then you would need to create a background process that manages the create of the subprocess outside of your plugin and pass the click command on the queue.

Believe me I tried and tried to get the background thread/process (I tried both threading and multiprocess) to run inside the plugin but it does not work. I even took the stuff I had working last night and incorporated it into the plugin file (by this I mean the MyAppletPlugin.py file) this morning but it does not work. The background process works as long as something in the applet is active but stops when the applet is not active any more. This means that when the timer triggers the onReload the background task will run until the onReload section completes and if you have a gui pop up on something like a click or middle click then the background will run until you close the window. It seems that the applet (including the background process) is all boxed and put on a shelf until an event triggers the applet again. When you create the background process at the top level (i.e. in the MyApplet file before the plugin is instantiated) then the background process is truly a separate process.

As I said I just got the basic functionality working last night so I have no idea how I am going to incorporate configuration items at the top level (can the configuration be read at this level or maybe do as I said and create a manager process that creates subprocesses based on items passed on a configuration queue, not sure yet).

Guest, Wednesday 25 April 2012 à 16:04

sorry for the spelling mistakes "start a background process that sites waiting on a queue" should read "sits waiting"

fabounet, Friday 27 April 2012 à 17:04


Subscription date : 30 November 2007
Messages : 17118
Hi,
for threads in a python applet, I think you can take a look at the Twitter applet made by Eduardo

also, I think that the previous poster wanted to have several icons (one for each HD I suppose), which is different than having several threads in a single applet.

about the configuration, it is read during get_config() ans nowhere else

Eduardo Mucelli, Thursday 03 May 2012 à 14:12


Subscription date : 05 August 2009
Messages : 285
Guest, are you using the latest version of the CDApplet interface? If not, probably you are not going to accomplish multi-threading on your Python applet. If so, please, check out on the Twitter applet, or directly here. Refer to the class:

class TwitterStreamAPI(API)


You just need to:

import threading
thread = threading.Thread(target=self.method_you_want_run_in_this_thread)
thread.start()


You do not even need to implement a run() method, only implement your target method and you are good to go. Cheers.

Technical discussions | Discussions techniques

Subjects Author Language Messages Last message
[Locked] Cannot get a new applet to work with multiple instances
lonavera English 7 Eduardo Mucelli [Read]
03 May 2012 à 14:12


Glx-Dock / Cairo-Dock List of forums Technical discussions | Discussions techniques Cannot get a new applet to work with multiple instances Top

Online users :

Powered by ElementSpeak © 2007 Adrien Pilleboue, 2009-2013 Matthieu Baerts.
Dock based on CSS Dock Menu (Ndesign) with jQuery. Icons by zgegball
Cairo-Dock is a free software under GNU-GPL3 licence. First stable version created by Fabounet.
Many thanks to TuxFamily for the web Hosting and Mav for the domain name.