ubuntuusers.de

ubuntuusers.deWikiOpenboxPipemenü

Pipemenü

Dieser Artikel wurde für die folgenden Ubuntu-Versionen getestet:

Zum Verständnis dieses Artikels sind folgende Seiten hilfreich:

./kontextmenue.png Eine Spezialfunktion des Fenstermanagers Openbox ist das sogenannte "pipe menu". Dahinter steckt ein Mechanismus, der ein externes Programm oder Skript aufruft und die Ausgabe/das Ergebnis des Programms/Skripts wieder an Openbox übergibt. Damit lassen sich dynamische (Unter-)Menüs erzeugen, da bei jedem Aufruf des Menüpunkts das Skript erneut abgearbeitet wird.

Diese Funktion wird erst vor dem Hintergrund verständlich, dass Openbox vom Konzept her ursprünglich auf das heute übliche Programm-Menü im Panel verzichtete und stattdessen das Kontextmenü rechte Maustaste zur Auswahl und zum Start von Programmen benutzte. Das Kontextmenü lässt sich mit einem Pipemenü um eigenen Funktionen erweitern und dem persönlichen Bedarf anpassen.

Nach ein wenig Grundwissen einige praktische Beispiele, was sich damit realisieren lässt. Weitere Ideen können gerne ergänzt werden.

Voraussetzungen

Unter Lubuntu muss man das Kontextmenü von Openbox erst aktivieren: "Beim Klicken auf den Desktop die Menüs des Fenstermanagers anzeigen". Dazu klickt man den leeren Desktop mit der rechten Maustaste rechte Maustaste an und wählt den Punkt "Einstellungen -> Fortgeschritten" aus (siehe auch LXDE Einstellungen).

Skripte

Neue Funktionen werden meist - aber nicht zwangsläufig - als Shell- oder Python-Skripte realisiert. Diese müssen irgendwo gespeichert werden. Zwar ist der Ort prinzipiell egal, aus Gründen der Übersichtlichkeit wird

  • ~/bin/ - im Homeverzeichnis des angemeldeten Benutzers (den Ordner ggfs. anlegen)

oder

  • /usr/local/bin/ - für eine systemweite Nutzung

empfohlen. Darüber hinaus müssen Shell-Skripte ausführbar [1] sein (bei Python-Skripten bisher nicht notwendig). Entweder benutzt man dazu einen Dateimanager oder den Befehl chmod in einem Terminalfenster [2]:

chmod 755 SKRIPTNAME.sh 

Einbindung in Openbox

Dazu editiert [3] man die Datei ~/.config/openbox/menu.xml. Ein Editor mit Syntax-Hervorhebung für XML, der die Übersicht erleichtert (z.B. Geany oder Medit), ist empfehlenswert.

Hinweis:

Unter Lubuntu muss zuerst:

  1. die Datei /usr/share/lubuntu/openbox/menu.xml nach ~/.config/openbox/menu.xml kopiert werden und

  2. die Verwendung von ~/.config/openbox/menu.xml in ~/.config/openbox/lubuntu-rc.xml aktiviert werden

Einträge in dieser Datei legen auch die Reihenfolge innerhalb des Kontextmenüs fest – je nachdem, ob etwas am Anfang oder am Ende steht, erscheint es am Anfang oder Ende des Kontextmenüs. Um das Einfügen zu erleichtern, hier ein Ausschnitt aus dieser Datei:

...
<item label="Firefox">
	<action name="Execute">
		<execute>firefox</execute>
	</action>
</item>
<separator/>
# ein neuer Pipemenü-Eintrag: 
<menu id="..." label="..." execute="..." />
#
<separator/>
...

Syntax für neue Pipemenü-Einträge
Parameter Erklärung
id="..." beliebig, muss aber eindeutig und nicht leer sein
label="..." Bezeichnung des Menü-Eintrags
execute="..." Pfad und Name des Skripts

Konkretere Hinweise finden sich in den einzelnen Beispielen.

Um eine neue Funktion verfügbar zu machen, muss Openbox neu gestartet werden:

