pyxfce: Python Anbindung an xfce

Du hast eine Idee für ein Projekt?
Antworten
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

Donnerstag 28. Mai 2009, 21:11

Ist zwar nicht ganz das richtige Forum, aber ein geeigneteres habe ich nicht gefunden.

Hallo, bin vor ein paar Wochen über xfce und der Python Anbindung gestolpert. Hat mich gleich interessiert. Leider ist das Projekt anscheinend eingefroren. http://pyxfce.xfce.org/. Es gibt leider kaum samples (ausser dem kurzen iwindowlist.py , testplugin.py und testtrayarea.py habe ich gefunden) und auch keine aktiven Users, mir scheint; d.h. keine Mailinglist, ...) und ich würde gerne ein kleines plugin schreiben.

Hat jmd. damit von euch Erfahrung oder selbst etwas gemacht?
Ich habe schon viel gesucht jedoch nichts gefunden. Es gibt unter windows ein tray icon, das mir alle Symbole auf dem desktop auflistet und dann direkt starten kann. Das hätte ich auch gerne in xfce, idealerweise in Pyhton programmiert. Wäre ein kleines, interessantes Projekt für mich (und vielleicht auch jmd. anderem)

Dann hätte ich nämlich schnell zugriff, ohne zuerst den desktop einzublenden (was mit dem "show desktop" geht und dann das icon zu suchen und doppelklicken). Eine Art Schnelllauncher praktisch. Die Taskliste unten möchte ich nicht übermäßig befüllen, darum wäre diese Lösung schön.
Benutzeravatar
snafu
User
Beiträge: 5926
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Freitag 29. Mai 2009, 05:29

Reicht es nicht, das Verzeichnis auszulesen, wo XFCE die Symbole ablegt? Ich verstehe nicht so ganz, wie dieses Modul dir dabei helfen soll. Jedenfalls fällt so etwas nicht unter die Aufgaben eines Windowmanagers. Und um eine Schnittstelle zu einem solchen (genauer: XFCE) geht es bei dem Binding ja offenbar.
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

Freitag 29. Mai 2009, 06:34

snafu hat geschrieben:Reicht es nicht, das Verzeichnis auszulesen, wo XFCE die Symbole ablegt? Ich verstehe nicht so ganz, wie dieses Modul dir dabei helfen soll. Jedenfalls fällt so etwas nicht unter die Aufgaben eines Windowmanagers. Und um eine Schnittstelle zu einem solchen (genauer: XFCE) geht es bei dem Binding ja offenbar.
Ja, das hätte ich schon geschafft (mit eigenem Programm). Nur die Anbindung an xfce (als panel plugin als tray icon), da weiß ich noch gar nicht, wie und wo.
Benutzeravatar
snafu
User
Beiträge: 5926
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Freitag 29. Mai 2009, 06:52

Die entsprechende Seite sagt:
Panel plugins - It has become quite popular during the Xfce 4.0 days to enhance the functionality of the Xfce panel by writing new plugins. The Xfce Goodies project provides several additional plugins already, thanks to the work of our contributors.
If you plan to write your plugin for the Xfce, there are a few things to take care of when you start. At best, you should have a look at the xfce4-panel source, it contains a sample plugin that demonstrates the basics of panel plugin writing. Furthermore, you should have a look at the source code of the existing panel plugins, to see some real-life examples of panel plugins. When you are done with learning, you should grab the versions of the GNU autotools available here, as those are the official ones used by the Xfce developers, and have proven to work fine. If you have questions about the panel plugin development or if you experience problems, please ask on the Xfce Goodies Development mailinglist.
Anscheinend unterstützt das Binding diese Funktionalität also nicht. Das macht die Sache wohl nicht ganz einfach. ;)
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Freitag 29. Mai 2009, 08:17

PyGTK/gnome-python bieten doch Trayicons und ich meine dass die Trayicons auf allen DEs funktionieren (also zumindest auf allen GTK+-basierten, aber ich meine dass da freedesktop.org auch GNOME und KDE zusammengebracht hat).
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

Freitag 29. Mai 2009, 10:09

Leonidas hat geschrieben:PyGTK/gnome-python bieten doch Trayicons und ich meine dass die Trayicons auf allen DEs funktionieren (also zumindest auf allen GTK+-basierten, aber ich meine dass da freedesktop.org auch GNOME und KDE zusammengebracht hat).
@snafu: Leider nicht. Interessieren würde das mich schon, das python-binding selbst zu erweitern. Aber die Zeit dazu habe ich nicht. Zusatz: ja, in der goodies mailing könnte ichs nochmals probieren.

