"Fossies" - the Fresh Open Source Software Archive

Member "screenkey-1.1/Screenkey/screenkey.py" (27 May 2020, 33117 Bytes) of package /linux/privat/screenkey-1.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "screenkey.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 1.0_vs_1.1.

    1 # -*- coding: utf-8 -*-
    2 # "screenkey" is distributed under GNU GPLv3+, WITHOUT ANY WARRANTY.
    3 # Copyright(c) 2010-2012: Pablo Seminario <pabluk@gmail.com>
    4 # Copyright(c) 2015-2020: wave++ "Yuri D'Elia" <wavexx@thregr.org>
    5 # Copyright(c) 2019-2020: Yuto Tokunaga <yuntan.sub1@gmail.com>
    6 
    7 from . import *
    8 from .labelmanager import LabelManager
    9 
   10 from threading import Timer
   11 import json
   12 import os
   13 import subprocess
   14 import numbers
   15 
   16 import gi
   17 gi.require_version('Gtk', '3.0')
   18 gi.require_version('Pango', '1.0')
   19 
   20 from gi.repository import GLib
   21 GLib.threads_init()
   22 
   23 from gi.repository import Gtk, Gdk, Pango
   24 import cairo
   25 
   26 
   27 START = Gtk.Align.START
   28 CENTER = Gtk.Align.CENTER
   29 END = Gtk.Align.END
   30 FILL = Gtk.Align.FILL
   31 TOP = Gtk.PositionType.TOP
   32 BOTTOM = Gtk.PositionType.BOTTOM
   33 RIGHT = Gtk.PositionType.RIGHT
   34 LEFT = Gtk.PositionType.LEFT
   35 HORIZONTAL = Gtk.Orientation.HORIZONTAL
   36 VERTICAL = Gtk.Orientation.VERTICAL
   37 IF_VALID = Gtk.SpinButtonUpdatePolicy.IF_VALID
   38 
   39 
   40 def gi_module_available(module, version):
   41     try:
   42         gi.require_version(module, version)
   43         return True
   44     except ValueError:
   45         return False
   46 
   47 
   48 class Screenkey(Gtk.Window):
   49     STATE_FILE = os.path.join(GLib.get_user_config_dir(), 'screenkey.json')
   50 
   51     def __init__(self, logger, options, show_settings=False):
   52         Gtk.Window.__init__(self, Gtk.WindowType.POPUP)
   53 
   54         self.exit_status = None
   55         self.timer_hide = None
   56         self.timer_min = None
   57         self.logger = logger
   58 
   59         defaults = Options({'no_systray': False,
   60                             'timeout': 2.5,
   61                             'recent_thr': 0.1,
   62                             'compr_cnt': 3,
   63                             'ignore': [],
   64                             'position': 'bottom',
   65                             'persist': False,
   66                             'font_desc': 'Sans Bold',
   67                             'font_size': 'medium',
   68                             'font_color': 'white',
   69                             'bg_color': 'black',
   70                             'opacity': 0.8,
   71                             'key_mode': 'composed',
   72                             'bak_mode': 'baked',
   73                             'mods_mode': 'normal',
   74                             'mods_only': False,
   75                             'multiline': False,
   76                             'vis_shift': False,
   77                             'vis_space': True,
   78                             'geometry': None,
   79                             'screen': 0})
   80         self.options = self.load_state()
   81         if self.options is None:
   82             self.options = defaults
   83         else:
   84             # copy missing defaults
   85             for k, v in defaults.items():
   86                 if k not in self.options:
   87                     self.options[k] = v
   88         if options is not None:
   89             # override with values from constructor
   90             for k, v in options.items():
   91                 if v is not None:
   92                     self.options[k] = v
   93 
   94         self.set_keep_above(True)
   95         self.set_accept_focus(False)
   96         self.set_focus_on_map(False)
   97         self.set_app_paintable(True)
   98 
   99         self.label = Gtk.Label()
  100         self.label.set_ellipsize(Pango.EllipsizeMode.START)
  101         self.label.set_justify(Gtk.Justification.CENTER)
  102         self.label.show()
  103         self.add(self.label)
  104 
  105         self.font = Pango.FontDescription(self.options.font_desc)
  106         self.update_colors()
  107 
  108         self.set_size_request(0, 0)
  109         self.set_gravity(Gdk.Gravity.CENTER)
  110         self.connect("configure-event", self.on_configure)
  111         self.connect("draw", self.on_draw)
  112 
  113         scr = self.get_screen()
  114         scr.connect("size-changed", self.on_configure)
  115         scr.connect("monitors-changed", self.on_monitors_changed)
  116         self.set_active_monitor(self.options.screen)
  117 
  118         visual = scr.get_rgba_visual()
  119         if visual is not None:
  120             self.set_visual(visual)
  121 
  122         self.labelmngr = None
  123         self.enabled = True
  124         self.on_change_mode()
  125 
  126         self.make_menu()
  127         self.make_about_dialog()
  128         self.make_preferences_dialog()
  129 
  130         if not self.options.no_systray:
  131             if gi_module_available('AppIndicator3', '0.1'):
  132                 self.make_appindicator()
  133             else:
  134                 self.make_systray()
  135 
  136         self.connect("delete-event", self.quit)
  137         if show_settings:
  138             self.on_preferences_dialog()
  139         if self.options.persist:
  140             self.show()
  141 
  142 
  143     def quit(self, widget=None, data=None, exit_status=os.EX_OK):
  144         self.labelmngr.stop()
  145         self.exit_status = exit_status
  146         Gtk.main_quit()
  147 
  148 
  149     def load_state(self):
  150         """Load stored options"""
  151         options = None
  152         try:
  153             with open(self.STATE_FILE, 'r') as f:
  154                 options = Options(json.load(f))
  155                 self.logger.debug("Options loaded.")
  156         except IOError:
  157             self.logger.debug("file %s does not exists." % self.STATE_FILE)
  158         except ValueError:
  159             self.logger.debug("file %s is invalid." % self.STATE_FILE)
  160 
  161         # compatibility with previous versions (0.5)
  162         if options and options.key_mode == 'normal':
  163             options.key_mode = 'composed'
  164 
  165         return options
  166 
  167 
  168     def store_state(self, options):
  169         """Store options"""
  170         try:
  171             with open(self.STATE_FILE, 'w') as f:
  172                 json.dump(options._store, f)
  173                 self.logger.debug("Options saved.")
  174         except IOError:
  175             self.logger.debug("Cannot open %s." % self.STATE_FILE)
  176 
  177 
  178     def set_active_monitor(self, monitor):
  179         scr = self.get_screen()
  180         if monitor >= scr.get_n_monitors():
  181             self.monitor = 0
  182         else:
  183             self.monitor = monitor
  184         self.update_geometry()
  185 
  186 
  187     def on_monitors_changed(self, *_):
  188         self.set_active_monitor(self.monitor)
  189 
  190 
  191     def update_font(self):
  192         _, window_height = self.get_size()
  193         text = self.label.get_text()
  194         lines = text.count('\n') + 1
  195         self.font.set_absolute_size((50 * window_height // lines // 100) * 1000)
  196         self.label.get_pango_context().set_font_description(self.font)
  197 
  198 
  199     def update_colors(self):
  200         self.label.modify_fg(Gtk.StateFlags.NORMAL, Gdk.color_parse(self.options.font_color))
  201         self.bg_color = Gdk.color_parse(self.options.bg_color)
  202         self.queue_draw()
  203 
  204 
  205     def on_draw(self, widget, cr):
  206         cr.set_source_rgba(self.bg_color.red_float,
  207                            self.bg_color.green_float,
  208                            self.bg_color.blue_float,
  209                            self.options.opacity)
  210         cr.set_operator(cairo.OPERATOR_SOURCE)
  211         cr.paint()
  212         return False
  213 
  214 
  215     def on_configure(self, *_):
  216         window_x, window_y = self.get_position()
  217         window_width, window_height = self.get_size()
  218 
  219         # set event mask for click-through
  220         self.input_shape_combine_region(cairo.Region(cairo.RectangleInt(0, 0, 0, 0)))
  221 
  222         # set some proportional inner padding
  223         self.label.set_padding(window_width // 100, 0)
  224 
  225         self.update_font()
  226 
  227 
  228     def update_geometry(self, configure=False):
  229         geometry = self.get_screen().get_monitor_geometry(self.monitor)
  230         if self.options.geometry is not None:
  231             x, y, w, h = self.options.geometry
  232             if not isinstance(x, numbers.Integral):
  233                 x = int(x * geometry.width)
  234             if not isinstance(y, numbers.Integral):
  235                 y = int(y * geometry.height)
  236             if not isinstance(w, numbers.Integral):
  237                 w = int(w * geometry.width)
  238             if not isinstance(h, numbers.Integral):
  239                 h = int(h * geometry.height)
  240             if x < 0:
  241                 x = geometry.width + x - w
  242             if y < 0:
  243                 y = geometry.height + y - h
  244             area_geometry = [x, y, w, h]
  245         else:
  246             area_geometry = [geometry.x, geometry.y, geometry.width, geometry.height]
  247 
  248         if self.options.position == 'fixed':
  249             self.move(*area_geometry[0:2])
  250             self.resize(*area_geometry[2:4])
  251             self.update_font()
  252             return
  253 
  254         if self.options.font_size == 'large':
  255             window_height = 24 * area_geometry[3] // 100
  256         elif self.options.font_size == 'medium':
  257             window_height = 12 * area_geometry[3] // 100
  258         else:
  259             window_height = 8 * area_geometry[3] // 100
  260         self.resize(area_geometry[2], window_height)
  261 
  262         if self.options.position == 'top':
  263             window_y = area_geometry[1] + area_geometry[3] // 10
  264         elif self.options.position == 'center':
  265             window_y = area_geometry[1] + area_geometry[3] // 2 - window_height // 2
  266         else:
  267             window_y = area_geometry[1] + area_geometry[3] * 9 // 10 - window_height
  268         self.move(area_geometry[0], window_y)
  269         self.update_font()
  270 
  271 
  272     def on_statusicon_popup(self, widget, button, timestamp, data=None):
  273         if button == 3 and data:
  274             data.show()
  275             data.popup_at_pointer(None)
  276 
  277 
  278     def show(self):
  279         super(Screenkey, self).show()
  280 
  281 
  282     def on_labelmngr_error(self):
  283         msg = Gtk.MessageDialog(parent=self,
  284                                 type=Gtk.MessageType.ERROR,
  285                                 buttons=Gtk.ButtonsType.OK,
  286                                 message_format="Error initializing Screenkey")
  287         text = _('Screenkey failed to initialize. This is usually a sign of an improperly '
  288                  'configured input method or desktop keyboard settings. Please see the <a '
  289                  'href="{url}">troubleshooting documentation</a> for further diagnosing '
  290                  'instructions.\n\nScreenkey cannot recover and will now quit!')
  291         msg.format_secondary_markup(text.format(url=ERROR_URL))
  292         msg.run()
  293         msg.destroy()
  294         self.quit(exit_status=os.EX_SOFTWARE)
  295 
  296 
  297     def on_label_change(self, markup, synthetic):
  298         if markup is None:
  299             self.on_labelmngr_error()
  300             return
  301 
  302         _, attr, text, _ = Pango.parse_markup(markup, -1, '\0')
  303         self.label.set_text(text)
  304         self.label.set_attributes(attr)
  305         self.update_font()
  306 
  307         if not self.get_property('visible'):
  308             self.show()
  309         if self.timer_hide:
  310             self.timer_hide.cancel()
  311         if self.options.timeout > 0:
  312             self.timer_hide = Timer(self.options.timeout, self.on_timeout_main)
  313             self.timer_hide.start()
  314         if self.timer_min:
  315             self.timer_min.cancel()
  316         if not synthetic:
  317             self.timer_min = Timer(self.options.recent_thr * 2, self.on_timeout_min)
  318             self.timer_min.start()
  319 
  320 
  321     def on_timeout_main(self):
  322         if not self.options.persist:
  323             self.hide()
  324         self.label.set_text('')
  325         self.labelmngr.clear()
  326 
  327 
  328     def on_timeout_min(self):
  329         self.labelmngr.queue_update()
  330 
  331 
  332     def restart_labelmanager(self):
  333         self.logger.debug("Restarting LabelManager.")
  334         if self.labelmngr:
  335             self.labelmngr.stop()
  336         self.labelmngr = LabelManager(self.on_label_change, logger=self.logger,
  337                                       key_mode=self.options.key_mode,
  338                                       bak_mode=self.options.bak_mode,
  339                                       mods_mode=self.options.mods_mode,
  340                                       mods_only=self.options.mods_only,
  341                                       multiline=self.options.multiline,
  342                                       vis_shift=self.options.vis_shift,
  343                                       vis_space=self.options.vis_space,
  344                                       recent_thr=self.options.recent_thr,
  345                                       compr_cnt=self.options.compr_cnt,
  346                                       ignore=self.options.ignore,
  347                                       pango_ctx=self.label.get_pango_context())
  348         self.labelmngr.start()
  349 
  350 
  351     def on_change_mode(self):
  352         if not self.enabled:
  353             return
  354         self.restart_labelmanager()
  355 
  356 
  357     def on_show_keys(self, widget, data=None):
  358         self.enabled = widget.get_active()
  359         if self.enabled:
  360             self.logger.debug("Screenkey enabled.")
  361             self.restart_labelmanager()
  362         else:
  363             self.logger.debug("Screenkey disabled.")
  364             self.labelmngr.stop()
  365 
  366 
  367     def on_preferences_dialog(self, widget=None, data=None):
  368         self.prefs.show()
  369 
  370 
  371     def on_preferences_changed(self, widget=None, data=None):
  372         self.store_state(self.options)
  373         self.prefs.hide()
  374         return True
  375 
  376 
  377     def make_preferences_dialog(self):
  378         # TODO: switch to something declarative or at least clean-up the following mess
  379         self.prefs = prefs = Gtk.Dialog(APP_NAME, None,
  380                                         Gtk.DialogFlags.DESTROY_WITH_PARENT,
  381                                         (Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE),
  382                                         use_header_bar=True,
  383                                         destroy_with_parent=True,
  384                                         resizable=False)
  385         prefs.connect("response", self.on_preferences_changed)
  386         prefs.connect("delete-event", self.on_preferences_changed)
  387 
  388         def on_sb_time_changed(widget, data=None):
  389             self.options.timeout = widget.get_value()
  390             self.logger.debug("Timeout value changed: %f." % self.options.timeout)
  391 
  392         def on_cbox_sizes_changed(widget, data=None):
  393             self.options.font_size = widget.props.active_id
  394             self.update_geometry()
  395             self.logger.debug("Window size changed: %s." % self.options.font_size)
  396 
  397         def on_cbox_modes_changed(widget, data=None):
  398             self.options.key_mode = widget.props.active_id
  399             self.on_change_mode()
  400             self.logger.debug("Key mode changed: %s." % self.options.key_mode)
  401 
  402         def on_cbox_bak_changed(widget, data=None):
  403             self.options.bak_mode = widget.props.active_id
  404             self.on_change_mode()
  405             self.logger.debug("Bak mode changed: %s." % self.options.bak_mode)
  406 
  407         def on_cbox_mods_changed(widget, data=None):
  408             self.options.mods_mode = widget.props.active_id
  409             self.on_change_mode()
  410             self.logger.debug("Mods mode changed: %s." % self.options.mods_mode)
  411 
  412         def on_cbox_modsonly_changed(widget, data=None):
  413             self.options.mods_only = widget.get_active()
  414             self.on_change_mode()
  415             self.logger.debug("Modifiers only changed: %s." % self.options.mods_only)
  416 
  417         def on_cbox_visshift_changed(widget, data=None):
  418             self.options.vis_shift = widget.get_active()
  419             self.on_change_mode()
  420             self.logger.debug("Visible Shift changed: %s." % self.options.vis_shift)
  421 
  422         def on_cbox_visspace_changed(widget, data=None):
  423             self.options.vis_space = widget.get_active()
  424             self.on_change_mode()
  425             self.logger.debug("Show Whitespace changed: %s." % self.options.vis_space)
  426 
  427         def on_cbox_position_changed(widget, data=None):
  428             new_position = widget.props.active_id
  429             if self.options.position != 'fixed' and new_position == 'fixed':
  430                 new_geom = on_btn_sel_geom(widget)
  431                 if not new_geom:
  432                     self.cbox_positions.props.active_id = self.options.position
  433                     return
  434             self.options.position = new_position
  435             self.update_geometry()
  436             self.logger.debug("Window position changed: %s." % self.options.position)
  437 
  438         def on_cbox_screen_changed(widget, data=None):
  439             self.options.screen = widget.get_active()
  440             self.set_active_monitor(self.options.screen)
  441             self.logger.debug("Screen changed: %d." % self.options.screen)
  442 
  443         def on_cbox_persist_changed(widget, data=None):
  444             self.options.persist = widget.get_active()
  445             if not self.get_property('visible'):
  446                 self.show()
  447             else:
  448                 self.on_label_change(self.label.get_text(), True)
  449             self.logger.debug("Persistent changed: %s." % self.options.persist)
  450 
  451         def on_sb_compr_changed(widget, data=None):
  452             self.options.compr_cnt = widget.get_value_as_int()
  453             self.on_change_mode()
  454             self.logger.debug("Compress repeats value changed: %d." % self.options.compr_cnt)
  455 
  456         def on_cbox_compr_changed(widget, data=None):
  457             compr_enabled = widget.get_active()
  458             self.sb_compr.set_sensitive(compr_enabled)
  459             self.options.compr_cnt = self.sb_compr.get_value_as_int() if compr_enabled else 0
  460             self.on_change_mode()
  461             self.logger.debug("Compress repeats value changed: %d." % self.options.compr_cnt)
  462 
  463         def on_btn_sel_geom(widget, data=None):
  464             try:
  465                 ret = subprocess.check_output(['slop', '-f', '%x %y %w %h %i'])
  466             except subprocess.CalledProcessError:
  467                 return False
  468             except OSError:
  469                 msg = Gtk.MessageDialog(parent=self,
  470                                         type=Gtk.MessageType.ERROR,
  471                                         buttons=Gtk.ButtonsType.OK,
  472                                         message_format="Error running \"slop\"")
  473                 msg.format_secondary_markup(_("\"slop\" is required for interactive selection. "
  474                                               "See <a href=\"{url}\">{url}</a>").format(url=SLOP_URL))
  475                 msg.run()
  476                 msg.destroy()
  477                 return False
  478 
  479             ret = ret.decode("utf8")
  480             data = list(map(int, ret.split(' ')))
  481             self.options.geometry = data[0:4]
  482             self.options.window = data[4]
  483             if not self.options.window or \
  484                self.options.window == self.get_screen().get_root_window().get_xid():
  485                 # region selected, switch to fixed
  486                 self.options.window = None
  487                 self.options.position = 'fixed'
  488                 self.cbox_positions.props.active_id = self.options.position
  489 
  490             self.update_geometry()
  491             self.btn_reset_geom.set_sensitive(True)
  492             return True
  493 
  494         def on_btn_reset_geom(widget, data=None):
  495             self.options.geometry = None
  496             if self.options.position == 'fixed':
  497                 self.options.position = 'bottom'
  498                 self.cbox_positions.props.active_id = self.options.position
  499             self.update_geometry()
  500             widget.set_sensitive(False)
  501 
  502         def on_adj_opacity_changed(widget, data=None):
  503             self.options.opacity = widget.get_value()
  504             self.update_colors()
  505 
  506         def on_font_color_changed(widget, data=None):
  507             self.options.font_color = widget.get_color().to_string()
  508             self.update_colors()
  509 
  510         def on_bg_color_changed(widget, data=None):
  511             self.options.bg_color = widget.get_color().to_string()
  512             self.update_colors()
  513 
  514         def on_btn_font(widget, data=None):
  515             widget.props.label = widget.props.font
  516             self.options.font_desc = widget.props.font
  517             self.font = widget.props.font_desc
  518             self.update_font()
  519 
  520         frm_time = Gtk.Frame(label_widget=Gtk.Label("<b>%s</b>" % _("Time"),
  521                                                     use_markup=True),
  522                              border_width=4,
  523                              shadow_type=Gtk.ShadowType.NONE,
  524                              margin=6, hexpand=True)
  525         vbox_time = Gtk.Grid(orientation=VERTICAL,
  526                              row_spacing=6, margin=6)
  527         hbox_time = Gtk.Grid(column_spacing=6)
  528         lbl_time1 = Gtk.Label(_("Display for"))
  529         lbl_time2 = Gtk.Label(_("seconds"))
  530         sb_time = Gtk.SpinButton(digits=1,
  531                                  numeric=True,
  532                                  update_policy=IF_VALID)
  533         sb_time.set_increments(0.5, 1.0)
  534         sb_time.set_range(0, 300)
  535         sb_time.set_value(self.options.timeout)
  536         sb_time.connect("value-changed", on_sb_time_changed)
  537         hbox_time.add(lbl_time1)
  538         hbox_time.add(sb_time)
  539         hbox_time.add(lbl_time2)
  540         vbox_time.add(hbox_time)
  541 
  542         chk_persist = Gtk.CheckButton(_("Persistent window"),
  543                                       active=self.options.persist)
  544         chk_persist.connect("toggled", on_cbox_persist_changed)
  545         vbox_time.add(chk_persist)
  546 
  547         frm_time.add(vbox_time)
  548 
  549         frm_position = Gtk.Frame(label_widget=Gtk.Label("<b>%s</b>" % _("Position"),
  550                                                         use_markup=True),
  551                                  border_width=4,
  552                                  shadow_type=Gtk.ShadowType.NONE,
  553                                  margin=6, hexpand=True)
  554         grid_position = Gtk.Grid(row_spacing=6, column_spacing=6,
  555                                  margin=6)
  556 
  557         lbl_screen = Gtk.Label(_("Screen"),
  558                                halign=START)
  559         cbox_screen = Gtk.ComboBoxText()
  560         scr = self.get_screen()
  561         for i in range(scr.get_n_monitors()):
  562             cbox_screen.insert_text(i, '%d: %s' % (i, scr.get_monitor_plug_name(i)))
  563         cbox_screen.set_active(self.monitor)
  564         cbox_screen.connect("changed", on_cbox_screen_changed)
  565 
  566         lbl_positions = Gtk.Label(_("Position"),
  567                                   halign=START)
  568         self.cbox_positions = Gtk.ComboBoxText(name='position')
  569         for id_, text in POSITIONS.items():
  570             self.cbox_positions.append(id_, text)
  571             if id_ == self.options.position:
  572                 self.cbox_positions.props.active_id = id_
  573         self.cbox_positions.connect("changed", on_cbox_position_changed)
  574 
  575         self.btn_reset_geom = Gtk.Button(_("Reset"))
  576         self.btn_reset_geom.connect("clicked", on_btn_reset_geom)
  577         self.btn_reset_geom.set_sensitive(self.options.geometry is not None)
  578 
  579         hbox_position = Gtk.Grid(column_spacing=6, halign=END)
  580         hbox_position.add(self.cbox_positions)
  581         hbox_position.add(self.btn_reset_geom)
  582 
  583         btn_sel_geom = Gtk.Button(_("Select window/region"),
  584                                   halign=FILL, hexpand=True)
  585         btn_sel_geom.connect("clicked", on_btn_sel_geom)
  586 
  587         grid_position.add(lbl_screen)
  588         grid_position.attach_next_to(cbox_screen, lbl_screen, RIGHT, 1, 1)
  589         grid_position.attach_next_to(lbl_positions, lbl_screen, BOTTOM, 1, 1)
  590         grid_position.attach_next_to(hbox_position, lbl_positions, RIGHT, 1, 1)
  591         grid_position.attach_next_to(btn_sel_geom, lbl_positions, BOTTOM, 2, 1)
  592 
  593         frm_aspect = Gtk.Frame(label_widget=Gtk.Label("<b>%s</b>" % _("Font"),
  594                                                       use_markup=True),
  595                                border_width=4,
  596                                shadow_type=Gtk.ShadowType.NONE,
  597                                margin=6, hexpand=True)
  598         grid_aspect = Gtk.Grid(row_spacing=6, column_spacing=6,
  599                                margin=6)
  600 
  601         frm_position.add(grid_position)
  602 
  603         lbl_font = Gtk.Label(_("Font"),
  604                              hexpand=True, halign=START)
  605         btn_font = Gtk.FontButton(self.options.font_desc,
  606                                   font=self.options.font_desc,
  607                                   use_font=True, show_size=False)
  608         if Gtk.check_version(3, 23, 0) is None:
  609             btn_font.set_level(Gtk.FontChooserLevel.STYLE)
  610         btn_font.connect("font-set", on_btn_font)
  611 
  612         lbl_sizes = Gtk.Label(_("Size"),
  613                               halign=START)
  614         cbox_sizes = Gtk.ComboBoxText(name='size')
  615         for id_, text in FONT_SIZES.items():
  616             cbox_sizes.append(id_, text)
  617             if id_ == self.options.font_size:
  618                 cbox_sizes.props.active_id = id_
  619         cbox_sizes.connect("changed", on_cbox_sizes_changed)
  620 
  621         grid_aspect.add(lbl_font)
  622         grid_aspect.attach_next_to(btn_font, lbl_font, RIGHT, 1, 1)
  623         grid_aspect.attach_next_to(lbl_sizes, lbl_font, BOTTOM, 1, 1)
  624         grid_aspect.attach_next_to(cbox_sizes, lbl_sizes, RIGHT, 1, 1)
  625         frm_aspect.add(grid_aspect)
  626 
  627         frm_kbd = Gtk.Frame(label_widget=Gtk.Label("<b>%s</b>" % _("Keys"),
  628                                                    use_markup=True),
  629                             border_width=4,
  630                             shadow_type=Gtk.ShadowType.NONE,
  631                             margin=6)
  632         grid_kbd = Gtk.Grid(row_spacing=6, column_spacing=6,
  633                             margin=6)
  634 
  635         lbl_modes = Gtk.Label(_("Keyboard mode"),
  636                               halign=START)
  637         cbox_modes = Gtk.ComboBoxText(name='mode')
  638         for id_, text in KEY_MODES.items():
  639             cbox_modes.append(id_, text)
  640             if id_ == self.options.key_mode:
  641                 cbox_modes.props.active_id = id_
  642         cbox_modes.connect("changed", on_cbox_modes_changed)
  643 
  644         lbl_bak = Gtk.Label(_("Backspace mode"),
  645                             halign=START)
  646         cbox_bak = Gtk.ComboBoxText()
  647         for id_, text in BAK_MODES.items():
  648             cbox_bak.append(id_, text)
  649             if id_ == self.options.bak_mode:
  650                 cbox_bak.props.active_id = id_
  651         cbox_bak.connect("changed", on_cbox_bak_changed)
  652 
  653         lbl_mods = Gtk.Label(_("Modifiers mode"),
  654                              halign=START)
  655         cbox_mods = Gtk.ComboBoxText()
  656         for id_, text in MODS_MODES.items():
  657             cbox_mods.append(id_, text)
  658             if id_ == self.options.mods_mode:
  659                 cbox_mods.props.active_id = id_
  660         cbox_mods.connect("changed", on_cbox_mods_changed)
  661 
  662         chk_modsonly = Gtk.CheckButton(_("Show Modifier sequences only"),
  663                                        active=self.options.mods_only)
  664         chk_modsonly.connect("toggled", on_cbox_modsonly_changed)
  665 
  666         chk_visshift = Gtk.CheckButton(_("Always show Shift"),
  667                                        active=self.options.vis_shift)
  668         chk_visshift.connect("toggled", on_cbox_visshift_changed)
  669 
  670         chk_visspace = Gtk.CheckButton(_("Show Whitespace characters"),
  671                                        active=self.options.vis_space)
  672         chk_visspace.connect("toggled", on_cbox_visspace_changed)
  673 
  674         hbox_compr = Gtk.Grid(column_spacing=6)
  675         chk_compr = Gtk.CheckButton(_("Compress repeats after"),
  676                                     active=self.options.compr_cnt > 0)
  677         chk_compr.connect("toggled", on_cbox_compr_changed)
  678         self.sb_compr = Gtk.SpinButton(digits=0,
  679                                        numeric=True,
  680                                        update_policy=IF_VALID,
  681                                        value=self.options.compr_cnt or 3)
  682         self.sb_compr.set_increments(1, 1)
  683         self.sb_compr.set_range(1, 100)
  684         self.sb_compr.connect("value-changed", on_sb_compr_changed)
  685         hbox_compr.add(chk_compr)
  686         hbox_compr.add(self.sb_compr)
  687 
  688         grid_kbd.add(lbl_modes)
  689         grid_kbd.attach_next_to(cbox_modes, lbl_modes, RIGHT, 1, 1)
  690         grid_kbd.attach_next_to(lbl_bak, lbl_modes, BOTTOM, 1, 1)
  691         grid_kbd.attach_next_to(cbox_bak, lbl_bak, RIGHT, 1, 1)
  692         grid_kbd.attach_next_to(lbl_mods, lbl_bak, BOTTOM, 1, 1)
  693         grid_kbd.attach_next_to(cbox_mods, lbl_mods, RIGHT, 1, 1)
  694         grid_kbd.attach_next_to(chk_modsonly, lbl_mods, BOTTOM, 2, 1)
  695         grid_kbd.attach_next_to(chk_visshift, chk_modsonly, BOTTOM, 2, 1)
  696         grid_kbd.attach_next_to(chk_visspace, chk_visshift, BOTTOM, 2, 1)
  697         grid_kbd.attach_next_to(hbox_compr, chk_visspace, BOTTOM, 2, 1)
  698         frm_kbd.add(grid_kbd)
  699 
  700         frm_color = Gtk.Frame(label_widget=Gtk.Label("<b>%s</b>" % _("Color"),
  701                                                      use_markup=True),
  702                               border_width=4,
  703                               shadow_type=Gtk.ShadowType.NONE,
  704                               margin=6)
  705         grid_color = Gtk.Grid(orientation=VERTICAL,
  706                               row_spacing=6, column_spacing=6,
  707                               margin=6)
  708 
  709         lbl_font_color = Gtk.Label(_("Font color"),
  710                                    halign=START)
  711         btn_font_color = Gtk.ColorButton(color=Gdk.color_parse(self.options.font_color),
  712                                          title=_("Text color"),
  713                                          halign=END)
  714         btn_font_color.connect("color-set", on_font_color_changed)
  715 
  716         lbl_bg_color = Gtk.Label(_("Background color"),
  717                                  halign=START)
  718         btn_bg_color = Gtk.ColorButton(color=Gdk.color_parse(self.options.bg_color),
  719                                        title=_("Background color"),
  720                                        halign=END)
  721         btn_bg_color.connect("color-set", on_bg_color_changed)
  722 
  723         lbl_opacity = Gtk.Label(_("Opacity"),
  724                                 halign=START)
  725         adj_opacity = Gtk.Adjustment(self.options.opacity, 0, 1.0, 0.1, 0, 0)
  726         adj_opacity.connect("value-changed", on_adj_opacity_changed)
  727         adj_scale = Gtk.Scale(adjustment=adj_opacity,
  728                               hexpand=True, halign=FILL)
  729 
  730         grid_color.add(lbl_font_color)
  731         grid_color.attach_next_to(btn_font_color, lbl_font_color, RIGHT, 1, 1)
  732         grid_color.attach_next_to(lbl_bg_color, lbl_font_color, BOTTOM, 1, 1)
  733         grid_color.attach_next_to(btn_bg_color, lbl_bg_color, RIGHT, 1, 1)
  734         grid_color.attach_next_to(lbl_opacity, lbl_bg_color, BOTTOM, 1, 1)
  735         grid_color.attach_next_to(adj_scale, lbl_opacity, RIGHT, 1, 1)
  736         frm_color.add(grid_color)
  737 
  738         hbox_main = Gtk.Grid(column_homogeneous=True)
  739         vbox_main = Gtk.Grid(orientation=VERTICAL)
  740         vbox_main.add(frm_time)
  741         vbox_main.add(frm_position)
  742         vbox_main.add(frm_aspect)
  743         hbox_main.add(vbox_main)
  744         vbox_main = Gtk.Grid(orientation=VERTICAL)
  745         vbox_main.add(frm_kbd)
  746         vbox_main.add(frm_color)
  747         hbox_main.add(vbox_main)
  748 
  749         box = prefs.get_content_area()
  750         box.add(hbox_main)
  751         box.show_all()
  752 
  753 
  754     def make_menu(self):
  755         self.menu = menu = Gtk.Menu()
  756 
  757         show_item = Gtk.CheckMenuItem(_("Show keys"))
  758         show_item.set_active(True)
  759         show_item.connect("toggled", self.on_show_keys)
  760         show_item.show()
  761         menu.append(show_item)
  762 
  763         preferences_item = Gtk.MenuItem(_("Preferences"))
  764         preferences_item.connect("activate", self.on_preferences_dialog)
  765         preferences_item.show()
  766         menu.append(preferences_item)
  767 
  768         about_item = Gtk.MenuItem(_("About"))
  769         about_item.connect("activate", self.on_about_dialog)
  770         about_item.show()
  771         menu.append(about_item)
  772 
  773         separator_item = Gtk.SeparatorMenuItem()
  774         separator_item.show()
  775         menu.append(separator_item)
  776 
  777         image = Gtk.MenuItem(_("Quit"))
  778         image.connect("activate", self.quit)
  779         image.show()
  780         menu.append(image)
  781         menu.show()
  782 
  783 
  784     def make_appindicator(self):
  785         from gi.repository import AppIndicator3 as AppIndicator
  786         self.systray = AppIndicator.Indicator.new(
  787             APP_NAME, 'indicator-messages', AppIndicator.IndicatorCategory.APPLICATION_STATUS)
  788         self.systray.set_status(AppIndicator.IndicatorStatus.ACTIVE)
  789         self.systray.set_attention_icon("indicator-messages-new")
  790         self.systray.set_icon("preferences-desktop-keyboard-shortcuts")
  791         self.systray.set_menu(self.menu)
  792         self.logger.debug("Using AppIndicator.")
  793 
  794     def make_systray(self):
  795         self.systray = Gtk.StatusIcon()
  796         self.systray.set_from_icon_name("preferences-desktop-keyboard-shortcuts")
  797         self.systray.connect("popup-menu", self.on_statusicon_popup, self.menu)
  798         self.logger.debug("Using StatusIcon.")
  799 
  800 
  801     def make_about_dialog(self):
  802         self.about = about = Gtk.AboutDialog()
  803         about.set_program_name(APP_NAME)
  804         about.set_version(VERSION)
  805         about.set_copyright("""
  806         Copyright(c) 2010-2012: Pablo Seminario <pabluk@gmail.com>
  807         Copyright(c) 2015-2020: wave++ "Yuri D'Elia" <wavexx@thregr.org>
  808         Copyright(c) 2019-2020: Yuto Tokunaga <yuntan.sub1@gmail.com>
  809         """)
  810         about.set_comments(APP_DESC)
  811         about.set_documenters(
  812             ["José María Quiroga <pepelandia@gmail.com>"]
  813         )
  814         about.set_website(APP_URL)
  815         about.set_icon_name('preferences-desktop-keyboard-shortcuts')
  816         about.set_logo_icon_name('preferences-desktop-keyboard-shortcuts')
  817         about.connect("response", lambda *_: about.hide_on_delete())
  818         about.connect("delete-event", lambda *_: about.hide_on_delete())
  819 
  820 
  821     def on_about_dialog(self, widget, data=None):
  822         self.about.show()
  823 
  824 
  825     def run(self):
  826         Gtk.main()
  827         return self.exit_status