Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
New app! PyMenu to replace ugly wm menus
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index Unsupported Software
View previous topic :: View next topic  
Author Message
Jengu
Guru
Guru


Joined: 28 Oct 2004
Posts: 384

PostPosted: Sat Mar 26, 2005 10:05 am    Post subject: New app! PyMenu to replace ugly wm menus Reply with quote

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
View user's profile Send private message
Maedhros
Bodhisattva
Bodhisattva


Joined: 14 Apr 2004
Posts: 5511
Location: Durham, UK

PostPosted: Sat Mar 26, 2005 10:42 am    Post subject: Reply with quote

Moved from Desktop Environments.
_________________
No-one's more important than the earthworm.
Back to top
View user's profile Send private message
bob_111
Apprentice
Apprentice


Joined: 12 Oct 2004
Posts: 155

PostPosted: Sat Mar 26, 2005 11:48 am    Post subject: Reply with quote

Looks impressive! I just happen to LOVE fluxbox's menu!

- bob_111
Back to top
View user's profile Send private message
c0mplex
n00b
n00b


Joined: 21 Mar 2005
Posts: 4

PostPosted: Sat Mar 26, 2005 1:47 pm    Post subject: Reply Reply with quote

WOW! Great menues!
_________________
Mozilla FireFox RULEZ!!!!!!!!!
Knowledge Management
Back to top
View user's profile Send private message
Jengu
Guru
Guru


Joined: 28 Oct 2004
Posts: 384

PostPosted: Sat Mar 26, 2005 8:13 pm    Post subject: Reply with quote

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
View user's profile Send private message
TanNewt
Retired Dev
Retired Dev


Joined: 26 Mar 2004
Posts: 340
Location: Seattle, WA

PostPosted: Sat Mar 26, 2005 11:24 pm    Post subject: Reply with quote

Looks cool. I'm interested in having my program, denu, support this app. How is the structure stored?
Back to top
View user's profile Send private message
Jengu
Guru
Guru


Joined: 28 Oct 2004
Posts: 384

PostPosted: Sun Mar 27, 2005 4:37 am    Post subject: Reply with quote

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
View user's profile Send private message
COiN3D
Guru
Guru


Joined: 02 Aug 2004
Posts: 543
Location: Munich, Germany

PostPosted: Mon Mar 28, 2005 4:02 pm    Post subject: Reply with quote

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
View user's profile Send private message
Jengu
Guru
Guru


Joined: 28 Oct 2004
Posts: 384

PostPosted: Mon Mar 28, 2005 11:42 pm    Post subject: Reply with quote

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
View user's profile Send private message
COiN3D
Guru
Guru


Joined: 02 Aug 2004
Posts: 543
Location: Munich, Germany

PostPosted: Wed Mar 30, 2005 4:22 pm    Post subject: Reply with quote

I'm still getting the same error :(
_________________
e17 documentation | Be free and use Jabber as your IM! | Combine IRC and IM
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Unsupported Software All times are GMT
Page 1 of 1

 
Jump to:  
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