@Leonidas: Ja, danke, das wäre auch ein Ansatz.
Benutzeravatar
C4S3
User
Beiträge: 292
Registriert: Donnerstag 21. September 2006, 10:07
Wohnort: Oberösterreich

Freitag 29. Mai 2009, 10:13

Für XFCE gibt es ein Tray-Plugin, welches explizit dazu gedacht, Gnome-Trayicons aufzunehmen. Ich benutze das immer, weil die Energieverwaltung/Batterieanzeige von Gnome um Längen besser ist, als die von XFCE.
Du könntest also ein Gnome-Trayicon schreiben und es dann trotzdem unter XFCE nutzen!

XfApplet
The XfApplet Plugin is a plugin for the Xfce 4 Panel which allows one to use applets designed for the Gnome Panel inside the Xfce Panel. You can think of XfApplet as a tiny Gnome Panel that lives inside the Xfce Panel and allows you to show the same applets that the Gnome Panel is capable of showing.
Gruß!
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

Freitag 29. Mai 2009, 10:37

C4S3 hat geschrieben:Für XFCE gibt es ein Tray-Plugin, welches explizit dazu gedacht, Gnome-Trayicons aufzunehmen. Ich benutze das immer, weil die Energieverwaltung/Batterieanzeige von Gnome um Längen besser ist, als die von XFCE.
Du könntest also ein Gnome-Trayicon schreiben und es dann trotzdem unter XFCE nutzen!

XfApplet
The XfApplet Plugin is a plugin for the Xfce 4 Panel which allows one to use applets designed for the Gnome Panel inside the Xfce Panel. You can think of XfApplet as a tiny Gnome Panel that lives inside the Xfce Panel and allows you to show the same applets that the Gnome Panel is capable of showing.
Hm, danke. Vielleicht gibt es ja für meinen Fall schon das passende.
Y0Gi
User
Beiträge: 1454
Registriert: Freitag 22. September 2006, 23:05
Wohnort: ja

Freitag 29. Mai 2009, 10:42

Ich denke, die Trayarea ist von freedesktop.org, XDG, vielleicht EWMH oder sonstwas/-wem einigermaßen spezifiziert. So funktionieren bspw. die Trayicons von Gaim, Sonata und GMPC auch in awesome und stalonetray. Allerdings sind das auch alles GTK-Apps. Da würde ich mal näher forschen, ob's da auch was unabhängiges gibt.
DasIch
User
Beiträge: 2519
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Freitag 29. Mai 2009, 15:49

Trayicons und einfache Benachrichtigungen sind über freedesktop.org "standardisiert". Problematisch ist es nur wenn man Visual Notifications nutzen will, also die Notifications die Ubuntu(Gnome) seit Jaunty hat und KDE seit 4.0, dass lässt sich zwar ohne Probleme über DBus machen aber die Interfaces sind leicht unterschiedlich.
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

Montag 1. Juni 2009, 08:50

Bitte bzgl. des Codes nicht zu streng mit mir zu sein, das letzte mal das ich wirklich etwas ernsthafter mit Python was tat, ist mindestens ein Jahr aus. ;)



Es war nicht leicht für mich. in pygtk hatte ich praktisch Null Kenntnisse. Habe mir von anderen Source viel genommen. Auch die pyxfce Doku ist sehr dürftig. Bin schon froh, dass ich überhaupt das soweit (nacht fast etwa zwei Tagen(!)) hinbekommen habe. :)

(Ich hätte noch gerne einen kleinen Screenshot (nicht größer als notwendig) hinaufgeladen (wenn einverstanden in diesem Forum), weiss aber nicht wie es geht. Kann man das direkt da hereinstellen oder muss man auf eine externe Seite verlinken? Wenn extern: Wo geht das schnell und unkompliziert?) irgendwann habe ich einmal imagehack oder so ähnlich verwendet. Gibts das noch?

ein desktop file erstellen
zB.

/usr/share/xfce4/panel-plugins/desktoplist.desktop

also in meinem Fall sieht das so aus:

Code: Alles auswählen

[Xfce Panel]
Type=Panel
Encoding=UTF-8
Name=Desktop List
Comment=Quick access to desktop entries
Icon=nm-device-wireless

X-XFCE-Exec=/home/franz/drpython/drpref/drpython_franz/saved/xfce/pyxfce/desktoplist.py

Code: Alles auswählen

#!/usr/bin/python
#-*- coding: iso-8859-1 -*-
# GPL licence

#Franz Steinhauesler, June 2009

import pygtk
pygtk.require("2.0")
import gtk
import os
              