openbox --reconfigure 

Alternativ kann man sich auch ab- und wieder neu anmelden.

Beispiele

Neue Datei aus Vorlage

Die Dateimanager Nautilus, Thunar und PCManFM bieten eine Funktion, um im gerade geöffneten Ordner eine neue Datei anzulegen. Diese Möglichkeit erreicht man über das Menü mit "Datei -> Dokument anlegen" oder "Rechtsklick -> Dokument anlegen bzw. aus Vorlage erstellen". Standardmäßig wird hier meist nur der Eintrag "Leere Datei" angeboten, mit dem man ein leeres Textdokument erstellen kann.

Um weitere Vorlagen zur Verfügung zu haben, kann man beliebige (leere) Vorlage-Dateien in den Ordner ~/Vorlagen/ im eigenen Homeverzeichnis abspeichern. Um z.B. eine Vorlage für OpenOffice Writer zu erstellen, speichert man einfach ein leeres Dokument als ~/Vorlagen/OpenOffice Dokument.odt. Eine kleine Auswahl an Vorlagen kann man hier {dl} herunterladen und in den Vorlagenordner entpacken [4].

Das folgende Skript geht noch einen Schritt weiter. Nachdem man eine passende Vorlage ausgewählt hat, startet sofort das dafür zuständige Standardprogramm (dieses muss unter Umständen noch festgelegt werden, am einfachsten mit einem Dateimanager und der Funktion rechte Maustaste und "Öffnen mit..."). Außerdem wird das Paket

  • libgnome2-0

Wiki/Vorlagen/Installbutton/button.png

benötigt.

Skript

Vorlage für das Skript templates.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash
#bad medicine to open templates, just like gnome!

function generate_template_menu {

ls ~/Vorlagen | while read; do

    echo '<item label="'"${REPLY}"'">'
    echo -n '<action name="Execute"><execute>'
    echo -n "gnome-open ~/Vorlagen/${REPLY}"
    echo '</execute></action>'
    echo '</item>'
  done
}

echo '<openbox_pipe_menu>'

generate_template_menu

echo '</openbox_pipe_menu>'

Quelle: David Barr - Openbox Pipemenu Scripts {en}

Über folgende Eintrag werden die Vorlagen in das Kontextmenü eingebunden:

<menu id="TemplatesPipeMenu" label="Vorlagen" execute="~/bin/templates.sh"/>

Wetterbericht

Skripte

Hier wird das aktuelle Wetter über Google mittels des Python-Skripts gweather.py geholt:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/python
# -*- coding: utf-8 -*-

from urllib import urlopen, quote
from xml.etree.cElementTree import parse
from datetime import datetime, timedelta
import os
from os.path import join
from sys import argv
try:
    import cPickle as pickle
except ImportError:
    import pickle


TRANSLATED_TEXT = {
    'en': {
        'current': 'Current conditions',
        'weather': 'Weather',
        'temp': 'Temperature',
        'forecast': 'Forecast',
        'mintemp': 'Minimun Temperature',
        'maxtemp': 'Maximun Temperature'
    },
    'de': {
        'current': u'Aktuell',
        'weather': u'Wetter',
        'temp': u'Temperatur',
        'forecast': u'Prognose',
        'mintemp': u'Minimale Temperatur',
        'maxtemp': u'Höchste Temperatur'
    },
    'sv': {
        'current': u'Aktuell prognos',
        'weather': u'Väder',
        'temp': u'Temperatur',
        'forecast': u'Prognos',
        'mintemp': u'Lägsta temperatur',
        'maxtemp': u'Högsta temperatur'
    },
    'es': {
        'current': u'Actualmente',
        'weather': u'Tiempo',
        'temp': u'Temperatura',
        'forecast': u'Previsión',
        'mintemp': u'Temperatura Mínima',
        'maxtemp': u'Temperatura Máxima'
    },
    'fr': {
        'current': u'Actuel',
        'weather': u'Météo',
        'temp': u'Température',
        'forecast': u'Prévision',
        'mintemp': u'Température minimale',
        'maxtemp': u'Température maximale'
    }
}


