openbox-keyconf: use tempory file to write config, and move it when it's ready
[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 shutil
93 import tempfile
94 import time
95 import string
96 import codecs
97 import pygtk
98 pygtk.require('2.0')
99 import gtk
100 import pango
101 import gobject
102 import gc
103
104 configfile=detect_os.get_openbox_config()
105 mybuffer=None
106 helpwindow=None
107
108 def create_help_view(data=None):
109 global helpwindow,mybuffer
110 if mybuffer!=None:
111 helpwindow.show()
112 helpwindow.present()
113 return
114 helpwindow = gtk.Window(gtk.WINDOW_TOPLEVEL)
115 helpwindow.set_title(_('Help: keyboard codes'))
116 helpwindow.set_size_request(600,400)
117 helpwindow.set_position(gtk.WIN_POS_CENTER_ALWAYS)
118 helpwindow.connect("destroy", destroy_help_view)
119 view=gtk.TextView()
120 view.set_editable(False)
121 view.set_cursor_visible(False)
122 view.set_pixels_above_lines(0)
123 view.set_pixels_below_lines(0)
124 view.set_pixels_inside_wrap(0)
125 view.set_wrap_mode(gtk.WRAP_NONE)
126 font_desc = pango.FontDescription('Monospace 12')
127 if font_desc:
128 view.modify_font(font_desc)
129 sw = gtk.ScrolledWindow()
130 sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
131 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
132 sw.add(view)
133 mybuffer=view.get_buffer()
134 mybuffer.set_text(helptext)
135 helpwindow.add(sw)
136 helpwindow.show_all()
137
138
139 def destroy_help_view(widget, data=None):
140 global helpwindow,mybuffer
141 helpwindow.destroy()
142 helpwindow=None
143 mybuffer=None
144
145
146
147 class Tool:
148
149 def delete_event(self, widget, event, data=None):
150 # If you return FALSE in the "delete_event" signal handler,
151 # GTK will emit the "destroy" signal. Returning TRUE means
152 # you don't want the window to be destroyed.
153 # This is useful for popping up 'are you sure you want to quit?'
154 # type dialogs.
155 # Change FALSE to TRUE and the main window will not be destroyed
156 # with a "delete_event".
157 windowsize=self.window.get_size()
158 windowpos=self.window.get_position()
159 filenamen=home+'/.openbox-keyconf'
160 f=file(filenamen,'w')
161 if f:
162 f.write('x=%i\n' % windowpos[0])
163 f.write('y=%i\n' % windowpos[1])
164 f.write('width=%i\n' % windowsize[0])
165 f.write('height=%i\n' % windowsize[1])
166 f.close()
167 return False
168
169
170 def destroy(self, widget, data=None):
171 #wird beim beenden ausgeführt
172 gtk.main_quit()
173
174
175 def goodbye(self,data=None):
176 #Cancel Button is clicked
177 if not self.delete_event(self.window,data):
178 gtk.main_quit()
179
180
181 def get_pathnumber(self,keycode):
182 # return pathnumber of a given keycode
183 for i in range(self.count_list):
184 if self.liststore[i][0]==keycode: break
185 if self.liststore[i][0]!=keycode: return -1
186 if self.liststore[i][3]=='add': return -1
187 return i
188
189
190 def save(self,data=None):
191 #Apply button is clicked
192 filename=configfile
193 text=''
194 try:
195 f=codecs.open(filename,'r','utf_8')
196 text=f.read()
197 config=text.split('\n')
198 f.close()
199 except:
200 print "can't read",filename
201 for i in range(self.count_list):
202 a=self.liststore[i][1]
203 b=self.liststore[i][2]
204 c=self.liststore[i][3]
205 d=self.liststore[i][0]
206 if c!='orginal':
207 if c=='delete': print c,d
208 else: print a,b
209 gc.collect()
210
211 f = tempfile.NamedTemporaryFile()
212 found=False
213 cutting=False
214 for line in config:
215 if '<keyboard>' in line:
216 found=True
217 f.write(line+'\n')
218 continue
219 if '</keyboard>' in line:
220 found=False
221 # add keybinding code
222 for i in range(self.count_list):
223 s=self.liststore[i][3]
224 if s=='add':
225 f.write(' <keybind key="'+self.liststore[i][1]+'">\n')
226 a=self.liststore[i][2]
227 a=a.split('command=')
228 b=a[1].split(',)')
229 f.write(' <action name="Execute">\n')
230 f.write(' <command>'+b[0]+'</command>\n')
231 f.write(' </action>\n')
232 f.write(' </keybind>\n')
233 f.write(line+'\n')
234 continue
235 if not found:
236 f.write(line+'\n')
237 continue
238 if cutting:
239 if '</keybind>' in line: cutting=False
240 continue
241 if '<keybind ' in line:
242 lin=line.split('"')
243 p=self.get_pathnumber(lin[1])
244 if p==-1:
245 f.write(line+'\n')
246 continue
247 s=self.liststore[p][3]
248 if s=='orginal':
249 f.write(line+'\n')
250 continue
251 if s=='delete':
252 cutting=True
253 continue
254 if s=='name':
255 f.write(' <keybind key="'+self.liststore[p][1]+'">\n')
256 continue
257 if s=='execute':
258 f.write(' <keybind key="'+self.liststore[p][1]+'">\n')
259 a=self.liststore[p][2]
260 a=a.split('command=')
261 b=a[1].split(',)')
262 f.write(' <action name="Execute">\n')
263 f.write(' <command>'+b[0]+'</command>\n')
264 f.write(' </action>\n')
265 f.write(' </keybind>\n')
266 cutting=True
267 continue
268 f.write(line+'\n')
269 shutil.copyfile(f.name, filename)
270 f.flush()
271 f.close()
272 os.system('openbox --reconfigure')
273 self.goodbye()
274
275
276 def mouse(self, widget, event, data=None):
277 if event.button==1:
278 # a keybinding in the treeview is clicked
279 pathinfo=self.treeview.get_path_at_pos(int(event.x),int(event.y))
280 if pathinfo==None: return False
281 pathnr=pathinfo[0][0]
282 name=self.liststore[pathnr][1]
283 action=self.liststore[pathnr][2]
284 self.lock=True
285 self.cb_exec.set_active(False)
286 self.execute.set_sensitive(0)
287 if 'command=' in action:
288 a1=action.split('command=')
289 a2=a1[1].split(',)')
290 self.execute.set_text(a2[0])
291 self.hbox.show()
292 else: self.hbox.hide()
293 self.path=pathnr
294 self.keys.set_text(name)
295 self.action.set_text(action)
296 self.lock=False
297
298
299 def row_activate(self, treeview, pathnr, column):
300 # a keybinding is selected by doubleclick or keyboard
301 name=self.liststore[pathnr][1]
302 action=self.liststore[pathnr][2]
303 self.lock=True
304 self.cb_exec.set_active(False)
305 self.execute.set_sensitive(0)
306 if 'command=' in action:
307 a1=action.split('command=')
308 a2=a1[1].split(',)')
309 self.execute.set_text(a2[0])
310 self.hbox.show()
311 else: self.hbox.hide()
312 self.path=pathnr
313 self.keys.set_text(name)
314 self.action.set_text(action)
315 self.lock=False
316
317
318 def keys_changed(self,data):
319 if self.lock: return
320 if self.path==-1: return
321 a=self.keys.get_text()
322 a=a.strip()
323 self.liststore[self.path][1]=a
324 b=self.liststore[self.path][3]
325 if b=='orginal': self.liststore[self.path][3]='name'
326
327
328 def execute_entry_changed(self,data):
329 if self.lock: return
330 if self.path==-1: return
331 a=self.execute.get_text()
332 a=a.strip()
333 b='Execute(command='+a+',)'
334 self.liststore[self.path][2]=b
335 self.action.set_text(b)
336
337
338 def add_new_keybinding(self,data=None):
339 self.count_list+=1
340 self.liststore.append(['F12','F12','Execute(command='+_("NameOfProgram")+',)','add'])
341
342
343 def delete_keybinding(self,data=None):
344 if self.lock: return
345 if self.path==-1: return
346 self.hbox.hide()
347 self.liststore[self.path][1]=""
348 self.liststore[self.path][2]=""
349 self.liststore[self.path][3]="delete"
350 self.action.set_text('')
351 self.keys.set_text('')
352
353
354 def toggle_execute_entry(self,data=None):
355 if self.lock: return
356 if self.path!=-1: b=self.liststore[self.path][3]
357 s=self.cb_exec.get_active()
358 if s:
359 self.execute.set_sensitive(1)
360 if self.path!=-1:
361 if (b=='orginal')or(b=='name'):
362 self.liststore[self.path][3]='execute'
363 else:
364 self.execute.set_sensitive(0)
365 if self.path!=-1:
366 if (b=='execute'):
367 self.liststore[self.path][3]='name'
368
369
370 def __init__(self):
371 namen=home+'/.openbox-keyconf'
372 if not os.access(namen,os.R_OK):
373 f=file(namen,'w')
374 f.write('x=0\n')
375 f.write('y=0\n')
376 f.write('width=800\n')
377 f.write('height=400\n')
378 f.close()
379 f=file(namen,'r')
380 s=f.readline()
381 while s !='':
382 s=s.replace('\n','')
383 z=s.split('=')
384 if z[0]=='x': self.pos_x=int(z[1])
385 if z[0]=='y': self.pos_y=int(z[1])
386 if z[0]=='width': self.width=int(z[1])
387 if z[0]=='height': self.height=int(z[1])
388 s=f.readline()
389 f.close()
390
391 window = gtk.Window(gtk.WINDOW_TOPLEVEL)
392 self.window=window
393 vbox = gtk.VBox(False, 0)
394 window.set_border_width(5)
395 window.add(vbox)
396
397 # When the window is given the "delete_event" signal (this is given
398 # by the window manager, usually by the "close" option, or on the
399 # titlebar), we ask it to call the delete_event () function
400 # as defined above. The data passed to the callback
401 # function is NULL and is ignored in the callback function.
402 window.connect("delete_event", self.delete_event)
403
404 # Here we connect the "destroy" event to a signal handler.
405 # This event occurs when we call gtk_widget_destroy() on the window,
406 # or if we return FALSE in the "delete_event" callback.
407 window.connect("destroy", self.destroy)
408
409 # Sets the border width of the window.
410 window.set_title(_('openbox keyboard bindings'))
411 window.set_size_request(400,200)
412 window.resize(self.width, self.height)
413 window.move(self.pos_x,self.pos_y)
414
415 self.liststore=gtk.ListStore(str,str,str,str)
416 self.treeview=gtk.TreeView()
417 self.treeview.set_headers_visible(True)
418 self.treeview.set_model(self.liststore)
419 self.treeview.set_property('rules-hint',True)
420 self.treeview.set_property('enable-grid-lines',True)
421 self.treeview.connect('row-activated', self.row_activate)
422 self.treeview.set_events(self.treeview.get_events() | gtk.gdk.BUTTON_PRESS_MASK)
423 self.treeview.connect("button-press-event", self.mouse)
424 self.cell1 = gtk.CellRendererText()
425 self.cell2 = gtk.CellRendererText()
426 self.spalte1 = gtk.TreeViewColumn(_('Key'))
427 self.spalte2 = gtk.TreeViewColumn(_('Action'))
428 self.treeview.append_column(self.spalte1)
429 self.treeview.append_column(self.spalte2)
430 self.spalte1.pack_start(self.cell1,True)
431 self.spalte2.pack_start(self.cell2,True)
432 self.spalte1.set_attributes(self.cell1, text=1)
433 self.spalte2.set_attributes(self.cell2, text=2)
434 sw = gtk.ScrolledWindow()
435 sw.set_shadow_type(gtk.SHADOW_ETCHED_IN)
436 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
437 sw.add(self.treeview)
438 filename=configfile
439 self.count_list=0
440 try:
441 f=codecs.open(filename,'r','utf_8')
442 line=f.readline()
443 found=False
444 data='frog'
445 action=False
446 name=''
447 while True:
448 line=f.readline()
449 if not line: break
450 if '<keyboard>' in line:
451 found=True
452 continue
453 if '</keyboard>' in line:
454 found=False
455 if not found: continue
456 lin=line.replace('\n','')
457 if '<keybind ' in lin:
458 lin2=lin.split('"')
459 name=lin2[1]
460 action=False
461 data=''
462 continue
463 if '</keybind>' in lin:
464 if action: data+=')'
465 if name!='':
466 self.liststore.append([name,name,data,'orginal'])
467 self.count_list+=1
468 name=''
469 continue
470 if name!='':
471 if '</action>' in lin: continue
472 if '<action ' in lin:
473 if action: data+='), '
474 action=True
475 lin2=lin.split('"')
476 data=data+lin2[1]+'('
477 continue
478 if '<startupnotify>' in lin:
479 continue
480 if '</startupnotify>' in lin:
481 continue
482 if ('<' in lin) and ('>' in lin):
483 lin2=lin.split('<')
484 lin3=lin2[1].split('>')
485 data=data+lin3[0]+'='+lin3[1]+','
486 continue
487 f.close()
488 except:
489 pass
490 gc.collect()
491 self.lock=True
492 self.path=-1
493 hbox = gtk.HBox(False, 5)
494 self.keys=gtk.Entry(max=40)
495 self.keys.connect("changed",self.keys_changed)
496 self.action=gtk.Label('')
497 hbox.pack_start(self.keys,False,False,0)
498 hbox.pack_start(self.action,True,True,0)
499 vbox.pack_start(hbox, False,False, 0)
500 self.hbox = gtk.HBox(False, 5)
501 self.cb_exec=gtk.CheckButton(_("Edit command")+" =")
502 self.cb_exec.connect_object("toggled",self.toggle_execute_entry,None)
503 self.execute=gtk.Entry(max=0)
504 self.execute.connect("changed",self.execute_entry_changed)
505 self.hbox.pack_start(self.cb_exec,False,False,0)
506 self.hbox.pack_start(self.execute,True,True,0)
507 vbox.pack_start(self.hbox, False,False, 0)
508 vbox.pack_start(sw, True, True, 0)
509 hbox = gtk.HBox(False, 5)
510 button1=gtk.Button(stock='gtk-apply')
511 hiddenlabel=gtk.Label(' ')
512 button2=gtk.Button(stock='gtk-cancel')
513 button3=gtk.Button(stock='gtk-add')
514 button4=gtk.Button(stock='gtk-delete')
515 button5=gtk.Button(stock='gtk-help')
516 hbox.pack_start(button1,False,False,0)
517 hbox.pack_start(button3,False,False,0)
518 hbox.pack_start(button4,False,False,0)
519 hbox.pack_start(button5,False,False,0)
520 hbox.pack_start(hiddenlabel,True,True,0)
521 hbox.pack_start(button2,False,False,0)
522 button1.connect_object("clicked", self.save,None)
523 button2.connect_object("clicked", self.goodbye,None)
524 button3.connect_object("clicked", self.add_new_keybinding,None)
525 button4.connect_object("clicked", self.delete_keybinding,None)
526 button5.connect("clicked", create_help_view)
527 vbox.pack_end(hbox, False, False, 0)
528 window.show_all()
529 self.hbox.hide()
530
531
532 def main(self):
533 # All PyGTK applications must have a gtk.main(). Control ends here
534 # and waits for an event to occur (like a key press or mouse event).
535 gtk.main()
536
537
538
539 # If the program is run directly or passed as an argument to the python
540 # interpreter then create a HelloWorld instance and show it
541 if __name__ == "__main__":
542 home=os.getenv("HOME")
543 Elfriede = Tool()
544 Elfriede.main()