Saturday, April 12, 2008

nlauch

I didn't like the fact that Awesome lacked a launcher (or at least one I could find), so I wrote my own.

commands.py

The path-searhing and command-matching class

#!/usr/bin/python
import os
import os.path
from os import environ, execve, listdir
from os.path import basename
class Commands(list):
def __init__(self, path):
dirs = path.split(':')
for dir in dirs:
if os.path.isdir(dir):
self += [os.path.join(dir, x) for x in listdir(dir) ]
def match_str(self, st):
l = len(st)
return [ (basename(x), x) for x in self if basename(x)[0:l] == st ];
if __name__ == '__main__':
tst = Commands(environ['PATH'])
print tst.match_str('ls')

nlaunch

The main window, with an ErrorDialog class left over from another project.

#!/usr/bin/python
# coding=utf8
import pygtk
pygtk.require('2.0')
import gtk
import os
import math
from commands import Commands
keep_atop = True

class ErrorDialog(object):
def destroy(self, widget, data=None):
self.dialog.destroy()
def __init__(self, message, parent = None):
self.dialog = gtk.Dialog(title = 'Error', parent = parent, flags = gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_MODAL)
self.button = gtk.Button('Okay')
self.dialog.action_area.pack_start(self.button, True, True, 1)
self.button.connect('clicked', self.destroy, None)
self.button.show()
self.label = gtk.Label(message)
self.dialog.vbox.pack_start(self.label)
self.label.show()
def run(self):
self.dialog.run()

class LaunchWindow(object):
controls = {
65293 : 'enter',
65307 : 'escape',
65289 : 'tab',
65056 : 'sh-tab'
}
def enter(self, widget, event, data = None):
#print event.keyval
if event.keyval in self.controls.keys():
cmd = self.controls[event.keyval]
if cmd == 'enter':
selected = self.cmdview.get_selection()
if selected:
(model, iter) = selected.get_selected()
if model and iter:
name, path = model.get_value(iter, 0), model.get_value(iter, 1)
if os.fork():
gtk.main_quit()
else:
os.execv(path, (path,))
self.input.grab_focus()
self.input.select_region(-1, -1)
elif cmd == 'tab':
selected = self.cmdview.get_selection()
if selected:
(model, iter) = selected.get_selected()
if iter and model.iter_is_valid(iter):
iter = model.iter_next(iter)
if iter and model.iter_is_valid(iter):
self.cmdview.set_cursor_on_cell(model.get_path(iter))
self.cmdview.scroll_to_cell(model.get_path(iter))
else:
iter = model.get_iter_first()
self.cmdview.set_cursor_on_cell(model.get_path(iter))
self.cmdview.scroll_to_cell(model.get_path(iter))
self.input.grab_focus()
self.input.select_region(-1, -1)
elif cmd == 'sh-tab':
selected = self.cmdview.get_selection()
if selected:
(model, iter) = selected.get_selected()
if iter and model.iter_is_valid(iter):
path = list(model.get_path(iter))
if path[0] > 0:
path[0] -= 1
self.cmdview.set_cursor_on_cell(tuple(path))
self.cmdview.scroll_to_cell(model.get_path(iter))
else:
iter = model.get_iter_first()
while model.iter_next(iter):
iter = model.iter_next(iter)
self.cmdview.set_cursor_on_cell(model.get_path(iter))
self.cmdview.scroll_to_cell(model.get_path(iter))
self.input.grab_focus()
self.input.select_region(-1, -1)
elif cmd == 'escape':
gtk.main_quit()
return True
else:
return False
def changed(self, widget, data):
tmp = self.input.get_text()
if tmp != self.oldcmd:
self.cmds.clear()
if len(tmp):
cmdfound = self.commands.match_str(tmp)
cmdfound.sort(lambda x, y: cmp(x[0], y[0]))
for i in cmdfound:
self.cmds.append(i)
iter = self.cmds.get_iter_first()
if iter and self.cmds.iter_is_valid(iter):
self.cmdview.set_cursor_on_cell(self.cmds.get_path(iter))
self.oldcmd = tmp
def delete_event(self, widget, event, data=None):
return False
def destroy(self, widget, data=None):
gtk.main_quit()
def __init__(self):
self.commands = Commands(os.environ['PATH'])
self.oldcmd = ''
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect('delete_event', self.delete_event)
self.window.connect('destroy', self.destroy)
self.window.set_border_width(4)
self.window.set_title('Launchy')
self.window.set_geometry_hints(None, 300, 400, 400, 900, 400, 800)
self.window.set_position(gtk.WIN_POS_CENTER_ALWAYS)
self.window.set_keep_above(keep_atop)

self.topbox = gtk.VBox(True, 1)
self.topbox.set_homogeneous(False)

self.input = gtk.Entry()
self.input.connect('key-press-event', self.enter, None)
self.input.connect('changed', self.changed, None)
self.input.show()
self.topbox.pack_start(self.input, False, False, 1)

self.cmds = gtk.ListStore(str, str)

self.cmdview = gtk.TreeView(self.cmds)
render = gtk.CellRendererText();
self.cmdview.append_column(gtk.TreeViewColumn('Cmd', render, text = 0));
self.cmdview.append_column(gtk.TreeViewColumn('Fullpath', render, text = 1));

self.cmdwin = gtk.ScrolledWindow()
self.cmdwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
self.cmdwin.add(self.cmdview)
self.cmdview.show()

self.topbox.pack_start(self.cmdwin, True, True, 1)
self.cmdwin.show()



self.window.add(self.topbox)
self.topbox.show()

self.window.show()
self.input.grab_focus()
def main(self):
gtk.main()


window = LaunchWindow()
window.main()

This launcher remains on top, and centered. To use it, you start to type a command and the list will show all that start with the part you've typed. Hitting tab goes down the list, and shift-tab goes up the list. To run the command hit enter, and to quit hit escape.

No comments: