View previous topic :: View next topic |
Author |
Message |
Jengu Guru
Joined: 28 Oct 2004 Posts: 384
|
Posted: Sat Mar 26, 2005 10:05 am Post subject: New app! PyMenu to replace ugly wm menus |
|
|
Screenshot!
http://img103.exs.cx/img103/5963/sweetshot9ku.jpg
What is PyMenu?
PyMenu is a replacement for the standard menu that you get when you right click on the root window in minimalist window managers. It is written in 100% Python and uses the PyGTK and pyxdg libraries. I wrote it because I wanted to learn PyGTK, become more familiar with Python itself, and because I thought specifying the icons I wanted by hand sucked
What makes PyMenu special?
The two main reasons I wanted PyMenu was that I thought it was silly that all the WM's were running around with their own obscure formats for making menus. I just wanted to make a folder with symlinks in it, and have it be treated as a menu -- a feature PyMenu supports. Also, PyMenu searches for icons automatically, so you don't have to tell your WM that yes, surprise, you want firefox.png to be the icon for firefox. All features:
-Can make menus out of folders of symlinks
-Support for the freedesktop.org .desktop file format
-Configure with python instead of some obscure WM syntax
-Automatically finds icons
-Caches icons for speed
How do I install it?
Code: |
$ emerge pygtk pyxdg
|
Then copy and paste the pymenu.py script and the showpymenu script below and put them wherever you like, maybe in ~/pymenu. Put:
Code: |
python ~/pymenu/pymenu.py
|
In your .xsession file, or your wm's startup file. PyMenu will start when your wm starts and run in the background. Then, use whatever is normal for your wm to bind a key or a mouse click to the showpymenu script, which will cause pymenu to appear.
Now, to actually have stuff appear in the menu, by default, pymenu will look in ~/.pymenu/menu for symlinks. Go ahead and make this folder. Then make subfolders and fill them with symlinks. For example, to add tuxracer to the menu:
Code: |
$ cd ~/.pymenu/menu
$ mkdir Games
$ cd Games
$ ln -s /usr/bin/tuxracer TuxRacer
|
Now open PyMenu with the bind you have setup, and click "Refresh Menu." TuxRacer should appear
I have a specific icon I want PyMenu to use, how can I make it use it?
PyMenu will always look for icons in your ~/.icons folder first. Put the icons you want there.
PyMenu isn't finding my icons, why?
First, make sure if you have specific icons you want that they are in ~/.icons. PyMenu will search other locations, such as /usr/share/pixmaps, after that.
Also, pymenu expects the name of the icon file to correspond to the name of the executable for a program. For The GIMP for example, it will search for gimp-2.0.png, not gimp.png.
I'm using sawfish, and the menu isn't appearing!
Sawfish has some quirky behavior with keybinds that can cause the menu not to appear. Put this in your .sawfishrc to bind right mouse click on the root window to showing the menu:
Code: |
(defun showPyMenu ()
(ungrab-pointer)
(ungrab-keyboard)
(ungrab-server)
(sync-server t)
(signal-process (string->number (read-line (open-file "~/.pymenu/.pid" `read))) 10))
(bind-keys root-window-keymap
"Button3-Click" `(showPyMenu))
|
This workaround isn't perfect, you'll have to hold the button down in order for the menu to stay visible.
The menu won't appear
Run:
Code: |
$ ps aux | grep pymenu
|
And make sure that pymenu is running. If it is not you haven't set it up to startup with your wm properly. Also, make sure you have chmod'd the showpymenu script to be executable:
Code: |
$ chmod +x showpymenu
|
Where did you get the icons in the screenshot?
They were made by the Gentoo community: http://www.gentoo.org/dyn/icons.xml
If I want to submit patches where should I send them?
k 0 4 j g 0 2 {(a)(t)} k z o o {(d)(o)(t)} e d u
Or just post them here!
Alright already! Where's the code?
pymenu.py:
Code: |
#!/usr/bin/env python
""" pymenu.py --- flexible menu application
-*- lisp-mode -*-
Copyright (C) 2005 Free Software Foundation, Inc.
Author: Joseph Garvin <k04jg02@kzoo.edu>
This file 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, or (at your option)
any later version.
This file 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."""
import pygtk
pygtk.require('2.0')
import gtk, pango
import glob, os, pwd, signal
import math, string
import cPickle as pickle
# For freedesktop.org .desktop file support
from xdg.DesktopEntry import *
# For .desktop file url opening support
import webbrowser
def setMenuItemIcon(menu_item, iconPath, sizeMod=1):
global PIXEL_SIZE
icon = gtk.Image()
pxIcon = gtk.gdk.pixbuf_new_from_file(iconPath)
pxIcon = pxIcon.scale_simple(PIXEL_SIZE * sizeMod, PIXEL_SIZE * sizeMod, gtk.gdk.INTERP_BILINEAR)
icon.set_from_pixbuf(pxIcon)
menu_item.set_image(icon)
#TODO: Look into using bash find instead, Juhaz says 10x speedup
def findIcon(iconName):
# See if we have a cached copy of the icon
global iconCache
if iconCache.has_key(iconName):
icon = iconCache[iconName]
if os.path.isfile(icon):
return iconCache[iconName]
# See if the icon is an absolute path
if iconName[0] == '/' and os.path.isfile(iconName):
return iconName
# Alright, actually search for the icon
global search_cache
for (path, dirs, files) in search_cache:
iconsFound = glob.glob(path + "/*" + iconName + "*")
if iconsFound != []:
print "Found Icon: " + iconsFound[0] + " for: " + iconName
iconCache[iconName] = iconsFound[0]
return iconsFound[0]
return ""
def makeMenuFromFolder(menu, menuFolderPath):
originalMenu = menu
# Quit when selection is made or menu is closed
def quit(w):
print "selection made", w
menu.popdown()
gtk.main_quit()
menu.connect("selection-done", quit)
menuWalk = os.walk(menuFolderPath)
# Didn't find cached copy or too old, start building new menu
AllMenus = {"":menu}
# Find folder icon
global folderIcon
global IGNORE_HIDDEN_FILES
for item in menuWalk:
# Add folder into menus if not present
fullFolderPath = item[0].replace(menuFolderPath, "")
if fullFolderPath != "":
folderHistory = fullFolderPath.split('/')
folderName = os.path.basename(fullFolderPath)
prevFolder = string.join(folderHistory[:-1], '/')
subMenuItem = gtk.ImageMenuItem(folderName)
if folderIcon != "":
setMenuItemIcon(subMenuItem, folderIcon)
AllMenus[prevFolder].prepend(subMenuItem)
menu = gtk.Menu()
subMenuItem.set_submenu(menu)
subMenuItem.show()
AllMenus[fullFolderPath] = menu
# Figure out what we want to make shortcuts to and sort
os.chdir(item[0])
shortcutList = item[2]
if IGNORE_HIDDEN_FILES:
shortcutList = filter(lambda x: x[0] != '.', shortcutList)
shortcutList.sort()
# Now actually add the icons to the folder
for shortcut in shortcutList:
executeMenuItem = executeProg
#Find a suitable icon
iconPath = ""
if os.path.islink(shortcut):
searchTerm = os.readlink(shortcut).split('/')[-1]
shortcutMenuName = shortcut
toExecute = item[0] + "/" + shortcut.replace(" ", r'\ ')
elif shortcut.split('.')[-1] == "desktop":
tempEntry = DesktopEntry()
tempEntry.parse(shortcut)
shortcutMenuName = tempEntry.getName()
searchTerm = tempEntry.getIcon()
if searchTerm == "":
searchTerm = shortcut.split('.')[0]
toExecute = tempEntry.getExec()
if toExecute == '':
toExecute = os.path.expandvars(tempEntry.getURL())
executeMenuItem = openURLWrapper
else:
searchTerm = shortcut
shortcutMenuName = shortcut
toExecute = item[0] + "/" + shortcut.replace(" ", r'\ ')
newShortcutEntry = gtk.ImageMenuItem(shortcutMenuName)
iconPath = findIcon(searchTerm)
if iconPath == "":
iconPath = findIcon(NO_ICON_SEARCH_TERM)
if iconPath != "":
setMenuItemIcon(newShortcutEntry, iconPath)
menu.append(newShortcutEntry)
newShortcutEntry.connect("activate", executeMenuItem, toExecute)
newShortcutEntry.show()
return originalMenu
# Open url when menu item selected
def openURLWrapper(widget, string):
webbrowser.open(string)
# Execute program when selected
def executeProg(widget, string):
os.popen2(string)
def main():
import time
time1 = time.time()
global home
home = pwd.getpwuid(os.getuid())[5]
if not os.path.isdir("%s/.pymenu" % home):
raise "Error: ~/.pymenu not found"
if not os.path.isdir("%s/.pymenu/menu" % home):
raise "Error: ~/.pymenu/menu not found"
screen = gtk.Window().get_screen()
# Configuration
global ICON_PATHS, FOLDER_ICON_SEARCH_TERM, PREF_ICON_SCALE
global NO_ICON_SEARCH_TERM, IGNORE_HIDDEN_FILES
if not os.path.isfile("%s/.pymenu/.pymenurc" % home):
print "Warning: ~/.pymenu/.pymenurc not found. Using defaults."
ICON_PATHS = gtk.icon_theme_get_for_screen(screen).get_search_path()
PREF_ICON_SCALE = 3
FOLDER_ICON_SEARCH_TERM = "folder"
NO_ICON_SEARCH_TERM = "question"
MENU_FOLDERS_APPEND = ("%s/.pymenu/menu/" % home,)
MENU_FOLDERS_ADD_SUBMENU = ("%s/Desktop" % home,)
MODULE_FOLDER = "%s/.pymenu/modules/" % home
IGNORE_HIDDEN_FILES = True
SHOW_REFRESH_OPTION = True
REFRESH_ICON_SEARCH_TERM = "refresh"
else:
# After this go through each of the vars and .replace('~', home)
execfile ("%s/.pymenu/.pymenurc" % home)
# Build icon cache for speed
global iconCache
try:
iconCacheFile = open("%s/.pymenu/.iconcache" % home, 'r')
iconCache = pickle.load(iconCacheFile)
iconCacheFile.close()
except IOError:
print "Icon cache not found, creating..."
iconCache = {}
def buildMenu():
global mainMenu
global ICON_PATHS
#Cache os.walk results for going through ICON_PATHS, for speed
global search_cache
search_cache = []
for i in ICON_PATHS:
search_cache += tuple(os.walk(i))
global folderIcon
folderIcon = findIcon(FOLDER_ICON_SEARCH_TERM)
refreshIcon = findIcon(REFRESH_ICON_SEARCH_TERM)
# Figure out how big menu icons should be
global PIXEL_SIZE
fontDesc = mainMenu.get_pango_context().get_font_description()
PIXEL_SIZE = int(math.floor(fontDesc.get_size() / pango.SCALE)) * PREF_ICON_SCALE
for menufolder in MENU_FOLDERS_APPEND:
mainMenu = makeMenuFromFolder(mainMenu, menufolder)
for menufolder in MENU_FOLDERS_ADD_SUBMENU:
newMenu = gtk.Menu()
newMenu = makeMenuFromFolder(newMenu, menufolder)
folderName = menufolder.split('/')[-1]
subMenuItem = gtk.ImageMenuItem(folderName)
if folderIcon != "":
setMenuItemIcon(subMenuItem, folderIcon)
subMenuItem.set_submenu(newMenu)
subMenuItem.show()
mainMenu.prepend(subMenuItem)
# Add refresh option
if SHOW_REFRESH_OPTION:
refreshMenuItem = gtk.ImageMenuItem("Refresh Menu")
if refreshIcon != "":
setMenuItemIcon(refreshMenuItem, refreshIcon)
def refreshMenu(widget, string):
print "Rebuilding menu...."
global iconCache, mainMenu
iconCache = {}
mainMenu = gtk.Menu()
buildMenu()
refreshMenuItem.connect("activate", refreshMenu, None)
refreshMenuItem.show()
mainMenu.append(refreshMenuItem)
try:
iconCacheFile = open("%s/.pymenu/.iconcache" % home, 'w')
pickle.dump(iconCache, iconCacheFile)
iconCacheFile.close()
except IOError:
print "Error: Couldn't save icon cache."
global mainMenu
mainMenu = gtk.Menu()
buildMenu()
pidPath = "%s/.pymenu/.pid" % home
try:
os.remove(pidPath)
except:
pass
pidPathFile = open(pidPath, 'w')
pidPathFile.write(str(os.getpid()))
pidPathFile.close()
global bShowingMenu
bShowingMenu = False
def showMenu(signum, stackframe):
global bShowingMenu
if bShowingMenu:
return
bShowingMenu = True
print "Entering loop.."
mainMenu.popup(None, None, None, 0, 0)
gtk.main()
bShowingMenu = False
# Wait for SIGUSR1 from the showmenu script
signal.signal(10, showMenu)
try:
while 1:
signal.pause()
finally:
os.remove(pidPath)
return 0
if __name__ == "__main__":
main()
|
showpymenu:
Code: |
kill -s 10 $(<~/.pymenu/.pid)
|
default ~/.pymenu/.pymenurc:
Code: |
ICON_PATHS = gtk.icon_theme_get_for_screen(screen).get_search_path()
PREF_ICON_SCALE = 3
FOLDER_ICON_SEARCH_TERM = "folder"
NO_ICON_SEARCH_TERM = "question"
MENU_FOLDERS_APPEND = ("%s/.pymenu/menu/" % home,)
MENU_FOLDERS_ADD_SUBMENU = ("%s/Desktop" % home,)
MODULE_FOLDER = "%s/.pymenu/modules/" % home
IGNORE_HIDDEN_FILES = True
SHOW_REFRESH_OPTION = True
REFRESH_ICON_SEARCH_TERM = "refresh"
|
Wait, what's this MODULE_FOLDER setting?
My original plan was to clean up the code a lot and make PyMenu easily extendable with modules. But spring break is just about over so I don't have the time right now. Feel free to submit patches though
Stuff I wish I had time to add:
-Support for .directory files
-Support for freedesktop.org menu format
-Module extendability
-Keyboard shortcut support (preferably autogenerated, like icon finding)
-Rewrite icon finding code to use find shell command for speed
-Dialogs to configure without command line
-Ebuild
Last edited by Jengu on Mon Mar 28, 2005 11:42 pm; edited 3 times in total |
|
Back to top |
|
|
Maedhros Bodhisattva
Joined: 14 Apr 2004 Posts: 5511 Location: Durham, UK
|
Posted: Sat Mar 26, 2005 10:42 am Post subject: |
|
|
Moved from Desktop Environments. _________________ No-one's more important than the earthworm. |
|
Back to top |
|
|
bob_111 Apprentice
Joined: 12 Oct 2004 Posts: 155
|
Posted: Sat Mar 26, 2005 11:48 am Post subject: |
|
|
Looks impressive! I just happen to LOVE fluxbox's menu!
- bob_111 |
|
Back to top |
|
|
c0mplex n00b
Joined: 21 Mar 2005 Posts: 4
|
|
Back to top |
|
|
Jengu Guru
Joined: 28 Oct 2004 Posts: 384
|
Posted: Sat Mar 26, 2005 8:13 pm Post subject: |
|
|
bob_111 wrote: | Looks impressive! I just happen to LOVE fluxbox's menu!
- bob_111 |
Hopefully, if I get the time to rewrite it, with modules people will be able to add fluxbox's menu options to PyMenu.
Also, if anyone wants to use their WMs old menu and this one, just bind them to separate clicks. For example, I use sawfish, but I still find myself needing stuff from the normal sawfish menu, so I have it bound to mouse button3 and PyMenu bound to mouse button2. |
|
Back to top |
|
|
TanNewt Retired Dev
Joined: 26 Mar 2004 Posts: 340 Location: Seattle, WA
|
Posted: Sat Mar 26, 2005 11:24 pm Post subject: |
|
|
Looks cool. I'm interested in having my program, denu, support this app. How is the structure stored? |
|
Back to top |
|
|
Jengu Guru
Joined: 28 Oct 2004 Posts: 384
|
Posted: Sun Mar 27, 2005 4:37 am Post subject: |
|
|
TanNewt wrote: | Looks cool. I'm interested in having my program, denu, support this app. How is the structure stored? |
PyMenu looks in ~/.pymenu/menu by default. The structure of the folders there determines what the menu will look like. For example if you have the folders:
~/.pymenu/menu/Games
~/.pymenu/menu/Adventure
~/.pymenu/menu/Internet
Then you'll see 3 subfolders in the menu, Games, Adventure, and Internet. There can also be folders within folders.
Also, the way you put items in a folder, is to make a symlink to the app you want inside the folder. See the example in the first post for adding Tuxracer to the menu to get the idea.
The actual menu used by the app is not stored per se, it's generated on the fly by what's inside ~/.pymenu/menu. But that's only the default, if you edit ~/.pymenu/.pymenurc you can make it represent any arbitrary folder you like. |
|
Back to top |
|
|
COiN3D Guru
Joined: 02 Aug 2004 Posts: 543 Location: Munich, Germany
|
Posted: Mon Mar 28, 2005 4:02 pm Post subject: |
|
|
Eh, I get this error message here when I try to start pymenu :
Quote: | Icon cache not found, creating...
Traceback (most recent call last):
File "pymenu/pymenu.py", line 300, in ?
main()
File "pymenu/pymenu.py", line 263, in main
buildMenu()
File "pymenu/pymenu.py", line 209, in buildMenu
for i in ICON_PATHS:
NameError: global name 'ICON_PATHS' is not defined
|
_________________ e17 documentation | Be free and use Jabber as your IM! | Combine IRC and IM |
|
Back to top |
|
|
Jengu Guru
Joined: 28 Oct 2004 Posts: 384
|
Posted: Mon Mar 28, 2005 11:42 pm Post subject: |
|
|
re-nice wrote: | Eh, I get this error message here when I try to start pymenu :
Quote: | Icon cache not found, creating...
Traceback (most recent call last):
File "pymenu/pymenu.py", line 300, in ?
main()
File "pymenu/pymenu.py", line 263, in main
buildMenu()
File "pymenu/pymenu.py", line 209, in buildMenu
for i in ICON_PATHS:
NameError: global name 'ICON_PATHS' is not defined
|
|
Whoops, slight bug, fixed, copy and paste from 1st post again. |
|
Back to top |
|
|
COiN3D Guru
Joined: 02 Aug 2004 Posts: 543 Location: Munich, Germany
|
|
Back to top |
|
|
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|