if len(argv) != 3:
    raise Exception('Usage: gweather.py city language')
else:
    city = argv[1]
    lang = argv[2]



CACHE_HOURS = 1

WEATHER_URL = 'http://www.google.com/ig/api?weather=%s&hl=%s&oe=UTF-8'


def get_weather(city, lang):
    url = WEATHER_URL % (quote(city), quote(lang))
    data = parse(urlopen(url))
    
    forecasts = []
    for forecast in data.findall('weather/forecast_conditions'):
        forecasts.append(
    dict([(element.tag, element.get("data")) for element in forecast.getchildren()]))
    
    return {
        'forecast_information': dict([(element.tag, element.get("data")) for element in data.find('weather/forecast_information').getchildren()]),
        'current_conditions': dict([(element.tag, element.get("data")) for element in data.find('weather/current_conditions').getchildren()]),
        'forecasts': forecasts
    }

def get_openbox_pipe_menu(lang, forecast_information, current_conditions, forecasts):
    if lang == 'en-US':
        lang = 'en'
    
    tt = TRANSLATED_TEXT[lang]
    
    temp_var, temp_unit = ("temp_c", u"\u00b0C") if forecast_information['unit_system'] == "SI" else ("temp_f", "F")
    
    output = '<openbox_pipe_menu>'
    output += '\n<separator label="%s (%s)" />' % (weather['forecast_information']['city'],forecast_information['forecast_date'])
    output += '\n<separator label="%s" />' % tt['current']
    output += '<item label="%s: %s" />' % (tt['weather'], current_conditions['condition'])
    output += '<item label="%s: %s %s" />' % (tt['temp'], current_conditions[temp_var], temp_unit)
    output += '<item label="%s" />' % (current_conditions['humidity'])
    output += '<item label="%s" />' % (current_conditions['wind_condition'])
    for forecast in forecasts:
        output += '\n<separator label="%s: %s" />' % (tt['forecast'], forecast['day_of_week'])
        output += '<item label="%s: %s" />' % (tt['weather'], forecast['condition'])
        output += '<item label="%s: %s %s" />' % ( tt['mintemp'], forecast['low'], temp_unit )
        output += '<item label="%s: %s %s" />' % ( tt['maxtemp'], forecast['high'], temp_unit )
    output += '\n</openbox_pipe_menu>'
    
    return output.encode('utf-8')

cache_file = join(os.getenv("HOME"), '.gweather.cache')

try:
    f = open(cache_file,'rb')
    cache = pickle.load(f)
    f.close()
except IOError:
    cache = None

if cache == None or (city, lang) not in cache or (
        cache[(city, lang)]['date'] + timedelta(hours=CACHE_HOURS) < datetime.utcnow()):
    # The cache is outdated
    weather = get_weather(city, lang)
    ob_pipe_menu = get_openbox_pipe_menu(lang, **weather)
    print ob_pipe_menu
    if cache == None:
        cache = dict()
    cache[(city, lang)] = {'date': datetime.utcnow(), 'ob_pipe_menu': ob_pipe_menu}
    
    #Save the data in the cache
    try:
        f = open(cache_file, 'wb')
        cache = pickle.dump(cache, f, -1)
        f.close()
    except IOError:
        raise
else:
    print cache[(city, lang)]['ob_pipe_menu']

Quelle: Archlinux-Forum {en}

Je nach Geschmack kann man statt Google auch Yahoo nutzen. Der Quellcode für das Skript yweather.py:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#!/usr/bin/python
# -*- coding: utf-8 -*-

import urllib
from xml.etree.cElementTree import parse
from datetime import datetime, timedelta
import os
from os.path import join
from sys import argv
try:
    import cPickle as pickle
except ImportError:
    import pickle

#Usage: yweather.py AYXX0001 Celsius

if len(argv) != 3:
    raise Exception('Usage: yweather.py zip_code units. zip_code is your city code in Yahoo Weather, units can be Celsius or Fahrenheit.')