from xfce4.panel import *
from xfce4.gui import *
from xfce4.netk import *

class MyPlugin(Plugin):
    def __init__(self):
        Plugin.__init__(self)
        
        self.screen = xfce4.netk.screen_get_default()
        xfce4.netk.Screen.force_update (self.screen) #dont know, what it is; is this necessary?

        #for left button
        self.pluginbutton = gtk.Button(">")
        self.pluginbutton.show()
        self.add(self.pluginbutton)
        
        self.pluginbutton.connect("clicked", self.desktopbutton_clicked)

        #for right button => context menu
        self.add_action_widget(self.pluginbutton)
        
        self.menu_show_about()
        #self.menu_show_configure() #no configuration settings yet
        
        self.connect("about", self.about)

    def about(self, *args):
        a_dlg = xfce4.gui.AboutDialog()
        
        a_dlg.add_credit("Franz Steinhaeusler", "secret@secret.at", "List desktop plugin")
        a_dlg.add_credit("python forum", "http://www.python-forum.de", "tips and support from the german pyton forum")
        a_dlg.add_credit("pyxfce crew", "http://pyxfce.xfce.org", "for developing that python bindings for xfce")
        a_dlg.add_credit("xfce crew", "www.xfce.org", "xfce developer")
        a_dlg.set_copyright("Franz Steinhaeusler, June 2009")
        a_dlg.set_description("Quick list and start entries from desktop.")
        a_dlg.set_version("0.1")
        a_dlg.set_program("Desktop List")
        
        ret = a_dlg.run()
        a_dlg.destroy()

    def desktopbutton_clicked(self, *args):
        #create popup menu
        #todo (1) use file lsit 2) cache the list if need (performance)
        #todo extract and show icons
        self.menu = gtk.Menu ()
        
        item = gtk.SeparatorMenuItem()
        #gtk.MenuShell.append(self.menu, item)
        self.menu.append(item)
        
        #todo loop
        global homedir
        #self.desktopdir = os.path.expanduser ('~') + '/Desktop/'
        self.desktopdir = homedir + '/Desktop/'

        desktopfiles = os.listdir(self.desktopdir)
        menulist = list()
        
        for desktopfile in desktopfiles:
          if desktopfile.endswith(".desktop"):

            filehandle = open(self.desktopdir + desktopfile)
            lines = filehandle.readlines()
            for line in lines:
                if line.startswith("Name="):
                    name = line.split('=')[1].strip()
                if line.startswith("Exec="):
                    launch = line.split('=')[1].strip()
                if line.startswith("Icon="):
                    icon = line.split('=')[1].strip()
            menulist.append([name, launch, icon])
            filehandle.close()

        #menulist.sort(lambda x,y: cmp(x[0].lower(), y[0].lower()))
        menulist.sort(self.CompareLowerCase)

        for entry in menulist:
          item = gtk.ImageMenuItem(entry[0])
          item.set_data("launch", entry[1])
          item_image = gtk.Image ()
          item_image.set_from_icon_name (entry[2], gtk.ICON_SIZE_MENU)
          item.set_image (item_image)
          item.show()
          item.connect("activate", self.startup)
          self.menu.append(item)
            
        self.menu.popup(None, None, None, 0, gtk.get_current_event_time())

    def CompareLowerCase(self, n1, n2):
        if n1[0].lower() == n2[0].lower():
            return 0
        elif n1[0].lower() < n2[0].lower():
            return -1
        return 1


    def startup(self, args):
        xfce4.gui.spawn_command_line(args.get_data("launch"))
        
    #debugging
    def MsgDlg(self, secondmsg):
        d = gtk.MessageDialog(None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK,'msg')
        d.format_secondary_text(secondmsg)
        ret = d.run()
        d.destroy()
 
    
import sys

homedir = os.path.expanduser('~')
sys.stdout = open(homedir + "/stdout.txt", 'w')
sys.stderr = open(homedir + "/stderr.txt", 'w')

# argc, argv, &info
plugin = MyPlugin()
if plugin == None:
    # TODO exception
    pass


plugin.show()

gtk.main()
sys.exit(0)
BlackJack

Montag 1. Juni 2009, 09:33

@Francesco: `MyPlugin` ist so nichtssagend, gibt's da nichts Beschreibenderes?

``global`` ist böse, und wird für `homedir` auch gar nicht gebraucht. `homedir` würde ich komplett in Grossbuchstaben schreiben, damit man sieht, dass es eine Konstante ist. An Namenskonventionen hältst Du Dich allgemein nicht. Alles ausser Klassennamen und Konstanten sollte eigentlich in kleinbuchstaben_mit_unterstrichen geschrieben werden. Auf gar keinen Fall sollte etwas ausser Klassennamen und Konstanten mit einem Grossbuchstaben beginnen.

