ibus-setup: improve ibus-daemon auto start
[platform/upstream/ibus.git] / setup / main.py
1 # vim:set et sts=4 sw=4:
2 #
3 # ibus - The Input Bus
4 #
5 # Copyright (c) 2007-2010 Peng Huang <shawn.p.huang@gmail.com>
6 # Copyright (c) 2007-2010 Red Hat, Inc.
7 #
8 # This library is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU Lesser General Public
10 # License as published by the Free Software Foundation; either
11 # version 2 of the License, or (at your option) any later version.
12 #
13 # This library is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU Lesser General Public License for more details.
17 #
18 # You should have received a copy of the GNU Lesser General Public
19 # License along with this program; if not, write to the
20 # Free Software Foundation, Inc., 59 Temple Place, Suite 330,
21 # Boston, MA  02111-1307  USA
22
23 import os
24 import signal
25 import sys
26 import time
27
28 from gi.repository import GLib
29 from gi.repository import Gtk
30 from gi.repository import IBus
31 from os import path
32 from xdg import BaseDirectory
33
34 import keyboardshortcut
35 import locale
36 from enginecombobox import EngineComboBox
37 from enginetreeview import EngineTreeView
38 from engineabout import EngineAbout
39 from i18n import DOMAINNAME, _, N_, init as i18n_init
40
41 (
42     COLUMN_NAME,
43     COLUMN_ENABLE,
44     COLUMN_PRELOAD,
45     COLUMN_VISIBLE,
46     COLUMN_ICON,
47     COLUMN_DATA,
48 ) = range(6)
49
50 (
51     DATA_NAME,
52     DATA_LOCAL_NAME,
53     DATA_LANG,
54     DATA_ICON,
55     DATA_AUTHOR,
56     DATA_CREDITS,
57     DATA_EXEC,
58     DATA_STARTED,
59     DATA_PRELOAD
60 ) = range(9)
61
62 class Setup(object):
63     def __flush_gtk_events(self):
64         while Gtk.events_pending():
65             Gtk.main_iteration()
66
67     def __init__(self):
68         super(Setup, self).__init__()
69         gtk_builder_file = path.join(path.dirname(__file__), "./setup.ui")
70         self.__builder = Gtk.Builder()
71         self.__builder.set_translation_domain(DOMAINNAME)
72         self.__builder.add_from_file(gtk_builder_file);
73         self.__bus = None
74         self.__init_bus()
75         self.__init_ui()
76
77     def __init_hotkey(self):
78         default_values = {
79             "trigger" : (N_("trigger"), ["Control+space"]),
80             "enable_unconditional" : (N_("enable"), []),
81             "disable_unconditional" : (N_("disable"), [])
82         }
83
84         values = dict(self.__config.get_values("general/hotkey"))
85
86         for name, (label, shortcuts) in default_values.items():
87             shortcuts = values.get(name, shortcuts)
88             button = self.__builder.get_object("button_%s" % name)
89             entry = self.__builder.get_object("entry_%s" % name)
90             entry.set_text("; ".join(shortcuts))
91             entry.set_tooltip_text("\n".join(shortcuts))
92             button.connect("clicked", self.__shortcut_button_clicked_cb,
93                     label, "general/hotkey", name, entry)
94
95     def __init_panel(self):
96         values = dict(self.__config.get_values("panel"))
97
98         # lookup table orientation
99         self.__combobox_lookup_table_orientation = self.__builder.get_object(
100                 "combobox_lookup_table_orientation")
101         self.__combobox_lookup_table_orientation.set_active(
102                 values.get("lookup_table_orientation", 0))
103         self.__combobox_lookup_table_orientation.connect("changed",
104                 self.__combobox_lookup_table_orientation_changed_cb)
105
106         # auto hide
107         self.__combobox_panel_show = self.__builder.get_object(
108                 "combobox_panel_show")
109         self.__combobox_panel_show.set_active(values.get("show", 0))
110         self.__combobox_panel_show.connect("changed",
111                 self.__combobox_panel_show_changed_cb)
112
113         # panel position
114         self.__combobox_panel_position = self.__builder.get_object(
115                 "combobox_panel_position")
116         self.__combobox_panel_position.set_active(values.get("position", 3))
117         self.__combobox_panel_position.connect("changed",
118                 self.__combobox_panel_position_changed_cb)
119
120         # custom font
121         self.__checkbutton_custom_font = self.__builder.get_object(
122                 "checkbutton_custom_font")
123         self.__checkbutton_custom_font.set_active(
124                 values.get("use_custom_font", False))
125         self.__checkbutton_custom_font.connect("toggled",
126                 self.__checkbutton_custom_font_toggled_cb)
127
128         self.__fontbutton_custom_font = self.__builder.get_object(
129                 "fontbutton_custom_font")
130         if values.get("use_custom_font", False):
131             self.__fontbutton_custom_font.set_sensitive(True)
132         else:
133             self.__fontbutton_custom_font.set_sensitive(False)
134         font_name = Gtk.Settings.get_default().get_property("gtk-font-name")
135         font_name = unicode(font_name, "utf-8")
136         font_name = values.get("custom_font", font_name)
137         self.__fontbutton_custom_font.connect("notify::font-name",
138                 self.__fontbutton_custom_font_notify_cb)
139         self.__fontbutton_custom_font.set_font_name(font_name)
140
141         # show icon on system tray
142         self.__checkbutton_show_icon_on_systray = self.__builder.get_object(
143                 "checkbutton_show_icon_on_systray")
144         self.__checkbutton_show_icon_on_systray.set_active(
145                 values.get("show_icon_on_systray", True))
146         self.__checkbutton_show_icon_on_systray.connect("toggled",
147                 self.__checkbutton_show_icon_on_systray_toggled_cb)
148
149         # show ime name
150         self.__checkbutton_show_im_name = self.__builder.get_object(
151                 "checkbutton_show_im_name")
152         self.__checkbutton_show_im_name.set_active(
153                 values.get("show_im_name", False))
154         self.__checkbutton_show_im_name.connect("toggled",
155                 self.__checkbutton_show_im_name_toggled_cb)
156
157     def __init_general(self):
158         values = dict(self.__config.get_values("general"))
159
160         # embed preedit text
161         self.__checkbutton_embed_preedit_text = self.__builder.get_object(
162                 "checkbutton_embed_preedit_text")
163         self.__checkbutton_embed_preedit_text.set_active(
164                 values.get("embed_preedit_text", True))
165         self.__checkbutton_embed_preedit_text.connect("toggled",
166                 self.__checkbutton_embed_preedit_text_toggled_cb)
167
168         # use system keyboard layout setting
169         self.__checkbutton_use_sys_layout = self.__builder.get_object(
170                 "checkbutton_use_sys_layout")
171         self.__checkbutton_use_sys_layout.set_active(
172                 values.get("use_system_keyboard_layout", True))
173         self.__checkbutton_use_sys_layout.connect("toggled",
174                 self.__checkbutton_use_sys_layout_toggled_cb)
175
176         # use global ime setting
177         self.__checkbutton_use_global_engine = self.__builder.get_object(
178                 "checkbutton_use_global_engine")
179         self.__checkbutton_use_global_engine.set_active(
180                 values.get("use_global_engine", False))
181         self.__checkbutton_use_global_engine.connect("toggled",
182                 self.__checkbutton_use_global_engine_toggled_cb)
183
184         # init engine page
185         self.__engines = self.__bus.list_engines()
186         self.__combobox = self.__builder.get_object("combobox_engines")
187         self.__combobox.set_engines(self.__engines)
188
189         tmp_dict = {}
190         for e in self.__engines:
191             tmp_dict[e.get_name()] = e
192         engine_names = values.get("preload_engines", [])
193         engines = [tmp_dict[name] for name in engine_names if name in tmp_dict]
194
195         self.__treeview = self.__builder.get_object("treeview_engines")
196         self.__treeview.set_engines(engines)
197
198         button = self.__builder.get_object("button_engine_add")
199         button.connect("clicked", self.__button_engine_add_cb)
200         button = self.__builder.get_object("button_engine_remove")
201         button.connect("clicked", lambda *args:self.__treeview.remove_engine())
202         button = self.__builder.get_object("button_engine_up")
203         button.connect("clicked", lambda *args:self.__treeview.move_up_engine())
204
205         button = self.__builder.get_object("button_engine_down")
206         button.connect("clicked",
207                 lambda *args:self.__treeview.move_down_engine())
208
209         button = self.__builder.get_object("button_engine_about")
210         button.connect("clicked", self.__button_engine_about_cb)
211
212         self.__engine_setup_exec_list = {}
213         button = self.__builder.get_object("button_engine_preferences")
214         button.connect("clicked", self.__button_engine_preferences_cb)
215
216         self.__combobox.connect("notify::active-engine",
217                 self.__combobox_notify_active_engine_cb)
218         self.__treeview.connect("notify::active-engine", self.__treeview_notify_cb)
219         self.__treeview.connect("notify::engines", self.__treeview_notify_cb)
220
221     def __init_ui(self):
222         # add icon search path
223         self.__window = self.__builder.get_object("window_preferences")
224         self.__window.connect("delete-event", Gtk.main_quit)
225
226         self.__button_close = self.__builder.get_object("button_close")
227         self.__button_close.connect("clicked", Gtk.main_quit)
228
229         # auto start ibus
230         self.__checkbutton_auto_start = self.__builder.get_object(
231                 "checkbutton_auto_start")
232         self.__checkbutton_auto_start.set_active(self.__is_auto_start())
233         self.__checkbutton_auto_start.connect("toggled",
234                 self.__checkbutton_auto_start_toggled_cb)
235
236         self.__config = self.__bus.get_config()
237
238         self.__init_hotkey()
239         self.__init_panel()
240         self.__init_general()
241
242     def __combobox_notify_active_engine_cb(self, combobox, property):
243         engine = self.__combobox.get_active_engine()
244         button = self.__builder.get_object("button_engine_add")
245         button.set_sensitive(
246                 engine != None and engine not in self.__treeview.get_engines())
247
248     def __get_engine_setup_exec_args(self, engine):
249         args = []
250         if engine == None:
251            return args
252         setup = str(engine.get_setup())
253         if len(setup) != 0:
254             args = setup.split()
255             args.insert(1, path.basename(args[0]))
256             return args
257         name = str(engine.get_name())
258         libexecdir = os.environ['IBUS_LIBEXECDIR']
259         setup_path = (libexecdir + '/' + 'ibus-setup-' if libexecdir != None \
260             else 'ibus-setup-') + name.split(':')[0]
261         if path.exists(setup_path):
262             args.append(setup_path)
263             args.append(path.basename(setup_path))
264         return args
265
266     def __treeview_notify_cb(self, treeview, prop):
267         if prop.name not in ("active-engine", "engines"):
268             return
269
270         engines = self.__treeview.get_engines()
271         engine = self.__treeview.get_active_engine()
272
273         self.__builder.get_object("button_engine_remove").set_sensitive(engine != None)
274         self.__builder.get_object("button_engine_about").set_sensitive(engine != None)
275         self.__builder.get_object("button_engine_up").set_sensitive(engine not in engines[:1])
276         self.__builder.get_object("button_engine_down").set_sensitive(engine not in engines[-1:])
277
278         obj = self.__builder.get_object("button_engine_preferences")
279         if len(self.__get_engine_setup_exec_args(engine)) != 0:
280             obj.set_sensitive(True)
281         else:
282             obj.set_sensitive(False)
283
284         if prop.name == "engines":
285             engine_names = map(lambda e: e.get_name(), engines)
286             value = GLib.Variant.new_strv(engine_names)
287             self.__config.set_value("general", "preload_engines", value)
288
289     def __button_engine_add_cb(self, button):
290         engine = self.__combobox.get_active_engine()
291         self.__treeview.append_engine(engine)
292
293     def __button_engine_about_cb(self, button):
294         engine = self.__treeview.get_active_engine()
295         if engine:
296             about = EngineAbout(engine)
297             about.run()
298             about.destroy()
299
300     def __button_engine_preferences_cb(self, button):
301         engine = self.__treeview.get_active_engine()
302         args = self.__get_engine_setup_exec_args(engine)
303         if len(args) == 0:
304             return
305         name = engine.get_name()
306         if name in self.__engine_setup_exec_list.keys():
307             try:
308                 wpid, sts = os.waitpid(self.__engine_setup_exec_list[name],
309                                        os.WNOHANG)
310                 # the setup is still running.
311                 if wpid == 0:
312                     return
313             except OSError:
314                 pass
315             del self.__engine_setup_exec_list[name]
316         self.__engine_setup_exec_list[name] = os.spawnl(os.P_NOWAIT, *args)
317
318     def __init_bus(self):
319         self.__bus = IBus.Bus()
320         if self.__bus.is_connected():
321             return
322
323         message = _("IBus daemon is not started. Do you want to start it now?")
324         dlg = Gtk.MessageDialog(type = Gtk.MessageType.QUESTION,
325                                 buttons = Gtk.ButtonsType.YES_NO,
326                                 text = message)
327         id = dlg.run()
328         dlg.destroy()
329         self.__flush_gtk_events()
330         if id != Gtk.ResponseType.YES:
331             sys.exit(0)
332
333         main_loop = GLib.MainLoop()
334
335         timeout = 5
336         GLib.timeout_add_seconds(timeout, lambda *args: main_loop.quit())
337         self.__bus.connect("connected", lambda *args: main_loop.quit())
338
339         os.spawnlp(os.P_NOWAIT, "ibus-daemon", "ibus-daemon", "--xim")
340
341         main_loop.run()
342
343         if self.__bus.is_connected():
344             message = _("IBus has been started! "
345                 "If you can not use IBus, please add below lines in $HOME/.bashrc, and relogin your desktop.\n"
346                 "  export GTK_IM_MODULE=ibus\n"
347                 "  export XMODIFIERS=@im=ibus\n"
348                 "  export QT_IM_MODULE=ibus"
349                 )
350             dlg = Gtk.MessageDialog(type = Gtk.MessageType.INFO,
351                                     buttons = Gtk.ButtonsType.OK,
352                                     text = message)
353             id = dlg.run()
354             dlg.destroy()
355             self.__flush_gtk_events()
356         else:
357             message = _("IBus daemon coundn't be started in %d seconds")
358             dlg = Gtk.MessageDialog(type = Gtk.MessageType.INFO,
359                                     buttons = Gtk.ButtonsType.OK,
360                                     text = message % timeout)
361             id = dlg.run()
362             dlg.destroy()
363             self.__flush_gtk_events()
364             sys.exit(0)
365
366     def __shortcut_button_clicked_cb(self, button, name, section, _name, entry):
367         buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
368                 Gtk.STOCK_OK, Gtk.ResponseType.OK)
369         title = _("Select keyboard shortcut for %s") %  _(name)
370         dialog = keyboardshortcut.KeyboardShortcutSelectionDialog(buttons = buttons, title = title)
371         text = entry.get_text()
372         if text:
373             shortcuts = text.split("; ")
374         else:
375             shortcuts = None
376         dialog.set_shortcuts(shortcuts)
377         id = dialog.run()
378         shortcuts = dialog.get_shortcuts()
379         dialog.destroy()
380         if id != Gtk.ResponseType.OK:
381             return
382         self.__config.set_value(section, _name, GLib.Variant.new_strv(shortcuts))
383         text = "; ".join(shortcuts)
384         entry.set_text(text)
385         entry.set_tooltip_text(text)
386
387
388     def __item_started_column_toggled_cb(self, cell, path_str, model):
389
390         # get toggled iter
391         iter = model.get_iter_from_string(path_str)
392         data = model.get_value(iter, COLUMN_DATA)
393
394         # do something with the value
395         if data[DATA_STARTED] == False:
396             try:
397                 self.__bus.register_start_engine(data[DATA_LANG], data[DATA_NAME])
398             except Exception, e:
399                 dlg = Gtk.MessageDialog(type = Gtk.MessageType.ERROR,
400                         buttons = Gtk.ButtonsType.CLOSE,
401                         message_format = str(e))
402                 dlg.run()
403                 dlg.destroy()
404                 self.__flush_gtk_events()
405                 return
406         else:
407             try:
408                 self.__bus.register_stop_engine(data[DATA_LANG], data[DATA_NAME])
409             except Exception, e:
410                 dlg = Gtk.MessageDialog(type = Gtk.MessageType.ERROR,
411                         buttons = Gtk.ButtonsType.CLOSE,
412                         message_format = str(e))
413                 dlg.run()
414                 dlg.destroy()
415                 self.__flush_gtk_events()
416                 return
417         data[DATA_STARTED] = not data[DATA_STARTED]
418
419         # set new value
420         model.set(iter, COLUMN_ENABLE, data[DATA_STARTED])
421
422     def __item_preload_column_toggled_cb(self, cell, path_str, model):
423
424         # get toggled iter
425         iter = model.get_iter_from_string(path_str)
426         data = model.get_value(iter, COLUMN_DATA)
427
428         data[DATA_PRELOAD] = not data[DATA_PRELOAD]
429         engine = "%s:%s" % (data[DATA_LANG], data[DATA_NAME])
430
431         if data[DATA_PRELOAD]:
432             if engine not in self.__preload_engines:
433                 self.__preload_engines.add(engine)
434                 value = GLib.Variant.new_strv(list(self.__preload_engines))
435                 self.__config.set_value("general", "preload_engines", value)
436         else:
437             if engine in self.__preload_engines:
438                 self.__preload_engines.remove(engine)
439                 value = GLib.Variant.new_strv(list(self.__preload_engines))
440                 self.__config.set_value("general", "preload_engines", value)
441
442         # set new value
443         model.set(iter, COLUMN_PRELOAD, data[DATA_PRELOAD])
444
445     def __is_auto_start(self):
446         link_file = path.join(BaseDirectory.xdg_config_home, "autostart/IBus.desktop")
447         ibus_desktop = path.join(os.getenv("IBUS_PREFIX"), "share/applications/IBus.desktop")
448
449         if not path.exists(link_file):
450             return False
451         if not path.islink(link_file):
452             return False
453         if path.realpath(link_file) != ibus_desktop:
454             return False
455         return True
456
457     def __checkbutton_auto_start_toggled_cb(self, button):
458         auto_start_dir = path.join(BaseDirectory.xdg_config_home, "autostart")
459         if not path.isdir(auto_start_dir):
460             os.makedirs(auto_start_dir)
461
462         link_file = path.join(BaseDirectory.xdg_config_home, "autostart/IBus.desktop")
463         ibus_desktop = path.join(os.getenv("IBUS_PREFIX"), "share/applications/IBus.desktop")
464         # unlink file
465         try:
466             os.unlink(link_file)
467         except:
468             pass
469         if self.__checkbutton_auto_start.get_active():
470             os.symlink(ibus_desktop, link_file)
471
472     def __combobox_lookup_table_orientation_changed_cb(self, combobox):
473         self.__config.set_value(
474                 "panel", "lookup_table_orientation",
475                 GLib.Variant.new_int32(self.__combobox_lookup_table_orientation.get_active()))
476
477     def __combobox_panel_show_changed_cb(self, combobox):
478         self.__config.set_value(
479                 "panel", "show",
480                 GLib.Variant.new_int32(self.__combobox_panel_show.get_active()))
481
482     def __combobox_panel_position_changed_cb(self, combobox):
483         self.__config.set_value(
484                 "panel", "position",
485                 GLib.Variant.new_int32(self.__combobox_panel_position.get_active()))
486
487     def __checkbutton_custom_font_toggled_cb(self, button):
488         if self.__checkbutton_custom_font.get_active():
489             self.__fontbutton_custom_font.set_sensitive(True)
490             self.__config.set_value("panel", "use_custom_font",
491                     GLib.Variant.new_boolean(True))
492         else:
493             self.__fontbutton_custom_font.set_sensitive(False)
494             self.__config.set_value("panel", "use_custom_font",
495                     GLib.Variant.new_boolean(False))
496
497     def __fontbutton_custom_font_notify_cb(self, button, arg):
498         font_name = self.__fontbutton_custom_font.get_font_name()
499         font_name = unicode(font_name, "utf-8")
500         self.__config.set_value("panel", "custom_font",
501                 GLib.Variant.new_string(font_name))
502
503     def __checkbutton_show_icon_on_systray_toggled_cb(self, button):
504         value = self.__checkbutton_show_icon_on_systray.get_active()
505         value = GLib.Variant.new_boolean(value)
506         self.__config.set_value("panel", "show_icon_on_systray", value)
507
508     def __checkbutton_show_im_name_toggled_cb(self, button):
509         value = self.__checkbutton_show_im_name.get_active()
510         value = GLib.Variant.new_boolean(value)
511         self.__config.set_value("panel", "show_im_name", value)
512
513     def __checkbutton_embed_preedit_text_toggled_cb(self, button):
514         value = self.__checkbutton_embed_preedit_text.get_active()
515         value = GLib.Variant.new_boolean(value)
516         self.__config.set_value("general", "embed_preedit_text", value)
517
518     def __checkbutton_use_sys_layout_toggled_cb(self, button):
519         value = self.__checkbutton_use_sys_layout.get_active()
520         value = GLib.Variant.new_boolean(value)
521         self.__config.set_value("general", "use_system_keyboard_layout", value)
522
523     def __checkbutton_use_global_engine_toggled_cb(self, button):
524         value = self.__checkbutton_use_global_engine.get_active()
525         value = GLib.Variant.new_boolean(value)
526         self.__config.set_value("general", "use_global_engine", value)
527
528     def __config_value_changed_cb(self, bus, section, name, value):
529         pass
530
531     def __config_reloaded_cb(self, bus):
532         pass
533
534     def __sigusr1_cb(self, *args):
535         self.__window.present()
536
537     def run(self):
538         self.__window.show_all()
539         signal.signal(signal.SIGUSR1, self.__sigusr1_cb)
540         Gtk.main()
541
542 if __name__ == "__main__":
543     locale.setlocale(locale.LC_ALL, '')
544     i18n_init()
545     setup = Setup()
546     setup.run()