Un buon Menu
Siamo giunti agli ultimi passaggi del progetto Chromix e ci serve il Menu.
Ma Perché ci serve un Menu ?
Ci serve perché il Menu di LXDE così come la maggior parte dei Menu delle varie interfacce grafiche, è studiato per far funzionare un Desktop e attinge alle relative applicazioni, mentre il nostro obiettivo è creare qualcosa di semplice che si interfacci con le Web-App di Chromium.
Devo dire che dopo molte titubanze, tipo la ricerca di un plugin adatto o l'idea di scriverlo, mi è venuto in mente che in fondo la cosa più facile era fare uno scriptino in Python.
Uno scriptino in Python ?
Si uno scriptino che usasse Python e le GTK, con tanto di supporto CAIRO.
Per installare ciò che ci serve occorre usare il terminale di root cioè quello rosso e poi dare il comando :
apt-get install python-gtk
e per quanto riguarda le operazioni come root abbiamo finito.
Aperto un terminale normale, invece creiamo due folder nascosti come chromix nella nostra HOME :
mkdir .panmenu
mkdir .menu
cd .panmenu
E siamo dentro la neonata cartella dove dobbiamo scrivere (se vogliamo con leafpad) il primo file che si chiama config e che contiene la configurazione del pannello :
Potrebbe essere :
Altra riga accettata da questo file è defaulticon che indica il nome dell'icona di default, la quale se non indicata diventa /.local/share/icons/default.png.bottom=50
minrows=3
maxrows=8
mincols=3
maxcols=12
Nella stessa cartella dobbiamo inserire l'icona del programma che si chiama panmenu.png , io ho scelto questa :
La stessa icona la potete copiare con un :
mkdir ~/.local/share/iconscp panmenu.png ~/.local/share/icons/default.png
Oppure potete sceglierne una completamente diversa.
Ora aggiungiamo il link chiamato : panmenu.desktop e scriviamo le seguenti righe di testo :
[Desktop Entry]
Version=1.0
Name=PanMenu
Comment=MenuPanel
Exec=/home/chromix/.panmenu/panmenu
Terminal=false
X-MultipleArgs=false
Type=Application
Icon=/home/chromix/.panmenu/panmenu.png
Lo script
Dopo avere aggiunto le parti secondarie arriviamo ora al vero e proprio pannello in python-gtk il cui codice è questo :
#!/usr/bin/python
#
# PANMENU
# A simple menu panel for Linux written in Python GTK
#
# Copyright 2013 by ElDuraMinga (elduraminga.blogspot.com)
#
# 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.
#
# See <http://www.gnu.org/licenses/>.
#
import pygtk
import gtk
import os
import fileinput
import gtk, gobject, cairo
global inside
inside=False
global page
page=0
global profile,home,folderlist
home=os.getenv("HOME")
folderlist=[]
global GLX,GLS
GLX=64
GLS=48
global bottom,minrows,mincols,maxrows,maxcols,defaulticon
bottom=0
minrows=5
mincols=3
maxrows=8
maxcols=8
defaulticon=home+"/.local/share/icons/default.png"
global nRows,nCols,nIndex,nCount
nRows=0
nCols=0
nIndex=-1
nCount=0
global pixmaps
pixmaps=[]
#Check single instance
def singleInstance() :
if os.access("/run/lock/panmenu.lock",os.F_OK):
quit()
else :
f=open("/run/lock/panmenu.lock","w")
f.close()
def killInstance() :
os.unlink("/run/lock/panmenu.lock")
# Read Global Profile
def profileRead() :
global bottom
global minrows
global mincols
global maxrows
global maxcols
global defaulticon
global home
for i in fileinput.input(home+"/.panmenu/config") :
v=i.strip().split('=')
if len(v)==2 :
if (v[0]=='bottom') :
bottom=int(v[1])
if (v[0]=='minrows') :
minrows=int(v[1])
if (v[0]=='maxrows') :
maxrows=int(v[1])
if (v[0]=='mincols') :
mincols=int(v[1])
if (v[0]=='maxcols') :
maxcols=int(v[1])
if (v[0]=='defaulticon') :
defaulticon=v[1]
def checkIcon( iconname ) :
global defaulticon
global home
for dirname, dirnames, filenames in os.walk(home+"/.local/share/icons"):
for i in filenames :
if i[:len(iconname)]==iconname :
return dirname+"/"+i
return defaulticon
def parseApplicationFile( name ) :
global folderlist
fname=""
ficon=""
fexec=""
for i in fileinput.input(name) :
v=i.strip()
if v[:5]=="Icon=" :
if v[5]=='/' :
ic=v[5:]
else :
ic=checkIcon(v[5:]+".png")
ficon=ic
if v[:5]=="Exec=" :
fexec=v[5:]
if v[:5]=="Name=" :
fname=v[5:]
folderlist.append([fname,ficon,fexec])
#Read the local applications directory
def applicationsRead() :
global home
dirlist=os.listdir(home+"/.menu")
dirlist.sort()
for i in dirlist :
j=home+"/.menu/"+i
if (os.path.isfile(j)) :
if (i[-8:]==".desktop") :
parseApplicationFile(j)
dirlist=os.listdir(home+"/.local/share/applications")
dirlist.sort()
for i in dirlist :
j=home+"/.local/share/applications/"+i
if (os.path.isfile(j)) :
if (i[-8:]==".desktop") :
parseApplicationFile(j)
def iconShow(surface,cr,x,y,w,h,alpha) :
cr.save();
width = surface.get_width()
height = surface.get_height()
width=w/width;
height=h/height;
cr.scale(width, height);
cr.set_source_surface(surface, x/width, y/height)
if alpha :
cr.paint_with_alpha(0.5)
else :
cr.paint();
cr.restore();
# Create a GTK+ widget on which we will draw using Cairo
class Screen(gtk.DrawingArea):
# Draw in response to an expose-event
__gsignals__ = { "expose-event": "override" }
# Handle the expose-event by drawing
def do_expose_event(self, event):
# Create the cairo context
cr = self.window.cairo_create()
# Restrict Cairo to the exposed area; avoid extra work
cr.rectangle(event.area.x, event.area.y,
event.area.width, event.area.height)
cr.clip()
self.draw(cr, *self.window.get_size())
def draw(self, cr, width, height):
global iconlist
global nIndex
j=page
for i in range(nRows*nCols) :
cx=GLX*((i%nCols)+1);
cy=GLX*((i/nCols)+1);
if (nIndex==j) :
iconShow(folderlist[j][3],cr,float(cx-GLS/2),float(cy-GLS/2),float(GLS),float(GLS),True);
else :
iconShow(folderlist[j][3],cr,float(cx-GLS/2),float(cy-GLS/2),float(GLS),float(GLS),False);
j+=1
if (j>=len(folderlist)) :
break
if nIndex!=-1 :
cr.save();
cr.set_source_rgb(0, 0, 0)
cr.select_font_face("Nimbus Sans L", cairo.FONT_SLANT_NORMAL,cairo.FONT_WEIGHT_NORMAL)
cr.set_font_size(20)
cr.move_to(GLX/2, height-26)
cr.show_text(folderlist[nIndex][0])
cr.restore();
def motionEvent(widget, event):
global folderlist
global nIndex
global inside
inside=True
j=page
q=-1
for i in range(nCols*nRows) :
cx=GLX*((i%nCols)+1);
cy=GLX*((i/nCols)+1);
if ( (event.x>=float(cx-GLX/2)) and (event.x<=float(cx+GLX/2)) and (event.y>=float(cy-GLX/2)) and (event.y<=float(cy+GLX/2)) ) :
q=j
break;
j+=1
if (j>=len(folderlist)) :
break
if (q!=nIndex) :
nIndex=q
widget.queue_draw();
return True
def buttonEvent(widget, event):
global nIndex
global folderlist
global page
if event.type == gtk.gdk.BUTTON_PRESS :
if event.button == 1:
killInstance()
if (nIndex!=-1) :
os.execl("/bin/sh","/bin/sh","-c",folderlist[nIndex][2]);
gtk.main_quit()
if event.button == 3:
page+=nCols*nRows
if page>=len(folderlist) :
page=0
widget.queue_draw()
return True
def quitAll(widget, event):
global inside
if inside :
killInstance()
gtk.main_quit()
return True
# Panel intialization and run
def run(Widget):
global nRows
global nCols
global nIndex
global nCount
global folderlist
global iconlist
nRows=minrows
nCols=mincols
nCount=len(folderlist)
while (nRows*nCols<nCount) :
nRows+=1;
if (nRows>maxrows) :
nCols+=1;
nRows=minrows
if (nCols>maxcols) :
nRows=maxrows
nCols=maxcols
nCount=nRows*nCols;
break;
for i in folderlist :
try:
f = cairo.ImageSurface.create_from_png(i[1])
except Exception, e:
f = cairo.ImageSurface.create_from_png(defaulticon)
i.append(f)
window = gtk.Window()
window.connect("delete-event", quitAll)
widget = Widget()
widget.show()
window.add(widget)
widget.connect("motion_notify_event", motionEvent)
widget.connect("button_press_event", buttonEvent)
widget.connect("leave_notify_event", quitAll)
widget.set_events(gtk.gdk.EXPOSURE_MASK
| gtk.gdk.LEAVE_NOTIFY_MASK
| gtk.gdk.BUTTON_PRESS_MASK
| gtk.gdk.POINTER_MOTION_MASK
| gtk.gdk.POINTER_MOTION_HINT_MASK)
color = gtk.gdk.Color(red=65535, green=65535, blue=65535, pixel=0)
window.background=color
window.resize(GLX*(nCols+1), GLX*(nRows+1)+32);
window.move(5, gtk.gdk.screen_height() - bottom - 32 - GLX*(nRows+1));
window.present()
window.set_name ("menupanel")
window.set_title("Menu Panel")
window.set_decorated(False)
gtk.main()
if __name__ == "__main__":
singleInstance()
#
# PANMENU
# A simple menu panel for Linux written in Python GTK
#
# Copyright 2013 by ElDuraMinga (elduraminga.blogspot.com)
#
# 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.
#
# See <http://www.gnu.org/licenses/>.
#
import pygtk
import gtk
import os
import fileinput
import gtk, gobject, cairo
global inside
inside=False
global page
page=0
global profile,home,folderlist
home=os.getenv("HOME")
folderlist=[]
global GLX,GLS
GLX=64
GLS=48
global bottom,minrows,mincols,maxrows,maxcols,defaulticon
bottom=0
minrows=5
mincols=3
maxrows=8
maxcols=8
defaulticon=home+"/.local/share/icons/default.png"
global nRows,nCols,nIndex,nCount
nRows=0
nCols=0
nIndex=-1
nCount=0
global pixmaps
pixmaps=[]
#Check single instance
def singleInstance() :
if os.access("/run/lock/panmenu.lock",os.F_OK):
quit()
else :
f=open("/run/lock/panmenu.lock","w")
f.close()
def killInstance() :
os.unlink("/run/lock/panmenu.lock")
# Read Global Profile
def profileRead() :
global bottom
global minrows
global mincols
global maxrows
global maxcols
global defaulticon
global home
for i in fileinput.input(home+"/.panmenu/config") :
v=i.strip().split('=')
if len(v)==2 :
if (v[0]=='bottom') :
bottom=int(v[1])
if (v[0]=='minrows') :
minrows=int(v[1])
if (v[0]=='maxrows') :
maxrows=int(v[1])
if (v[0]=='mincols') :
mincols=int(v[1])
if (v[0]=='maxcols') :
maxcols=int(v[1])
if (v[0]=='defaulticon') :
defaulticon=v[1]
def checkIcon( iconname ) :
global defaulticon
global home
for dirname, dirnames, filenames in os.walk(home+"/.local/share/icons"):
for i in filenames :
if i[:len(iconname)]==iconname :
return dirname+"/"+i
return defaulticon
def parseApplicationFile( name ) :
global folderlist
fname=""
ficon=""
fexec=""
for i in fileinput.input(name) :
v=i.strip()
if v[:5]=="Icon=" :
if v[5]=='/' :
ic=v[5:]
else :
ic=checkIcon(v[5:]+".png")
ficon=ic
if v[:5]=="Exec=" :
fexec=v[5:]
if v[:5]=="Name=" :
fname=v[5:]
folderlist.append([fname,ficon,fexec])
#Read the local applications directory
def applicationsRead() :
global home
dirlist=os.listdir(home+"/.menu")
dirlist.sort()
for i in dirlist :
j=home+"/.menu/"+i
if (os.path.isfile(j)) :
if (i[-8:]==".desktop") :
parseApplicationFile(j)
dirlist=os.listdir(home+"/.local/share/applications")
dirlist.sort()
for i in dirlist :
j=home+"/.local/share/applications/"+i
if (os.path.isfile(j)) :
if (i[-8:]==".desktop") :
parseApplicationFile(j)
def iconShow(surface,cr,x,y,w,h,alpha) :
cr.save();
width = surface.get_width()
height = surface.get_height()
width=w/width;
height=h/height;
cr.scale(width, height);
cr.set_source_surface(surface, x/width, y/height)
if alpha :
cr.paint_with_alpha(0.5)
else :
cr.paint();
cr.restore();
# Create a GTK+ widget on which we will draw using Cairo
class Screen(gtk.DrawingArea):
# Draw in response to an expose-event
__gsignals__ = { "expose-event": "override" }
# Handle the expose-event by drawing
def do_expose_event(self, event):
# Create the cairo context
cr = self.window.cairo_create()
# Restrict Cairo to the exposed area; avoid extra work
cr.rectangle(event.area.x, event.area.y,
event.area.width, event.area.height)
cr.clip()
self.draw(cr, *self.window.get_size())
def draw(self, cr, width, height):
global iconlist
global nIndex
j=page
for i in range(nRows*nCols) :
cx=GLX*((i%nCols)+1);
cy=GLX*((i/nCols)+1);
if (nIndex==j) :
iconShow(folderlist[j][3],cr,float(cx-GLS/2),float(cy-GLS/2),float(GLS),float(GLS),True);
else :
iconShow(folderlist[j][3],cr,float(cx-GLS/2),float(cy-GLS/2),float(GLS),float(GLS),False);
j+=1
if (j>=len(folderlist)) :
break
if nIndex!=-1 :
cr.save();
cr.set_source_rgb(0, 0, 0)
cr.select_font_face("Nimbus Sans L", cairo.FONT_SLANT_NORMAL,cairo.FONT_WEIGHT_NORMAL)
cr.set_font_size(20)
cr.move_to(GLX/2, height-26)
cr.show_text(folderlist[nIndex][0])
cr.restore();
def motionEvent(widget, event):
global folderlist
global nIndex
global inside
inside=True
j=page
q=-1
for i in range(nCols*nRows) :
cx=GLX*((i%nCols)+1);
cy=GLX*((i/nCols)+1);
if ( (event.x>=float(cx-GLX/2)) and (event.x<=float(cx+GLX/2)) and (event.y>=float(cy-GLX/2)) and (event.y<=float(cy+GLX/2)) ) :
q=j
break;
j+=1
if (j>=len(folderlist)) :
break
if (q!=nIndex) :
nIndex=q
widget.queue_draw();
return True
def buttonEvent(widget, event):
global nIndex
global folderlist
global page
if event.type == gtk.gdk.BUTTON_PRESS :
if event.button == 1:
killInstance()
if (nIndex!=-1) :
os.execl("/bin/sh","/bin/sh","-c",folderlist[nIndex][2]);
gtk.main_quit()
if event.button == 3:
page+=nCols*nRows
if page>=len(folderlist) :
page=0
widget.queue_draw()
return True
def quitAll(widget, event):
global inside
if inside :
killInstance()
gtk.main_quit()
return True
# Panel intialization and run
def run(Widget):
global nRows
global nCols
global nIndex
global nCount
global folderlist
global iconlist
nRows=minrows
nCols=mincols
nCount=len(folderlist)
while (nRows*nCols<nCount) :
nRows+=1;
if (nRows>maxrows) :
nCols+=1;
nRows=minrows
if (nCols>maxcols) :
nRows=maxrows
nCols=maxcols
nCount=nRows*nCols;
break;
for i in folderlist :
try:
f = cairo.ImageSurface.create_from_png(i[1])
except Exception, e:
f = cairo.ImageSurface.create_from_png(defaulticon)
i.append(f)
window = gtk.Window()
window.connect("delete-event", quitAll)
widget = Widget()
widget.show()
window.add(widget)
widget.connect("motion_notify_event", motionEvent)
widget.connect("button_press_event", buttonEvent)
widget.connect("leave_notify_event", quitAll)
widget.set_events(gtk.gdk.EXPOSURE_MASK
| gtk.gdk.LEAVE_NOTIFY_MASK
| gtk.gdk.BUTTON_PRESS_MASK
| gtk.gdk.POINTER_MOTION_MASK
| gtk.gdk.POINTER_MOTION_HINT_MASK)
color = gtk.gdk.Color(red=65535, green=65535, blue=65535, pixel=0)
window.background=color
window.resize(GLX*(nCols+1), GLX*(nRows+1)+32);
window.move(5, gtk.gdk.screen_height() - bottom - 32 - GLX*(nRows+1));
window.present()
window.set_name ("menupanel")
window.set_title("Menu Panel")
window.set_decorated(False)
gtk.main()
if __name__ == "__main__":
singleInstance()
try :
profileRead()
applicationsRead()
run(Screen)
profileRead()
applicationsRead()
run(Screen)
except e:
killInstance()
Lo dovete copia-incollare dentro un file chiamato panmenu nella stessa cartella, menù e renderlo eseguibile tipo :
chmod 755 panmenu
Come funziona PanMenu
Panmenu recupera le applicazioni dai desktop link contenuti in .menu, cioè quelle che aggiungeremo noi e .local/share/applications che invece sono quelle che aggiunge Chromium, ossia le web-app.
Entrambi andranno a reperire le icone relative in .local/share/icons . Notare che questo menù non usa le libmenu, ma fa per conto suo ed è estremamente dinamico.
Il suo funzionamento è la costruzione di una finestra a sinistra in basso BOTTOM righe sopra l'ultima linea, individuando la serie di icone del pannello mettendole in ordine alfabetico, infine visualizzando un rettangolo che si estende da MINROWS MINCOLS a MAXROWS MAXCOLS, in modo progressivo.
Oltre il termine di MAXROWS/MAXCOLS si procede a pagine e per andare sulle successive si usa il Right-Click del mouse a rotazione.
Il pannello scompare quando si esce dal menu col mouse o quando si preme su un link, nel qual caso viene usato un execl con sh per lanciare il comando, che resta così regolarmente "figlio" del pannello.
Nota : Se il menù non si apre più controllare ed eventualmente rimuovere il lock per l'istanza singola : /run/lock/panmenu.lock
Integrare PanMenu nel launcher di LXDE
Per prima cosa, con leafpad si va a modificare il file :
~/.config/lxpanel/LXDE/panels/panel
Aggiungendo al primo launcher una altro Button, col seguente codice :
...
Plugin {
type = launchbar
Config {
Button {
id=/home/chromix/.panmenu/panmenu.desktop
type = launchbar
Config {
Button {
id=/home/chromix/.panmenu/panmenu.desktop
}
Button {
id=/usr/share/applications/chromium.deskto
Button {
id=/usr/share/applications/chromium.deskto
}
}
}
}
}
...
poi dobbiamo fare in modo che l'applicazione non sia visualizzata nella taskbar, modificando opportunamente la configurazione di openbox cioè il file :
~/.config/openbox/lxde-rc.xml
aggiungendo in fondo, prima della chiusura del tag XML </applications> quattro righe:
...
# end of the example
-->
<application name="panmenu" >
<skip_taskbar>yes</skip_taskbar>
<skip_pager>yes</skip_pager>
</application>
</applications>
</openbox_config>
...
Ora non ci resta che popolare, con due applicazioni di base il .menu e per far questo diamo la sequenza dei comandi :
Cioè copiamo sia il link a queste due applicazioni (in .menu) che le relative icone (in .local/share/icons), come indicate all'interno dei link.cd ~/.menu
cp /usr/share/applications/lxterminal.desktop .
cp /usr/share/applications/pcmanfm.desktop .
cd ~/.local/share/icons/
cp /usr/share/icons/nuoveXT2/48x48/apps/terminal.png lxterminal.png
cp /usr/share/icons/nuoveXT2/48x48/apps/file-manager.png system-file-manager.png
Abbiamo scelto icone 48x48 che è la dimensione standard per il menu.
Siccome non sono accettate le sostituzioni tipo %U nei links, andiamo nuovamente in .menu e sostituiamo la riga di exec di pcmanfm.desktop con :
...
Exec=pcmanfm ~/Downloads
...
...
A questo punto possiamo riavviare.
Riavvio
Se avete fatto i regolari passaggi, cliccando sul nuovo launcher nel pannello del menù vedrete qualcosa tipo :
Facendo click sul file manager per altro, entreremo direttamente in Downloads, dove ci si aspetta che Chromium metta ciò che scarichiamo.
Inoltre sul tab sinistro di pcmanfm troviamo Applications , se clicchiamo qui, scopriamo che si tratta di un altro menu completo da cui possiamo avviare tutte le applicazioni installate :
dunque possiamo rimuovere quello sul pannello e lasciare solo il nostro PanMenu e Chromium.
Aggiungere Web-Apps e Links
Se apriamo Chromium sulla new-tab classica, dovremmo trovare l'elenco di tutte le applicazioni che abbiamo integrato.
All'inizio c'è solo il Chrome Web Store che però è un Web-App come tutte le altre dunque possiamo benissimo inserirlo come applicazione.
Per farlo clicchiamo col tasto destro sull'icona stessa e scegliamo Create Shortcuts :
Per farlo clicchiamo col tasto destro sull'icona stessa e scegliamo Create Shortcuts :
Ora, se andiamo a vedere il nostro menù, sarà diventato così :
E potremo lanciare l'applicazione come se fosse locale :
Questo ovviamente, si può fare con tutte le applicazioni che vogliamo e ciò è abbastanza grandioso.
Ma c'è molto altro, tipo inserire un LINK come se fosse una web-app, con l'unico problema che l'icona sarà quella dello standard web di solito 16x16, poi scalata di tre volte.
Comunque apriamo Chromium , andiamo su un sito tipo www.twitter.com e clicchiamo sul menù di Chromium , sotto Tools troviamo Create Application Shortcut .
Stessa operazione di prima, cioè creazione del link non sul desktop ma nel menu ed ecco il nostro nuovo menu con l'uccellino.
Ed ecco il nostro Chromix
Se volete, potete portare avanti piccole rifiniture, tipo cambiare la PNG che sta in /usr/share/lxde/images/logout-banner.png, con l'icona di Chromium 256x256, in modo da avere un menu di chiusura più raffinato e tante altre cose che lascio fare a voi.
Per ora arrivederci.
Nessun commento:
Posta un commento