openbox-keyconf: use tempory file to write config, and move it when it's ready
[lxde/lxadmin.git] / src / openbox-keyconf / openbox-keyconf.py
CommitLineData
2a6d9869
JL
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
22helptext="""M = [Mod3]
23W = [Windows]
24S = [Shift]
25A = [Alt]
26C = [Control]
27
28F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12
29Escape
30Return
31Menu
32Tab
33Pause
34space
35Print
36Caps_Lock
37Scroll_Lock
38Num_Lock
39Insert
40Delete
41BackSpace
42Home = [Pos1]
43End = [End]
44Prior = [PageUp]
45Next = [PageDown]
46asciicircum = [^]
47less = [<]
48greater = [>]
49minus = [-]
50plus = [+]
51comma = [,]
52period = [.]
53numbersign = [#]
54acute = [´]
55
56Left,Right,Up,Down
57_____________________
58
59[Keypad/Numberblock]
60
61KP_0, KP_1, KP_2, KP_3, KP_4, KP_5, KP_6, KP_7, KP_8, KP_9
62KP_Divide = [/]
63KP_Multiply = [*]
64KP_Subtract = [-]
65KP_Add = [+]
66KP_Enter
67KP_Begin = [5]
68KP_Home = [7]
69KP_End = [1]
70KP_Prior = [9] [PageUp]
71KP_Next = [3] [PageDown]
72KP_Up = [8] [cursor up]
73KP_Down = [2] [cursor down]
74KP_Left = [4] [cursor left]
75KP_Right = [6] [cursor right]
76
77for more keycodes start "xev" in a terminal"""
78
5e89655d 79import lxadmin.defs as defs
28275799 80import lxadmin.detect_os as detect_os
2a6d9869 81
5e89655d 82import gettext
2a6d9869 83
5e89655d
JL
84gettext.bindtextdomain('lxadmin', defs.LOCALE_DIR)
85gettext.textdomain('lxadmin')
86_ = gettext.gettext
87
88from gettext import gettext as _
2a6d9869 89
2a6d9869
JL
90import os
91import sys
8f698f38
JL
92import shutil
93import tempfile
2a6d9869
JL
94import time
95import string
96import codecs
97import pygtk
98pygtk.require('2.0')
99import gtk
100import pango
101import gobject
102import gc
103
955ccca7 104configfile=detect_os.get_openbox_config()
2a6d9869
JL
105mybuffer=None
106helpwindow=None
2a6d9869
JL
107
108def 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)
a48ca210 115 helpwindow.set_title(_('Help: keyboard codes'))
2a6d9869
JL
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
139def destroy_help_view(widget, data=None):
140 global helpwindow,mybuffer
141 helpwindow.destroy()
142 helpwindow=None
143 mybuffer=None
144
145
146
147class 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
28275799 192 filename=configfile
2a6d9869
JL
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
8f698f38 211 f = tempfile.NamedTemporaryFile()
2a6d9869
JL
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')
8f698f38 269 shutil.copyfile(f.name, filename)
2a6d9869
JL
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
a48ca210 340 self.liststore.append(['F12','F12','Execute(command='+_("NameOfProgram")+',)','add'])
2a6d9869
JL
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.
a48ca210 410 window.set_title(_('openbox keyboard bindings'))
2a6d9869
JL
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()
a48ca210
JL
426 self.spalte1 = gtk.TreeViewColumn(_('Key'))
427 self.spalte2 = gtk.TreeViewColumn(_('Action'))
2a6d9869
JL
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)
28275799 438 filename=configfile
2a6d9869
JL
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)
a48ca210 501 self.cb_exec=gtk.CheckButton(_("Edit command")+" =")
2a6d9869
JL
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
541if __name__ == "__main__":
542 home=os.getenv("HOME")
2a6d9869
JL
543 Elfriede = Tool()
544 Elfriede.main()