else:
    zip_code = argv[1]
    if argv[2] == 'Fahrenheit' or argv[2] == 'fahrenheit':
        units = 'f'
    else:
        units = 'c'



CACHE_HOURS = 1

#http://weather.yahooapis.com/forecastrss
WEATHER_URL = 'http://xml.weather.yahoo.com/forecastrss?p=%s&u=%s'
WEATHER_NS = 'http://xml.weather.yahoo.com/ns/rss/1.0'

def weather_for_zip(zip_code, units):
    url = WEATHER_URL % (zip_code, units)
    rss = parse(urllib.urlopen(url)).getroot()
    forecasts = []
    for element in rss.findall('channel/item/{%s}forecast' % WEATHER_NS):
        forecasts.append(dict(element.items()))
    ycondition = rss.find('channel/item/{%s}condition' % WEATHER_NS)
    return {
        'current_condition': dict(ycondition.items()),
        'forecasts': forecasts,
        'title': rss.findtext('channel/title'),
        'pubDate': rss.findtext('channel/item/pubDate'), #rss.findtext('channel/lastBuildDate'),
        'location': dict(rss.find('channel/{%s}location' % WEATHER_NS).items()),
        'wind': dict(rss.find('channel/{%s}wind' % WEATHER_NS).items()),
        'atmosphere': dict(rss.find('channel/{%s}atmosphere' % WEATHER_NS).items()),
        'astronomy': dict(rss.find('channel/{%s}astronomy' % WEATHER_NS).items()),
        'units': dict(rss.find('channel/{%s}units' % WEATHER_NS).items())
    }

def print_openbox_pipe_menu(weather):
    print '<openbox_pipe_menu>'
    print '<separator label="%s %s" />' % (weather['location']['city'],weather['pubDate'])
    print '<separator label="Aktuelle Wetterlage" />'
    print '<item label="Wetter: %s" />' % weather['current_condition']['text']
    print '<item label="Temperatur: %s %s" />' % ( weather['current_condition']['temp'],
                                          weather['units']['temperature'] )
    print '<item label="Luftfeuchtigkeit: %s%%" />' % weather['atmosphere']['humidity']
    print '<item label="Sicht: %s %s" />' % ( weather['atmosphere']['visibility'],
                                          weather['units']['distance'] )
    
    #pressure: steady (0), rising (1), or falling (2)
    if weather['atmosphere']['rising'] == 0:
        pressure_state = 'unveraendert'
    elif weather['atmosphere']['rising'] == 1:
        pressure_state = 'steigend'
    else:
        pressure_state = 'fallend'
    print '<item label="Luftdruck: %s %s (%s)" />' % ( weather['atmosphere']['pressure'],
                                          weather['units']['pressure'], pressure_state )
    print '<item label="gefühlte Temperatur: %s %s" />' % ( weather['wind']['chill'],
                                          weather['units']['temperature'] )
    print '<item label="Windrichtung: %s degrees" />' % weather['wind']['direction']
    print '<item label="Windgeschwindigkeit: %s %s" />' % ( weather['wind']['speed'],
                                          weather['units']['speed'] )
    print '<item label="Sonnenaufgang: %s" />' % weather['astronomy']['sunrise']
    print '<item label="Sonnenuntergang: %s" />' % weather['astronomy']['sunset']
    for forecast in weather['forecasts']:
        print '<separator label="Vorhersage: %s" />' % forecast['day']
        print '<item label="Wetter: %s" />' % forecast['text']
        print '<item label="Min. Temperatur: %s %s" />' % ( forecast['low'],
                                                weather['units']['temperature'] )
        print '<item label="Max. Temperatur: %s %s" />' % ( forecast['high'],
                                                weather['units']['temperature'] )
    print '</openbox_pipe_menu>'

cache_file = join(os.getenv("HOME"), '.yweather.cache')

try:
    f = open(cache_file,'rb')
    cache = pickle.load(f)
    f.close()
except IOError:
    cache = None