Pfade sollte man mit `os.path.join()` zusammen setzen und nicht mit ``+``.

Importe sollten am Anfang eines Moduls stehen, damit man auf den ersten Blick die Abhängigkeiten sieht.

`plugin` kann nicht `None` sein, der Test ist also nicht nur überflüssig weil da ein ``pass`` steht.

Das ``sys.exit(0)`` am Ende ist überflüssig. An der Stelle beendet sich das Programm sowieso und 0 ist der Standardrückgabecode.

Wenn eine "Methode" `self` nicht verwendet, dann ist es semantisch gesehen keine Methode und man sollte sich gut überlegen warum man eine Funktion in eine Klasse gesteckt hat. Falls es dafür einen Grund gibt, sollte man eine `staticmethod()` daraus machen.

`CompareLowerCase()` ist zu kompliziert, das lässt sich mit der `cmp()`-Funktion in einer Zeile ausdrücken:

Code: Alles auswählen

   @staticmethod
    def _compare_lower_case(n1, n2):
        return cmp(n1[0].lower(), n2[0].lower())
Allerdings wäre eine Funktion für das `key`-Argument von `list.sort()` noch einfacher und effizienter:

Code: Alles auswählen

        menulist.sort(key=lambda entry: entry[0].lower())
Das Einlesen der Desktop-Dateien ist nicht robust. Wenn in der ersten Datei eines der drei Felder nicht vorkommen sollte, gibt es einen `NameError`. Falls die erste Datei komplett ist, aber eine der folgenden nicht, dann werden die jeweiligen Daten der letzten Datei übernommen. Ausserdem gibt es Probleme, wenn im Feldwert Gleicheitszeichen vorkommen, was bei 'Exec=' in der Praxis durchaus vorkommt, wenn Argumente angegeben werden!
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

Montag 1. Juni 2009, 09:44

BlackJack hat geschrieben:@Francesco: `MyPlugin` ist so nichtssagend, gibt's da nichts Beschreibenderes?
[...]
Danke, das war ein quick and dirty project und geshcaut, dass es schnell zum laufen bekommen hätte. Ich hätte den code sonst gar nicht reingestellt, aber ich dachte, wenn jmd. vor so einem problem steht (pyxfce und nicht wissen, wo anfangen; wie es mir auch ergangen ist), als Starthilfe.

Irgendwie bin ich auch stolz, das erste pyxfce Plugin auf der ganzen Welt geschrieben zu haben (zumindest fand ich in google nichts). Ich hoffe, es klingt nicht zu eingebildet. Wenn jmd. doch noch eins irgendwo findet, bin ich auch nicht traurig und nehme es gerne in meine Sammlung auf. ;)

Trotzdem danke, ich werde mir das Progrämmchen mal nochamls in Ruhe durchsehen und auf deine Korrekturvorschläge eingehen.

Ich habe noch einige andere Ideen, jedoch möchte ich nicht zuviel Zeit darauf verwenden (cachen, manuell sortieren, favoriten mit counter für zugriffe und danach sortieren, einstellungen dialog für die default sortierung, ...)

mfg,

Nachtrag: wenn ichs korrigiere, soll ich es nochmals reinstellen? Wenn ja: mein letztes Posting bearbeiten (da es dann weniger Platz beansprucht) oder den Code extern wohin oder macht es nichts, den code erneut zu posten?

Noch etwas: Würde mich interessieren, ob google (hier im Python Forum) dann etwas zu pyxfce plugin (oder panel plugin) etwas findet, damit die nächsten User nicht von praktisch null wegstarten müssen (ich hätte mir das nämlich auch gewünscht)...

Hier noch schnell ein screenshot:
Bild
Francesco
User
Beiträge: 824
Registriert: Mittwoch 1. Dezember 2004, 12:35
Wohnort: Upper Austria

Montag 1. Juni 2009, 14:55

so jetzt habe ich deine Hinweise einmal eingebaut:
Drei Probleme gibt es noch:
1) ohne global kann ich nicht auf homedir zugreifen (habs einmal entfernt)
2) die redirect umleitung von stdout und stderror geht manchmal, meist nicht. Ich frage mich warum? Ich habs aus dem code entfernt und zum debuggen den msg_dlg verwendet.
3) Der button passt mir nicht. ich möchte einen "flache" wie in den anderen panel plugins mit einem Icon. Hier weiß ich noch nicht, wie ich das hinbekomme. Wie zb beim Clipman plugin

Hier nun der überarbeitete Code:

Code: Alles auswählen

#!/usr/bin/python
#GPL licence

#Destkop List
#xcfe Panel Plugin written in pyxfce
#Quick access to Desktop Entries
#creates a pop up menu for the desktop entries and offer to start them

#Franz Steinhaeusler, June 2009
#Version 0.1, 06/01/2009
#thanks to BlackJack from the german python forum for the code review

#import pygtk stuff
import pygtk
pygtk.require("2.0")
import gtk

#import basic modules
import sys
import os

#import xfce stuff
from xfce4.panel import *
from xfce4.gui import *
from xfce4.netk import *

class DesktopListPlugin(Plugin):
    """Plugin Class for Desktop List Plugin."""

    def __init__(self):
        """Init the plugin class."""
    
        Plugin.__init__(self)
        
        #create and show desktop list panel icon
        pluginbutton = gtk.Button(">")
        pluginbutton.show()
        self.add(pluginbutton)
        
        #connect the button to list the desktop items
        pluginbutton.connect("clicked", self.desktop_button_clicked)

        #register button for right button context menu
        self.add_action_widget(pluginbutton)
        
        #create about menu entry
        self.menu_show_about()
        self.connect("about", self.show_about_dialog)

    def show_about_dialog(self, arg):
        """create and show the xfce About Dialog."""

        dlg = xfce4.gui.AboutDialog()
        
        #set the About Dialog members
        dlg.add_credit("Franz Steinhaeusler", "secret@secret.at", "Desktop List plugin")
        dlg.add_credit("python forum", "http://www.python-forum.de", "tips and support from the german pyton forum (in special BlackJack)")
        dlg.add_credit("pyxfce crew", "http://pyxfce.xfce.org", "for developing that python bindings for xfce")
        dlg.add_credit("xfce crew", "www.xfce.org", "xfce developer")
        dlg.set_copyright("Franz Steinhaeusler, June 2009")
        dlg.set_description("Quick list and start entries from desktop.")
        dlg.set_version("0.1")
        dlg.set_program("Desktop List")
        
        #show About Dialog
        ret = dlg.run()
        dlg.destroy()

    def desktop_button_clicked(self, arg):
        """create and show desktop list popup menu."""

        #create the menu
        menu = gtk.Menu()
        
        #set the desktop files directory
        desktopdir = os.path.join(os.path.expanduser('~'), 'Desktop')

        #get the desktop files
        desktopfiles = os.listdir(desktopdir)
        
        #temporary list for the menu items
        menulist = list()
        
        #get the desktop entries
        for desktopfile in desktopfiles:
          if desktopfile.endswith(".desktop"):

            filehandle = open(os.path.join(desktopdir, desktopfile))
            name = launch = icon = str()
            lines = filehandle.readlines()
            
            for line in lines:
                #name of the destop entry
                if line.startswith("Name="):
                    name = line.split('=')[1].strip()
                #command for that destop entry
                if line.startswith("Exec="):
                    #split only first '=', because there could be arguments, which contain '='
                    launch = line.split('=', 1)[1].strip()
                #icon for that destop entry
                if line.startswith("Icon="):
                    icon = line.split('=')[1].strip()
            
            #add that desktop entry to the menu list
            menulist.append([name, launch, icon])
            filehandle.close()
        
        #sort menu list entries alphabetically case insensitive
        menulist.sort(key=lambda entry: entry[0].lower())

        #set the popup menu
        for entry in menulist:
          #append single menu entry to menu
          menuitem = gtk.ImageMenuItem(entry[0])
          menuitem.set_data("launch", entry[1])
          menuitemimage = gtk.Image()
          menuitemimage.set_from_icon_name(entry[2], gtk.ICON_SIZE_MENU)
          menuitem.set_image(menuitemimage)
          menuitem.show()
          menuitem.connect("activate", self.launch_program)
          menu.append(menuitem)
        
        #pop up the popup menu
        menu.popup(None, None, None, 0, gtk.get_current_event_time())

    def launch_program(self, menuitem):
        """get the command line and starts the program."""
        xfce4.gui.spawn_command_line(menuitem.get_data("launch"), use_startup_notification=False)
        
    #debugging
    def msg_dlg(self, msg):
        """Message Dialog."""
        d = gtk.MessageDialog(None, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_WARNING, gtk.BUTTONS_OK,'Desktop List')
        d.format_secondary_text(msg)
        d.run()
        d.destroy()
    #end debugging
 
#create the desktop list plugin
desktoplistplugin = DesktopListPlugin()
desktoplistplugin.show()

gtk.main()
Antworten