1 #!/usr/bin/env python2.5
4 # inspired in part by Auxlaunch
6 # We have a list of folders each of which contains a list of
7 # tasks, each of which can have a small number of options.
8 # We present the folders in one column and the tasks in another,
9 # with the options in buttons below.
11 # Program: Option is to run the program
12 # If the program is currently running, there is also an option to
14 # If there is a window that is believed to be attached to the program
15 # The kill option simply closes that window, and there is another option
17 # Window: Options are to raise or to kill the window.
21 # LATER Make list of windows-to-exclude (Panel 0) configurable
23 # more space around main words
25 # Design thoughts 28Dec2010
26 # Having separete 'internal' folders is bad
27 # And having to explicitly list lots of speed-dials for a speed-dial
29 # So I want two classes of folder:
30 # - one that has an explicit list of items, which can be programs or
31 # any internal function.
32 # - one that has an implicit list of items, which is generated by an
33 # internal function or a plug-in
34 # The implicit list could be an internal function which creates multiple
38 # tag,command line,window-name
41 # tag,module.internal(arguments)
42 # *,internal(arguments)
44 # The list-creating function would need a call-back to ask for the list
46 # possible function lists are:
50 # - wifi networks scanned (add/add-auto, possibly with password)
51 # - wifi networks known (connect, delete)
53 # - generic list that was asked for, thus effecting a three-level
54 # list. Maybe I want that anyway?
55 # Slight change - allow an internal function to provide a new list. This
56 # switches to a virtual folder showing that list. When the original folder
57 # is selected, we go back there...
66 from fingerscroll import FingerScroll
67 from subprocess import Popen, PIPE
68 from wmctrl import winlist
71 libc = ctypes.cdll.LoadLibrary("libc.so.6")
75 def __init__(self, path, on_event):
76 self.f = os.open(path, os.O_RDWR|os.O_NONBLOCK);
77 self.ev = gobject.io_add_watch(self.f, gobject.IO_IN, self.read)
78 self.on_event = on_event
83 str = os.read(self.f, 16)
89 (sec,usec,typ,code,value) = struct.unpack_from("IIHHI", str)
96 if self.down_count < 0:
98 self.on_event(self.down_count, typ, code, value, sec* 1000 + int(usec/1000))
104 fcntl.ioctl(self.f, EVIOC_GRAB, 1)
110 fcntl.ioctl(self.f, EVIOC_GRAB, 0)
116 read in a window list - present each as a Task
117 Allow registering tasks so that when a window appears, we connect it.
123 self.pid = os.getpid()
124 self.old_windows = None
126 self.winlist = winlist()
127 gobject.io_add_watch(self.winlist.fd, gobject.IO_IN, self.winlist.events)
128 self.winlist.on_change(self.refresh)
130 def add(self, winid, desk, pid, host, name):
131 self.windows[winid] = [name, pid]
132 if self.old_windows and winid in self.old_windows:
133 self.windows[winid] = [name, pid] + self.old_windows[winid][2:]
134 p = 'pid:%d' % int(pid)
135 #print "Looking for ", p
138 self.windows[winid].append(self.tasks[p])
143 self.windows[winid].append(self.tasks[n])
146 def remove_old(self):
147 for winid in self.old_windows:
148 if not winid in self.windows:
149 #print "removing",winid
150 for c in self.old_windows[winid][2:]:
152 self.old_windows = None
154 def register(self, pid, name, found):
156 p = 'pid:%d' % int(pid)
157 self.tasks[p] = found
160 self.tasks[n] = found
163 self.old_windows = self.windows
166 for w in self.winlist.winfo:
167 win = self.winlist.winfo[w]
168 if win.pid == self.pid:
170 self.add(win, 0, win.pid, '', win.name)
171 self.tasklist.append(WinTask(win, 0, win.pid, '', win.name))
176 if not self.tasks[k]():
194 Call to start a process, and get a call back when the process finishes.
198 def __init__(self, cmd, finished = None):
199 self.finished = finished
201 self._child_created = False
203 self.Popen = Popen(cmd, shell=True, close_fds = True)
204 self.pid = self.Popen.pid
205 self.returncode = None
206 ProcessList.append(self)
209 if self.Popen.poll() != None and self.finished:
210 self.returncode = self.Popen.returncode
211 self.finished(self.returncode)
213 window.folder_select(window.folder_num)
214 return self.returncode
218 l = range(len(ProcessList))
227 class Selector(gtk.DrawingArea):
228 def __init__(self, names = [], pos = 0, center=False):
229 gtk.DrawingArea.__init__(self)
231 self.on_select = None
232 self.do_center = center
235 self.width = self.height = 0
236 self.need_redraw = True
240 self.connect("expose-event", self.redraw)
241 self.connect("configure-event", self.reconfig)
243 self.connect("button_release_event", self.release)
244 self.connect("button_press_event", self.press)
245 self.set_events(gtk.gdk.EXPOSURE_MASK
246 | gtk.gdk.BUTTON_PRESS_MASK
247 | gtk.gdk.BUTTON_RELEASE_MASK
248 | gtk.gdk.STRUCTURE_MASK)
251 fd = self.get_pango_context().get_font_description()
252 fd.set_absolute_size(25 * pango.SCALE)
255 met = self.get_pango_context().get_metrics(fd)
256 self.lineheight = (met.get_ascent() + met.get_descent()) / pango.SCALE
257 self.lineheight *= 1.5
258 self.lineheight = int(self.lineheight)
266 def assign_colour(self, purpose, name):
267 self.collist[purpose] = name
269 def set_list(self, names, pos = 0):
276 def reconfig(self, w, ev):
277 alloc = w.get_allocation()
280 if alloc.width != self.width or alloc.height != self.height:
282 self.need_redraw = True
284 def add_col(self, sym, col):
285 c = gtk.gdk.color_parse(col)
286 gc = self.window.new_gc()
287 gc.set_foreground(self.get_colormap().alloc_color(c))
288 self.colours[sym] = gc
290 def redraw(self, w, ev):
291 if self.colours == None:
293 for p in self.collist:
294 self.add_col(p, self.collist[p])
295 self.bg = self.get_style().bg_gc[gtk.STATE_NORMAL]
300 self.window.draw_drawable(self.bg, self.pixbuf, 0, 0, 0, 0,
301 self.width, self.height)
305 self.need_redraw = False
306 if self.pixbuf == None:
307 alloc = self.get_allocation()
308 self.pixbuf = gtk.gdk.Pixmap(self.window, alloc.width, alloc.height)
309 self.width = alloc.width
310 self.height = alloc.height
311 self.pixbuf.draw_rectangle(self.bg, True, 0, 0,
312 self.width, self.height)
317 lines = int(self.height / self.lineheight)
318 entries = self.names()
319 # probably place current entry in the middle
320 top = self.pos - lines / 2
323 # but try not to leave blank space at the end
324 if top + lines > entries:
325 top = entries - lines
326 # but never have blank space at the top
332 for l in range(lines):
334 (type, name, other) = self.names(top+l)
335 #print type, name, other
340 layout = self.create_pango_layout("")
341 layout.set_markup(name)
342 (ink, (ex,ey,ew,eh)) = layout.get_pixel_extents()
344 # never truncate the start
347 height = self.lineheight
352 if l == self.pos - top:
353 self.pixbuf.draw_rectangle(self.colours['selected'], True,
355 self.width-4, height)
356 self.pixbuf.draw_layout(self.colours[type],
358 offset + (height-eh)/2,
360 offsets.append(offset + height)
361 self.offsets = offsets
365 self.need_redraw = True
367 # must return False so that timers don't refire
370 def press(self,w,ev):
373 row = len(self.offsets)
374 for i in range(len(self.offsets)):
375 if ev.y < self.offsets[i]:
379 if self.pos != row + self.top:
380 self.pos = row + self.top
382 self.on_select(self.pos)
386 def release(self,w,ev):
391 """Identifies a particular task that is a member of a folder.
392 If the task is running, the PID is tracked here too.
395 def __init__(self, name):
399 #return ["Yes", "No"]
402 def copyconfig(self, orig):
405 def setgroup(self, group, pos):
410 def refresh(self, select):
415 print "REFRESH", window.folder_num, window.folder_pos[window.folder_num], self.group, self.pos
416 if window.folder_num != self.group:
417 window.folder_select(self.group)
418 window.task_select(self.pos)
419 elif window.folder_pos[window.folder_num] != self.pos:
420 window.task_select(self.pos)
421 window.active = False
424 gobject.timeout_add(300, window.refresh)
426 def set_tasks(self, list, pos):
428 window.set_tasks(list, pos)
432 Task subtype for handling normal commands
434 def __init__(self, line):
435 fields = line.split(',')
437 Task.__init__(self, name)
446 self.winname = fields[2].strip()
449 def gotit(winid = None):
454 windowlist.register(None, self.winname, gotit)
458 return ["Raise", "Close"]
465 def event(self, num):
469 self.win_id.raise_win()
475 self.win_id.close_win()
479 os.kill(self.job.pid, 15)
481 gobject.timeout_add(400,
482 lambda *a :(JobCtrl(None).poll_all(),window.refresh()))
485 self.job = JobCtrl(self.cmd, self.finished)
486 windowlist.register(self.job.pid, self.winname, self.winfound)
487 print "registered ", self.job.pid, " for ", self.name
491 def winfound(self, winid = None):
493 #print "task",self.name,"now has winid", winid
495 return self.job != None
497 def finished(self, retcode):
503 if self.win_id or self.job:
509 return (typ, self.name, self)
511 def copyconfig(self, orig):
513 self.winname = orig.winname
518 This is a fake task that simply holds the name of a window
519 not to show -- i.e. the parsed info from the config file
521 def __init__(self, line):
526 Task subtype for handling Windows that have been found
528 def __init__(self, winid, desk, pid, host, name):
529 Task.__init__(self, name)
535 return ["Raise", "Close", "Kill"]
537 return ["Raise", "Close"]
539 def event(self, num):
541 self.win_id.raise_win()
544 self.win_id.close_win()
546 os.kill(self.pid, 15)
550 return ('window', self.name, self)
553 class InternTask(Task):
555 An InternTask runs an internal command to choose text to display
556 It may be inactive, so options returns an empty list.
557 If the name contains a dot, we import the module and just call the
558 named function. If not, we run internal_$name.
559 The function takes two argument. A string:
560 "_name" - return (type, name, self)
561 "_options" - return list of button strings
562 button-name - take appropriate action
563 and this InternTask object. The 'state' array may be manipulated.
566 def __init__(self, line, tag = None):
578 exec "import " + p[0]
584 self.fn = eval("internal_" + line)
588 self.current_input = current_input
590 t,n = 'cmd', self.tag
592 t,n = self.fn("_name", self)
597 self.current_input = current_input
598 self.optionlist = self.fn("_options", self)
599 return self.optionlist
601 def event(self, num):
603 self.current_input = current_input
604 return self.fn(num, self)
607 """Holds a number of folders, each with a number of tasks
609 Tasks(filename) loads from a file (or directory???)
610 reload() re-reads the file and makes changes as needed
611 folders() - array of folder names
612 tasks(folder) - array of Task objects
615 def __init__(self, path):
617 self.tasks = {}; self.gtype = {}
621 self.orig_tasks = self.tasks
622 self.orig_types = self.gtype
630 f = ["[Built-in]", "Exit,(quit),"]
636 if l[0] == '"' or l[0] == '[':
646 if group_type not in ['cmd','wm']:
649 if group_type == 'cmd':
650 words = l.split(',',1)
651 word1 = words[1].strip(' ')
652 arg0 = word1.split(' ',1)[0]
654 t = InternTask(word1.strip('()'), words[0])
656 t = InternTask(word1, words[0])
659 elif group_type == 'wm':
663 if group not in self.tasks:
664 self.folders.append(group)
665 self.tasks[group] = []
666 self.gtype[group] = group_type
667 if group in self.orig_tasks and \
668 self.orig_types[group] == self.gtype[group]:
669 for ot in self.orig_tasks[group]:
670 if t.name == ot.name:
674 self.tasks[group].append(t)
675 t.setgroup(len(self.folders)-1, len(self.tasks[group])-1)
676 self.orig_tasks = None
677 self.orig_types = None
680 def folder_list(self):
681 return self.get_folder
682 def get_folder(self, ind = -1):
684 return len(self.folders)
685 elif ind < len(self.folders):
686 return ("folder", self.folders[ind], None)
688 return ("end", "end", None)
690 def task_list(self, num):
692 gtype = self.gtype[self.folders[num]]
694 return lambda ind = -1 : self.get_task(ind, windowlist.reload())
696 return lambda ind = -1 : self.get_task(ind, self.tasks[self.folders[num]])
697 def get_task(self, ind, tl):
703 return tl[ind].info()
705 return ("end", None, None)
707 cliptargets = [ (gtk.gdk.SELECTION_TYPE_STRING, 0, 0) ]
708 class LaunchWindow(gtk.Window):
710 A window containing a list of folders and a list of entries in the folder
711 Along the bottom are per-entry buttons.
712 When a folder is selected, the entires are updated. The last-used entry
713 in the folder is selected.
714 When an entry is selected, the buttons are updated.
715 When a button is pressed, its action is effected.
717 One type of action can produce text output. In this case a replacement
718 display pane is used that is finger-scrollable. It comes with a button
719 to revert to main display
722 def __init__(self, tasks):
723 gtk.Window.__init__(self)
724 self.connect("destroy", self.close_application)
725 self.set_title("Launcher")
731 self.clip = gtk.Clipboard(selection='PRIMARY')
735 self.folder_list = tasks.folder_list()
736 self.folder_pos = self.folder_list() * [0]
737 self.col1.set_list(self.folder_list)
738 self.set_default_size(480, 640)
750 v.set_property('can-focus', True)
752 v.add_events(gtk.gdk.KEY_PRESS_MASK)
753 v.connect('key_press_event', self.keystroke)
757 v.pack_start(e, expand=False);
759 e.connect('changed', self.entry_changed)
760 e.connect('backspace', self.entry_changed)
763 self.entry.set_text("")
766 v.pack_start(h, expand=True, fill=True)
769 self.col1 = Selector()
770 self.col2 = Selector(center=True)
773 h.pack_start(self.col1)
774 h.pack_end(self.col2)
776 self.col1.on_select = self.folder_select
777 self.col2.on_select = self.task_select
779 self.col1.assign_colour('folder','darkblue')
780 self.col1.assign_colour('selected','white')
782 self.col2.assign_colour('active','blue')
783 self.col2.assign_colour('cmd','black')
784 self.col2.assign_colour('selected','white')
785 self.col2.assign_colour('window','blue')
789 v.pack_end(h, expand=False)
790 h.set_size_request(-1,80)
793 ctx = self.get_pango_context()
794 fd = ctx.get_font_description()
795 fd.set_absolute_size(30*pango.SCALE)
798 self.button_names = []
801 b.child.modify_font(fd)
802 b.set_property('can-focus', False)
804 b.connect('clicked', self.button_pressed, bn)
805 self.buttons.append(b)
807 fd.set_absolute_size(40*pango.SCALE)
808 self.entry.modify_font(fd)
812 # Now create alternate view with FingerScroll and a button
815 f = FingerScroll(); f.show()
817 self.text_buffer = f.get_buffer()
818 b = gtk.Button("Done")
819 fd.set_absolute_size(30*pango.SCALE)
820 b.child.modify_font(fd)
821 b.set_property('can-focus', False)
822 b.connect('clicked', self.text_done)
823 v.pack_end(b, expand=False)
825 fd = pango.FontDescription('Monospace 10')
826 fd.set_absolute_size(15*pango.SCALE)
832 def text_done(self,widget):
833 self.text_view.hide()
834 self.main_view.show()
836 def close_application(self, widget):
840 cl = self.clip.wait_for_text()
841 if cl == self.cliptext:
847 while len(cl) > 0 and ord(cl[-1]) >= 127:
849 if re.match('^ *\+?[-0-9 ()\n]*$', cl):
850 # looks like a phone number. Remove rubbish.
851 cl = cl.replace('-', '')
852 cl = cl.replace('(', '')
853 cl = cl.replace(')', '')
854 cl = cl.replace(' ', '')
855 cl = cl.replace('\n', '')
857 if len(self.entry.get_text()) == 0:
858 self.entry.set_text(cl)
860 self.entry.insert_text(cl, self.entry.get_position())
861 self.entry.set_position(self.entry.get_position() +
865 def entry_changed(self, widget):
866 if not widget.get_text():
871 if not widget.is_focus():
874 current_input = widget.get_text()
876 self.task_select(self.col2.pos)
878 def keystroke(self, widget, ev):
879 if not widget.is_focus():
882 # some weird control key - or AUX
885 self.entry.grab_focus()
888 def button_pressed(self, widget, num):
889 hide = self.task.event(num)
890 self.folder_select(self.folder_num)
894 def set_tasks(self, lister, posn, folder_num = -1):
895 self.folder_num = folder_num
896 self.get_task = lister
897 self.col2.set_list(lister, posn)
899 def folder_select(self, folder_num):
904 if folder_num < 0 or folder_num >= self.folder_list():
906 self.col1.pos = folder_num
908 self.set_tasks(self.tasks.task_list(folder_num),
909 self.folder_pos[folder_num],
912 def task_select(self, task_num):
913 if task_num >= self.get_task():
915 if self.folder_num >= 0:
916 self.folder_pos[self.folder_num] = task_num
917 (typ, name, self.task) = self.get_task(task_num)
918 if self.task == None:
919 print "folder %d task %d" %(self.folder_num, task_num)
920 # FIXME how does this happen? what do I do with buttons?
921 # This can happen if we remember and old task number
922 # which (For window list) no longer exists.
925 options = self.task.options()
926 while len(options) < len(self.button_names):
927 self.button_names.pop()
928 self.buttons[len(self.button_names)].hide()
929 for i in range(len(self.button_names)):
930 if options[i] != self.button_names[i]:
931 self.button_names[i] = options[i]
932 self.buttons[i].child.set_text(self.button_names[i])
933 while len(options) > len(self.button_names):
934 p = len(self.button_names)
935 self.button_names.append(options[p])
936 self.buttons[p].child.set_text(self.button_names[p])
937 self.buttons[p].show()
944 gobject.idle_add(self.checkclip)
946 self.col1.set_list(self.folder_list, 0)
950 self.folder_select(self.folder_num)
953 class LaunchIcon(gtk.StatusIcon):
955 gtk.StatusIcon.__init__(self)
956 self.set_from_stock(gtk.STOCK_EXECUTE)
957 self.connect('activate', activate)
963 JobCtrl(None).poll_all()
967 def aux_activate(cnt, type, code, value, msec):
971 if code != 169 and code != 116:
972 # not the AUX key and not the power key
978 #print "down_at", down_at
981 #print "up at", msec, down_at
982 if msec - down_at > 250:
983 # too long - someone else wants this press
988 def tap_check(cnt, type, code, value, msec):
999 # hack - only require one tap
1002 if msec - last_tap < 200:
1004 last_tap = msec - 400
1007 window.entry.delete_text(0,-1)
1012 def internal_quit(arg, obj):
1015 return ('cmd', 'Exit')
1016 if arg == "_options":
1019 window.close_application(None)
1021 def internal_time(arg, obj):
1024 if 'next' not in obj.state:
1025 obj.state['next'] = 0
1027 next_minute = int(now/60)+1
1028 if next_minute != obj.state['next']:
1029 gobject.timeout_add(int (((next_minute*60) - now) * 1000),
1030 lambda *a :(window.refresh()))
1031 obj.state['next'] = next_minute
1032 tm = time.strftime("%H:%M", time.localtime(now))
1033 return ('cmd', '<span size="15000">'+tm+'</span>')
1034 if arg == "_options":
1035 return ['Set Timezone', 'wifi']
1037 window.set_tasks(tasklist_tz(), 0)
1039 window.set_tasks(tasklist_wifi(), 0)
1042 def internal_date(arg, obj):
1043 if len(obj.state) == 0:
1044 obj.state['cmd'] = CmdTask('cal,/usr/local/bin/cal,cal')
1046 # no need to schedule a timeout as the 1-minute tick will do it.
1048 #tm = time.strftime('<span size="5000">%d-%b-%Y</span>', time.localtime(time.time()))
1049 tm = time.strftime('%d-%b-%Y', time.localtime(time.time()))
1051 if arg == '_options':
1052 return obj.state['cmd'].options()
1053 return obj.state['cmd'].event(arg)
1055 def internal_tz(zone):
1056 return lambda arg, obj: _internal_tz(arg, obj, zone)
1058 def _internal_tz(arg, obj, zone):
1060 if 'TZ' in os.environ:
1061 TZ = os.environ['TZ']
1064 os.environ['TZ'] = zone
1067 tm = time.strftime("%d-%b-%Y %H:%M", time.localtime(now))
1070 os.environ['TZ'] = TZ
1072 del(os.environ['TZ'])
1073 return ('cmd', '<span size="10000">'+tm+"\n"+zone+'</span>')
1074 if arg == '_options':
1078 def internal_echo(arg, obj):
1080 global current_input
1082 a = a.replace('&','&')
1083 a = a.replace('<','<')
1084 a = a.replace('>','>')
1086 if arg == '_options':
1090 def internal_calc(arg, obj):
1092 global current_input
1094 n = eval(current_input)
1101 a = a.replace('&','&')
1102 a = a.replace('<','<')
1103 a = a.replace('>','>')
1105 if arg == '_options':
1109 def internal_rotate(arg, obj):
1111 return ('cmd', 'rotate')
1112 if arg == '_options':
1113 return ['normal','left']
1115 Popen(['xrandr', '-o', 'normal'], shell=False, close_fds = True)
1118 Popen(['xrandr', '-o', 'left'], shell=False, close_fds = True)
1121 def internal_text(cmd):
1122 return lambda arg, obj : _internal_text(arg, cmd, obj)
1124 def readsome(f, dir, p, b):
1126 b.insert(b.get_end_iter(), l)
1131 def child_done(pid, status, arg):
1133 fcntl.fcntl(p.stdout, fcntl.F_SETFL, 0)
1134 while readsome(p.stdout, None, p, b):
1136 gobject.source_remove(w)
1137 b.insert(b.get_end_iter(), "-----//-----")
1140 def _internal_text(arg, cmd, obj):
1143 if arg == '_options':
1147 b = window.text_buffer
1148 b.delete(b.get_start_iter(),b.get_end_iter())
1149 p = Popen(cmd, shell=True, close_fds = True, stdout=PIPE)
1150 flg = fcntl.fcntl(p.stdout, fcntl.F_GETFL, 0)
1151 fcntl.fcntl(p.stdout, fcntl.F_SETFL, flg | os.O_NONBLOCK)
1152 watch = gobject.io_add_watch(p.stdout, gobject.IO_IN, readsome, p, b)
1153 gobject.child_watch_add(p.pid, child_done, ( p, b, watch ))
1154 window.text_view.show()
1155 window.main_view.hide()
1157 def internal_file(fname):
1158 # return a function to be used as an internal_* function
1159 # that reads the content of a file
1160 return lambda arg, obj : _internal_file(arg, fname, obj)
1162 def _internal_file(arg, fname, obj):
1163 if 'dndir' not in obj.state:
1165 d = dnotify.dir(os.path.dirname(fname))
1166 obj.state['dndir'] = d
1167 obj.state['pending'] = False
1169 obj.state['pending'] = True
1170 obj.state['value'] = '--'
1172 if not obj.state['pending']:
1174 obj.state['dndir'].watch(os.path.basename(fname),
1175 lambda f : _internal_file_notify(f, obj))
1176 obj.state['pending'] = True
1179 l = f.readline().strip()
1181 obj.state['value'] = l
1183 obj.state['value'] = '--'
1186 l = obj.state['value']
1188 if arg == '_options':
1192 def _internal_file_notify(f, obj):
1194 obj.state['pending'] = False
1196 # wait a while for changes to the file to stablise
1197 gobject.timeout_add(300, window.refresh)
1199 def get_task(ind, tl):
1205 return tl[ind].info()
1207 return ("end", None, None)
1209 def internal_windows(arg, obj):
1211 return "Window List"
1212 if arg == '_options':
1215 global windowlist, window
1216 window.set_tasks(lambda ind = -1 : get_task(ind, windowlist.reload()), 0)
1220 self.last_refresh = 0
1223 self.refresh_time = 60
1224 self.callback = None
1225 self.refresh_task = 'refresh_list'
1226 self.name = 'Generic List'
1228 def __call__(self, ind = -1):
1230 if self.last_refresh + self.refresh_time < time.time():
1231 self.last_refresh = time.time()
1232 self.start_refresh()
1233 return len(self.list) + 1
1235 # The first entry is a simple refresh task
1236 t = InternTask(self.refresh_task, self.name)
1237 t.state['list'] = self
1240 if ind <= len(self.list):
1241 return tasklist_task(self, ind-1).info()
1242 return ("end", None, None)
1244 def refresh_cmd(self, cmd):
1245 p = Popen(cmd, shell=True, close_fds=True, stdout=PIPE)
1246 fcntl.fcntl(p.stdout, fcntl.F_SETFL, os.O_NONBLOCK)
1247 watch = gobject.io_add_watch(p.stdout, gobject.IO_IN, self.readsome, p)
1248 gobject.child_watch_add(p.pid, self.child_done, (p, watch))
1250 def readsome(self, f, dir, p):
1253 self.readline(l.strip())
1257 def child_done(self, pid, status, arg):
1259 fcntl.fcntl(p.stdout, fcntl.F_SETFL, 0)
1260 while self.readsome(p.stdout, None, p):
1262 gobject.source_remove(watch)
1265 self.list = self.newlist
1268 self.callback.refresh(False)
1270 class tasklist_task(Task):
1272 A tasklist_task calls into the tasklist to get info required.
1274 def __init__(self, tasklist, entry):
1275 self.list = tasklist
1278 t,n = self.list.info(self.entry)
1281 return self.list.options(self.entry)
1282 def event(self, num):
1283 return self.list.event(self.entry, num)
1285 class tasklist_tz(tasklist):
1286 # Synthesise a list of tasks to represent selection a time zone
1287 # ind==-1 must return the length of the list, other values return tasks
1288 # We can call window.set_folder (or something) to get the list refreshed
1289 # First item is 'TimeZone' with a button to refresh the list
1290 # other items are best 10 timezones.
1291 # We refresh the list when the refresh button is pressed, or when
1292 # len is requested move than 10 minutes after the last refresh.
1295 tasklist.__init__(self)
1296 self.refresh_time = 10*60
1297 self.name = 'TimeZone'
1299 def start_refresh(self):
1300 self.refresh_cmd("/root/gpstz --list")
1302 def readline(self, l):
1306 self.newlist.append(words[1])
1309 return 'cmd', self.list[n]
1310 def options(self, n):
1311 return ['Set Timezone']
1312 def event(self, n, ev):
1314 Popen("/root/gpstz "+ self.list[n], shell=True, close_fds=True)
1318 def internal_refresh_list(arg, obj):
1320 return "Refresh List"
1321 if arg == '_options':
1324 t = obj.state['list']
1329 class tasklist_wifi(tasklist):
1331 tasklist.__init__(self)
1332 self.refresh_time = 60
1333 self.name = 'Wifi Networks'
1335 def start_refresh(self):
1339 self.refresh_cmd("iwlist eth0 scanning")
1341 def readline(self, l):
1343 self.read_finished()
1349 self.read_finished()
1355 self.essid = id.strip('"')
1357 if w[0] == 'Encryption key':
1358 self.encrypt = (w[1] == 'on')
1361 if w[0] == 'Quality':
1365 def read_finished(self):
1366 if self.essid == None:
1370 if self.quality == None:
1375 self.newlist.append((self.essid, self.quality, c))
1378 essid, quality, c = self.list[n]
1379 return 'cmd', ('<span size="15000">%s</span>\n<span size="10000">%s%s</span>'
1380 % (essid, quality, c))
1381 def options(self, n):
1382 return ['Configure Wifi']
1383 def event(self, n, ev):
1384 print "please configure %s"% self.list[n][0]
1387 global window, windowlist, tasks
1388 global current_input
1390 windowlist = WinList()
1391 tasks = Tasks(os.getenv('HOME') + "/.launchrc")
1393 window = LaunchWindow(tasks)
1395 aux = EvDev("/dev/input/event4", aux_activate)
1396 # may aux button broke so ...
1397 EvDev("/dev/input/event0", aux_activate)
1401 EvDev("/dev/input/event3", tap_check)
1405 gtk.settings_get_default().set_long_property("gtk-cursor-blink", 0, "main")
1409 if __name__ == '__main__':
1410 sys.exit(main(sys.argv))