if cache == None or (zip_code, units) not in cache or (
        cache[(zip_code, units)]['date'] + timedelta(hours=CACHE_HOURS) < datetime.utcnow()):
    # The cache is outdated
    weather = weather_for_zip(zip_code, units)
    if cache == None:
        cache = dict()
    cache[(zip_code, units)] = {'date': datetime.utcnow(), 'weather': weather}
    
    #Save the data in the cache
    try:
        f = open(cache_file, 'wb')
        cache = pickle.dump(cache, f, -1)
        f.close()
    except IOError:
        raise
else:
    weather = cache[(zip_code, units)]['weather']


print_openbox_pipe_menu(weather)

Über folgende Einträge wird der Wetterbericht (für Berlin) in das Kontextmenü eingebunden:

  • Google:

    <menu id="pipe-weather" label="Wetterbericht Google" execute="python ~/bin/gweather.py Berlin de" />
  • Yahoo:

    <menu id="pipe-weather2" label="Wetterbericht Yahoo" execute="python ~/bin/yweather.py GMXX0007 Celsius" />

Das Ergebnis wird in beiden Varianten für eine Stunde (CACHE_HOURS = 1) zwischengespeichert.

XDG-Menü

Skript

Mit dem Python-Skript xdg-menu.py ist das komplette LXDE/start-here.png LXDE-Menü unten links (zusätzlich) über das Kontextmenü erreichbar.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python
#
# Copyright (C) 2008  Red Hat, Inc.
#
# 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 2 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/>.
#
# Author(s): Luke Macken <lmacken@redhat.com>
#            Miroslav Lichvar <mlichvar@redhat.com>


import gmenu, re, sys
from xml.sax.saxutils import escape

def walk_menu(entry):
        if entry.get_type() == gmenu.TYPE_DIRECTORY:
                print '<menu id="%s" label="%s">' \
                        % (escape(entry.menu_id), escape(entry.get_name()))
                map(walk_menu, entry.get_contents())
                print '</menu>'
        elif entry.get_type() == gmenu.TYPE_ENTRY and not entry.is_excluded:
                print ' <item label="%s">' % \
                        escape(entry.get_name().replace('"', ''))
                command = re.sub(' [^ ]*%[fFuUdDnNickvm]', '', entry.get_exec())
                if entry.launch_in_terminal:
                        command = 'xterm -title "%s" -e %s' % \
                                (entry.get_name(), command)
                print '         <action name="Execute">' + \
                        '<command>%s</command></action>' % escape(command)
                print ' </item>'

if len(sys.argv) > 1:
        menu = sys.argv[1] + '.menu'
else:
        menu = 'applications.menu'

print '<?xml version="1.0" encoding="UTF-8"?>'
print '<openbox_pipe_menu>'
map(walk_menu, gmenu.lookup_tree(menu).root.get_contents())
print '</openbox_pipe_menu>'

Quelle: Fedora Packages Git Repositories {en}

Über folgenden Eintrag wird das Skript ins Kontextmenü eingebunden:

<menu id="pipe-xdg-menu" label="Menü" execute="python ~/bin/xdg-menu.py" />

Problembehebung

Manchmal kann es passieren, dass ein Skript eine bestimmte Komponente oder ein anderes Programm benötigt, ohne die es nicht funktioniert. Falls also beispielsweise das oben genannte xdg-menu.py nicht funktioniert, führt man das Skript in einem Terminal-Fenster [2] aus, um die Ausgabe zu überprüfen:

python ~/bin/xdg-menu.py 

Eine Fehlermeldung könnte z.B. so aussehen:

Traceback (most recent call last):
  File "bin/xdg-menu.py", line 22, in <module>
    import gmenu, re, sys
ImportError: No module named gmenu

Nun sucht man in der Paketverwaltung, ob ein entsprechendes Paket vorhanden ist und installiert es:

  • python-gmenu

Wiki/Vorlagen/Installbutton/button.png

Damit ist das Problem in diesem konkreten Fall behoben.

Diese Revision wurde am 24. Mai 2012 14:36 von aasche erstellt.
Die folgenden Schlagworte wurden dem Artikel zugewiesen: LXDE, Desktop, Shell

Passwort vergessen?