9c2fdc95367f825e1da6b2f1c9e1a5cbb3f564d2
[lxde/lxadmin.git] / src / openbox-keyconf / openbox-keyconf.py
1 #!/usr/bin/python
2 # -*- coding: UTF-8 -*-
3 #**************************************************************************
4 # *
5 # Copyright (c) 2010 by Elfriede Apfelkuchen *
6 # *
7 # This program is free software: you can redistribute it and/or modify *
8 # it under the terms of the GNU General Public License as published by *
9 # the Free Software Foundation, either version 3 of the License, or *
10 # (at your option) any later version. *
11 # *
12 # This program is distributed in the hope that it will be useful, *
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 # GNU General Public License for more details. *
16 # *
17 # You should have received a copy of the GNU General Public License *
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. *
19 # *
20 #**************************************************************************
21
22 helptext="""M = [Mod3]
23 W = [Windows]
24 S = [Shift]
25 A = [Alt]
26 C = [Control]
27
28 F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12
29 Escape
30 Return
31 Menu
32 Tab
33 Pause
34 space
35 Print
36 Caps_Lock
37 Scroll_Lock
38 Num_Lock
39 Insert
40 Delete
41 BackSpace
42 Home = [Pos1]
43 End = [End]
44 Prior = [PageUp]
45 Next = [PageDown]
46 asciicircum = [^]
47 less = [<]
48 greater = [>]
49 minus = [-]
50 plus = [+]
51 comma = [,]
52 period = [.]
53 numbersign = [#]
54 acute = [´]
55
56 Left,Right,Up,Down
57 _____________________
58
59 [Keypad/Numberblock]
60
61 KP_0, KP_1, KP_2, KP_3, KP_4, KP_5, KP_6, KP_7, KP_8, KP_9
62 KP_Divide = [/]
63 KP_Multiply = [*]
64 KP_Subtract = [-]
65 KP_Add = [+]
66 KP_Enter
67 KP_Begin = [5]
68 KP_Home = [7]
69 KP_End = [1]
70 KP_Prior = [9] [PageUp]
71 KP_Next = [3] [PageDown]
72 KP_Up = [8] [cursor up]
73 KP_Down = [2] [cursor down]
74 KP_Left = [4] [cursor left]
75 KP_Right = [6] [cursor right]
76
77 for more keycodes start "xev" in a terminal"""
78
79 import lxadmin.defs as defs
80 import lxadmin.detect_os as detect_os
81
82 import gettext
83
84 gettext.bindtextdomain('lxadmin', defs.LOCALE_DIR)
85 gettext.textdomain('lxadmin')
86 _ = gettext.gettext
87
88 from gettext import gettext as _
89
90 import os
91 import sys
92 import time
93 import string
94 import codecs
95 import pygtk
96 pygtk.require('2.0')
97 import gtk
98 import pango
99 import gobject
100 import gc
101
102 configfile=detect_os.get_openbox_config()
103 mybuffer=None
104 helpwindow=None
105
106 def create_help_view(data=None):
107 global helpwindow,mybuffer
108 if mybuffer!=None:
109 helpwindow.show()
110 helpwindow.present()
111 return
112 helpwindow = gtk.Window(gtk.WINDOW_TOPLEVEL)
113 helpwindow.set_title(_('Help: keyboard codes'))
114 helpwindow.set_size_request(600,400)
115 helpwindow.set_position(gtk.WIN_POS_CENTER_ALWAYS)
116 helpwindow.connect("destroy", destroy_help_view)
117 view=gtk.TextView()
118 view.set_editable(False)
119 view.set_cursor_visible(False)
120 view.set_pixels_above_lines(0)
121 view.set_pixels_below_lines(0)
122 view.set_pixels_inside_wrap(0)
123 view.set_wrap_mode(gtk.WRAP_NONE)
124 font_desc = pango.FontDescription('Monospace 12')
125 if font_desc:
126 view.modify_font(font_desc)
127 sw = gtk.ScrolledWindow()
128 sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
129 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
130 sw.add(view)
131 mybuffer=view.get_buffer()
132 mybuffer.set_text(helptext)
133 helpwindow.add(sw)
134 helpwindow.show_all()
135
136
137 def destroy_help_view(widget, data=None):
138 global helpwindow,mybuffer
139 helpwindow.destroy()
140 helpwindow=None
141 mybuffer=None
142
143
144
145 class Tool:
146
147 def delete_event(self, widget, event, data=None):
148 # If you return FALSE in the "delete_event" signal handler,
149 # GTK will emit the "destroy" signal. Returning TRUE means
150 # you don't want the window to be destroyed.
151 # This is useful for popping up 'are you sure you want to quit?'
152 # type dialogs.
153 # Change FALSE to TRUE and the main window will not be destroyed
154 # with a "delete_event".
155 windowsize=self.window.get_size()
156 windowpos=self.window.get_position()
157 filenamen=home+'/.openbox-keyconf'
158 f=file(filenamen,'w')
159 if f:
160 f.write('x=%i\n' % windowpos[0])
161 f.write('y=%i\n' % windowpos[1])
162 f.write('width=%i\n' % windowsize[0])
163 f.write('height=%i\n' % windowsize[1])
164 f.close()
165 return False
166
167
168 def destroy(self, widget, data=None):
169 #wird beim beenden ausgeführt
170 gtk.main_quit()
171
172
173 def goodbye(self,data=None):
174 #Cancel Button is clicked
175 if not self.delete_event(self.window,data):
176 gtk.main_quit()
177
178
179 def get_pathnumber(self,keycode):
180 # return pathnumber of a given keycode
181 for i in range(self.count_list):
182 if self.liststore[i][0]==keycode: break
183 if self.liststore[i][0]!=keycode: return -1
184 if self.liststore[i][3]=='add': return -1
185 return i
186
187
188 def save(self,data=None):
189 #Apply button is clicked
190 filename=configfile
191 text=''
192 try:
193 f=codecs.open(filename,'r','utf_8')
194 text=f.read()
195 config=text.split('\n')
196 f.close()
197 except:
198 print "can't read",filename
199 for i in range(self.count_list):
200 a=self.liststore[i][1]
201 b=self.liststore[i][2]
202 c=self.liststore[i][3]
203 d=self.liststore[i][0]
204 if c!='orginal':
205 if c=='delete': print c,d
206 else: print a,b
207 gc.collect()
208
209 f=codecs.open(filename,'w','utf_8')
210 found=False
211 cutting=False
212 for line in config:
213 if '<keyboard>' in line:
214 found=True
215 f.write(line+'\n')
216 continue
217 if '</keyboard>' in line:
218 found=False
219 # add keybinding code
220 for i in range(self.count_list):
221 s=self.liststore[i][3]
222 if s=='add':
223 f.write(' <keybind key="'+self.liststore[i][1]+'">\n')
224 a=self.liststore[i][2]
225 a=a.split('command=')
226 b=a[1].split(',)')
227 f.write(' <action name="Execute">\n')
228 f.write(' <command>'+b[0]+'</command>\n')
229 f.write(' </action>\n')
230 f.write(' </keybind>\n')
231 f.write(line+'\n')
232 continue
233 if not found:
234 f.write(line+'\n')
235 continue
236 if cutting:
237 if '</keybind>' in line: cutting=False
238 continue
239 if '<keybind ' in line:
240 lin=line.split('"')
241 p=self.get_pathnumber(lin[1])
242 if p==-1:
243 f.write(line+'\n')
244 continue
245 s=self.liststore[p][3]
246 if s=='orginal':
247 f.write(line+'\n')
248 continue
249 if s=='delete':
250 cutting=True
251 continue
252 if s=='name':
253 f.write(' <keybind key="'+self.liststore[p][1]+'">\n')
254 continue
255 if s=='execute':
256 f.write(' <keybind key="'+self.liststore[p][1]+'">\n')
257 a=self.liststore[p][2]
258 a=a.split('command=')
259 b=a[1].split(',)')
260 f.write(' <action name="Execute">\n')
261 f.write(' <command>'+b[0]+'</command>\n')
262 f.write(' </action>\n')
263 f.write(' </keybind>\n')
264 cutting=True
265 continue
266 f.write(line+'\n')
267 f.flush()
268 f.close()
269 os.system('openbox --reconfigure')
270 self.goodbye()
271
272
273 def mouse(self, widget, event, data=None):
274 if event.button==1:
275 # a keybinding in the treeview is clicked
276 pathinfo=self.treeview.get_path_at_pos(int(event.x),int(event.y))
277 if pathinfo==None: return False
278 pathnr=pathinfo[0][0]
279 name=self.liststore[pathnr][1]
280 action=self.liststore[pathnr][2]
281 self.lock=True
282 self.cb_exec.set_active(False)
283 self.execute.set_sensitive(0)
284 if 'command=' in action:
285 a1=action.split('command=')
286 a2=a1[1].split(',)')
287 self.execute.set_text(a2[0])
288 self.hbox.show()
289 else: self.hbox.hide()
290 self.path=pathnr
291 self.keys.set_text(name)
292 self.action.set_text(action)
293 self.lock=False
294
295
296 def row_activate(self, treeview, pathnr, column):
297 # a keybinding is selected by doubleclick or keyboard
298 name=self.liststore[pathnr][1]
299 action=self.liststore[pathnr][2]
300 self.lock=True
301 self.cb_exec.set_active(False)
302 self.execute.set_sensitive(0)
303 if 'command=' in action:
304 a1=action.split('command=')
305 a2=a1[1].split(',)')
306 self.execute.set_text(a2[0])
307 self.hbox.show()
308 else: self.hbox.hide()
309 self.path=pathnr
310 self.keys.set_text(name)
311 self.action.set_text(action)
312 self.lock=False
313
314
315 def keys_changed(self,data):
316 if self.lock: return
317 if self.path==-1: return
318 a=self.keys.get_text()
319 a=a.strip()
320 self.liststore[self.path][1]=a
321 b=self.liststore[self.path][3]
322 if b=='orginal': self.liststore[self.path][3]='name'
323
324
325 def execute_entry_changed(self,data):
326 if self.lock: return
327 if self.path==-1: return
328 a=self.execute.get_text()
329 a=a.strip()
330 b='Execute(command='+a+',)'
331 self.liststore[self.path][2]=b
332 self.action.set_text(b)
333
334
335 def add_new_keybinding(self,data=None):
336 self.count_list+=1
337 self.liststore.append(['F12','F12','Execute(command='+_("NameOfProgram")+',)','add'])
338
339
340 def delete_keybinding(self,data=None):
341 if self.lock: return
342 if self.path==-1: return
343 self.hbox.hide()
344 self.liststore[self.path][1]=""
345 self.liststore[self.path][2]=""
346 self.liststore[self.path][3]="delete"
347 self.action.set_text('')
348 self.keys.set_text('')
349
350
351 def toggle_execute_entry(self,data=None):
352 if self.lock: return
353 if self.path!=-1: b=self.liststore[self.path][3]
354 s=self.cb_exec.get_active()
355 if s:
356 self.execute.set_sensitive(1)
357 if self.path!=-1:
358 if (b=='orginal')or(b=='name'):
359 self.liststore[self.path][3]='execute'
360 else:
361 self.execute.set_sensitive(0)
362 if self.path!=-1:
363 if (b=='execute'):
364 self.liststore[self.path][3]='name'
365
366
367 def __init__(self):
368 namen=home+'/.openbox-keyconf'
369 if not os.access(namen,os.R_OK):
370 f=file(namen,'w')
371 f.write('x=0\n')
372 f.write('y=0\n')
373 f.write('width=800\n')
374 f.write('height=400\n')
375 f.close()
376 f=file(namen,'r')
377 s=f.readline()
378 while s !='':
379 s=s.replace('\n','')
380 z=s.split('=')
381 if z[0]=='x': self.pos_x=int(z[1])
382 if z[0]=='y': self.pos_y=int(z[1])
383 if z[0]=='width': self.width=int(z[1])
384 if z[0]=='height': self.height=int(z[1])
385 s=f.readline()
386 f.close()
387
388 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
389 self.window=window
390 vbox = gtk.VBox(False, 0)
391 window.set_border_width(5)
392 window.add(vbox)
393
394 # When the window is given the "delete_event" signal (this is given
395 # by the window manager, usually by the "close" option, or on the
396 # titlebar), we ask it to call the delete_event () function
397 # as defined above. The data passed to the callback
398 # function is NULL and is ignored in the callback function.
399 window.connect("delete_event", self.delete_event)
400
401 # Here we connect the "destroy" event to a signal handler.
402 # This event occurs when we call gtk_widget_destroy() on the window,
403 # or if we return FALSE in the "delete_event" callback.
404 window.connect("destroy", self.destroy)
405
406 # Sets the border width of the window.
407 window.set_title(_('openbox keyboard bindings'))
408 window.set_size_request(400,200)
409 window.resize(self.width, self.height)
410 window.move(self.pos_x,self.pos_y)
411
412 self.liststore=gtk.ListStore(str,str,str,str)
413 self.treeview=gtk.TreeView()
414 self.treeview.set_headers_visible(True)
415 self.treeview.set_model(self.liststore)
416 self.treeview.set_property('rules-hint',True)
417 self.treeview.set_property('enable-grid-lines',True)
418 self.treeview.connect('row-activated', self.row_activate)
419 self.treeview.set_events(self.treeview.get_events() | gtk.gdk.BUTTON_PRESS_MASK)
420 self.treeview.connect("button-press-event", self.mouse)
421 self.cell1 = gtk.CellRendererText()
422 self.cell2 = gtk.CellRendererText()
423 self.spalte1 = gtk.TreeViewColumn(_('Key'))
424 self.spalte2 = gtk.TreeViewColumn(_('Action'))
425 self.treeview.append_column(self.spalte1)
426 self.treeview.append_column(self.spalte2)
427 self.spalte1.pack_start(self.cell1,True)
428 self.spalte2.pack_start(self.cell2,True)
429 self.spalte1.set_attributes(self.cell1, text=1)
430 self.spalte2.set_attributes(self.cell2, text=2)
431 sw = gtk.ScrolledWindow()
432 sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
433 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
434 sw.add(self.treeview)
435 filename=configfile
436 self.count_list=0
437 try:
438 f=codecs.open(filename,'r','utf_8')
439 line=f.readline()
440 found=False
441 data='frog'
442 action=False
443 name=''
444 while True:
445 line=f.readline()
446 if not line: break
447 if '<keyboard>' in line:
448 found=True
449 continue
450 if '</keyboard>' in line:
451 found=False
452 if not found: continue
453 lin=line.replace('\n','')
454 if '<keybind ' in lin:
455 lin2=lin.split('"')
456 name=lin2[1]
457 action=False
458 data=''
459 continue
460 if '</keybind>' in lin:
461 if action: data+=')'
462 if name!='':
463 self.liststore.append([name,name,data,'orginal'])
464 self.count_list+=1
465 name=''
466 continue
467 if name!='':
468 if '</action>' in lin: continue
469 if '<action ' in lin:
470 if action: data+='), '
471 action=True
472 lin2=lin.split('"')
473 data=data+lin2[1]+'('
474 continue
475 if '<startupnotify>' in lin:
476 continue
477 if '</startupnotify>' in lin:
478 continue
479 if ('<' in lin) and ('>' in lin):
480 lin2=lin.split('<')
481 lin3=lin2[1].split('>')
482 data=data+lin3[0]+'='+lin3[1]+','
483 continue
484 f.close()
485 except:
486 pass
487 gc.collect()
488 self.lock=True
489 self.path=-1
490 hbox = gtk.HBox(False, 5)
491 self.keys=gtk.Entry(max=40)
492 self.keys.connect("changed",self.keys_changed)
493 self.action=gtk.Label('')
494 hbox.pack_start(self.keys,False,False,0)
495 hbox.pack_start(self.action,True,True,0)
496 vbox.pack_start(hbox, False,False, 0)
497 self.hbox = gtk.HBox(False, 5)
498 self.cb_exec=gtk.CheckButton(_("Edit command")+" =")
499 self.cb_exec.connect_object("toggled",self.toggle_execute_entry,None)
500 self.execute=gtk.Entry(max=0)
501 self.execute.connect("changed",self.execute_entry_changed)
502 self.hbox.pack_start(self.cb_exec,False,False,0)
503 self.hbox.pack_start(self.execute,True,True,0)
504 vbox.pack_start(self.hbox, False,False, 0)
505 vbox.pack_start(sw, True, True, 0)
506 hbox = gtk.HBox(False, 5)
507 button1=gtk.Button(stock='gtk-apply')
508 hiddenlabel=gtk.Label(' ')
509 button2=gtk.Button(stock='gtk-cancel')
510 button3=gtk.Button(stock='gtk-add')
511 button4=gtk.Button(stock='gtk-delete')
512 button5=gtk.Button(stock='gtk-help')
513 hbox.pack_start(button1,False,False,0)
514 hbox.pack_start(button3,False,False,0)
515 hbox.pack_start(button4,False,False,0)
516 hbox.pack_start(button5,False,False,0)
517 hbox.pack_start(hiddenlabel,True,True,0)
518 hbox.pack_start(button2,False,False,0)
519 button1.connect_object("clicked", self.save,None)
520 button2.connect_object("clicked", self.goodbye,None)
521 button3.connect_object("clicked", self.add_new_keybinding,None)
522 button4.connect_object("clicked", self.delete_keybinding,None)
523 button5.connect("clicked", create_help_view)
524 vbox.pack_end(hbox, False, False, 0)
525 window.show_all()
526 self.hbox.hide()
527
528
529 def main(self):
530 # All PyGTK applications must have a gtk.main(). Control ends here
531 # and waits for an event to occur (like a key press or mouse event).
532 gtk.main()
533
534
535
536 # If the program is run directly or passed as an argument to the python
537 # interpreter then create a HelloWorld instance and show it
538 if __name__ == "__main__":
539 home=os.getenv("HOME")
540 Elfriede = Tool()
541 Elfriede.main()