diff options
author | Eitan Isaacson <eitan@monotonous.org> | 2011-04-24 16:57:28 -0700 |
---|---|---|
committer | Eitan Isaacson <eitan@monotonous.org> | 2011-05-02 10:21:08 -0700 |
commit | e1ae6ee91507a47cbe2fe537f3107c17390bc0e2 (patch) | |
tree | 04ea360d747c6e489e50c9649fe33cd6723f83a8 /caribou | |
parent | f195b7aafa878568e7aae8ccc27e3716ae7c21cc (diff) | |
download | caribou-e1ae6ee91507a47cbe2fe537f3107c17390bc0e2.tar.gz |
Major re-work of Python modules:
* Created Antler to use new keyboard model via PyGI
* Rearanged settings to accomodate view/model seperation.
* Created in preferences executable, it will be a standalone
generic caribou settings UI where we will eventually get the important
stuff in GNOME's control panel.
* DBusified the UI.
Diffstat (limited to 'caribou')
27 files changed, 604 insertions, 1430 deletions
diff --git a/caribou/Makefile.am b/caribou/Makefile.am index 962a2b4..713d8ad 100644 --- a/caribou/Makefile.am +++ b/caribou/Makefile.am @@ -1,11 +1,15 @@ cariboudir = $(pkgpythondir)/ caribou_PYTHON = \ - __init__.py + __init__.py \ + i18n.py SUBDIRS = \ - common/ \ - ui/ + antler/ \ + settings/ \ + daemon/ + +DISTCLEANFILES = i18n.py clean-local: rm -rf *.pyc *.pyo diff --git a/caribou/__init__.py b/caribou/__init__.py index 1aadcdc..cf8fdc7 100644 --- a/caribou/__init__.py +++ b/caribou/__init__.py @@ -1 +1,2 @@ -data_path = "data/" +from i18n import _ +APP_NAME=_("Caribou") diff --git a/caribou/antler/Makefile.am b/caribou/antler/Makefile.am new file mode 100644 index 0000000..51efe0f --- /dev/null +++ b/caribou/antler/Makefile.am @@ -0,0 +1,10 @@ +caribou_antlerdir = $(pkgpythondir)/antler/ + +caribou_antler_PYTHON = \ + __init__.py \ + keyboard_view.py \ + main.py \ + window.py + +clean-local: + rm -rf *.pyc *.pyo diff --git a/caribou/antler/__init__.py b/caribou/antler/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/caribou/antler/__init__.py diff --git a/caribou/antler/keyboard_view.py b/caribou/antler/keyboard_view.py new file mode 100644 index 0000000..6cea11b --- /dev/null +++ b/caribou/antler/keyboard_view.py @@ -0,0 +1,164 @@ +from caribou.settings.preferences_window import PreferencesDialog +from caribou.settings import CaribouSettings +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import Caribou +import gobject + +PRETTY_LABELS = { + "BackSpace" : u'\u232b', + "space" : u' ', + "Return" : u'\u23ce', + 'Caribou_Prefs' : u'\u2328', + 'Caribou_ShiftUp' : u'\u2b06', + 'Caribou_ShiftDown' : u'\u2b07', + 'Caribou_Emoticons' : u'\u263a', + 'Caribou_Symbols' : u'123', + 'Caribou_Symbols_More' : u'{#*', + 'Caribou_Alpha' : u'Abc' +} + +class AntlerKey(Gtk.Button): + def __init__(self, key): + gobject.GObject.__init__(self) + self.caribou_key = key + self.connect("pressed", self._on_pressed) + self.connect("released", self._on_released) + self.set_label(self._get_key_label()) + if key.props.name == "Caribou_Prefs": + key.connect("key-clicked", self._on_prefs_clicked) + if key.get_extended_keys (): + self._sublevel = AntlerSubLevel(self) + + def _on_prefs_clicked(self, key): + p = PreferencesDialog(CaribouSettings()) + p.show_all() + p.run() + p.destroy() + + def _get_key_label(self): + label = self.caribou_key.props.name + if PRETTY_LABELS.has_key(self.caribou_key.props.name): + label = PRETTY_LABELS[self.caribou_key.props.name] + elif self.caribou_key.props.name.startswith('Caribou_'): + label = self.caribou_key.name.replace('Caribou_', '') + else: + unichar = unichr(Gdk.keyval_to_unicode(self.caribou_key.props.keyval)) + if not unichar.isspace() and unichar != u'\x00': + label = unichar + + return label + + def _on_pressed(self, button): + self.caribou_key.press() + + def _on_released(self, button): + self.caribou_key.release() + + def do_get_preferred_width_for_height(self, w): + return (w, w) + + def do_get_request_mode(self): + return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH + +class AntlerSubLevel(Gtk.Window): + def __init__(self, key): + gobject.GObject.__init__(self, type=Gtk.WindowType.POPUP) + + self.set_decorated(False) + self.set_resizable(False) + self.set_accept_focus(False) + self.set_position(Gtk.WindowPosition.MOUSE) + self.set_type_hint(Gdk.WindowTypeHint.DIALOG) + + key.caribou_key.connect("notify::show-subkeys", self._on_show_subkeys) + self._key = key + + layout = AntlerLayout() + layout.add_row(key.caribou_key.get_extended_keys()) + self.add(layout) + + def _on_show_subkeys(self, key, prop): + parent = self._key.get_toplevel() + if key.props.show_subkeys: + self.set_transient_for(parent) + parent.set_sensitive(False) + self.show_all() + else: + parent.set_sensitive(True) + self.hide() + +class AntlerLayout(Gtk.Grid): + KEY_SPAN = 4 + + def __init__(self, level=None): + gobject.GObject.__init__(self) + self.set_column_homogeneous(True) + self.set_row_homogeneous(True) + self.set_row_spacing(4) + self.set_column_spacing(4) + if level: + self.load_rows(level.get_rows ()) + + def add_row(self, row, row_num=0): + col_num = 0 + for i, key in enumerate(row): + antler_key = AntlerKey(key) + self.attach(antler_key, + col_num + int(key.props.margin_left * self.KEY_SPAN), + row_num * self.KEY_SPAN, + int(self.KEY_SPAN * key.props.width), + self.KEY_SPAN) + col_num += int((key.props.width + key.props.margin_left ) * self.KEY_SPAN) + + + def load_rows(self, rows): + for row_num, row in enumerate(rows): + self.add_row(row.get_keys(), row_num) + +class AntlerKeyboardView(Gtk.Notebook): + def __init__(self): + gobject.GObject.__init__(self) + self.set_show_tabs(False) + self.keyboard_model = Caribou.KeyboardModel() + self.keyboard_model.connect("notify::active-group", self._on_group_changed) + self.layers = {} + for gname in self.keyboard_model.get_groups(): + group = self.keyboard_model.get_group(gname) + self.layers[gname] = {} + group.connect("notify::active-level", self._on_level_changed) + for lname in group.get_levels(): + level = group.get_level(lname) + layout = AntlerLayout(level) + layout.show() + self.layers[gname][lname] = self.append_page(layout, None) + + self._set_to_active_layer() + + def _on_level_changed(self, group, prop): + self._set_to_active_layer() + + def _on_group_changed(self, kb, prop): + self._set_to_active_layer() + + def _set_to_active_layer(self): + active_group_name = self.keyboard_model.props.active_group + active_group = self.keyboard_model.get_group(active_group_name) + active_level_name = active_group.props.active_level + + self.set_current_page(self.layers[active_group_name][active_level_name]) + + +if __name__ == "__main__": + import signal + signal.signal(signal.SIGINT, signal.SIG_DFL) + + w = Gtk.Window() + w.set_accept_focus(False) + + kb = AntlerKeyboardView() + w.add(kb) + + w.show_all() + + Gtk.main() diff --git a/caribou/antler/main.py b/caribou/antler/main.py new file mode 100644 index 0000000..ce0eca3 --- /dev/null +++ b/caribou/antler/main.py @@ -0,0 +1,30 @@ +from gi.repository import Caribou +from window import AntlerWindowEntry +from keyboard_view import AntlerKeyboardView +import gobject + +class AntlerKeyboardService(Caribou.KeyboardService): + def __init__(self): + gobject.GObject.__init__(self) + self.register_keyboard("Antler") + self.window = AntlerWindowEntry(AntlerKeyboardView()) + + def run(self): + loop = gobject.MainLoop() + loop.run() + + def do_show(self): + self.window.show_all() + + def do_hide(self): + self.window.hide() + + def do_set_cursor_location (self, x, y, w, h): + self.window.set_cursor_location(x, y, w, h) + + def do_set_entry_location (self, x, y, w, h): + self.window.set_entry_location(x, y, w, h) + +if __name__ == "__main__": + antler_keyboard_service = AntlerKeyboardService() + antler_keyboard_service.run() diff --git a/caribou/ui/window.py b/caribou/antler/window.py index 2dc7423..973824a 100644 --- a/caribou/ui/window.py +++ b/caribou/antler/window.py @@ -20,9 +20,6 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -from caribou import data_path -from opacity import ProximityWindowBase - from gi.repository import Gtk from gi.repository import Gdk from gi.repository import Clutter @@ -30,10 +27,71 @@ import os import sys import gobject -Clutter.init("caribou") +Clutter.init("antler") + +class ProximityWindowBase(object): + def __init__(self, min_alpha=1.0, max_alpha=1.0, max_distance=100): + if self.__class__ == ProximityWindowBase: + raise TypeError, \ + "ProximityWindowBase is an abstract class, " \ + "must be subclassed with a Gtk.Window" + self.connect('map-event', self.__onmapped) + self.max_distance = max_distance + if max_alpha < min_alpha: + raise ValueError, "min_alpha can't be larger than max_alpha" + self.min_alpha = min_alpha + self.max_alpha = max_alpha + + def __onmapped(self, obj, event): + if self.is_composited(): + self.set_opacity(self.max_alpha) + if self.max_alpha != self.min_alpha: + # Don't waste CPU if the max and min are equal. + glib.timeout_add(80, self._proximity_check) + + def _proximity_check(self): + px, py = self.get_pointer() + + ww = self.get_allocated_width() + wh = self.get_allocated_height() + + distance = self._get_distance_to_bbox(px, py, ww, wh) -class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase): - __gtype_name__ = "CaribouWindow" + opacity = (self.max_alpha - self.min_alpha) * \ + (1 - min(distance, self.max_distance)/self.max_distance) + opacity += self.min_alpha + + self.set_opacity(opacity) + return self.props.visible + + def _get_distance_to_bbox(self, px, py, bw, bh): + if px < 0: + x_distance = float(abs(px)) + elif px > bw: + x_distance = float(px - bw) + else: + x_distance = 0.0 + + if py < 0: + y_distance = float(abs(px)) + elif py > bh: + y_distance = float(py - bh) + else: + y_distance = 0.0 + + if y_distance == 0 and x_distance == 0: + return 0.0 + elif y_distance != 0 and x_distance == 0: + return y_distance + elif y_distance == 0 and x_distance != 0: + return x_distance + else: + x2 = 0 if x_distance > 0 else bw + y2 = 0 if y_distance > 0 else bh + return sqrt((px - x2)**2 + (py - y2)**2) + +class AntlerWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase): + __gtype_name__ = "AntlerWindow" __gproperties__ = { 'animated-window-position' : (gobject.TYPE_PYOBJECT, 'Window position', 'Window position in X, Y coordinates', @@ -49,7 +107,7 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase): max_alpha=max_alpha, max_distance=max_distance) - self.set_name("CaribouWindow") + self.set_name("AntlerWindow") self._vbox = Gtk.VBox() self.add(self._vbox) @@ -61,7 +119,7 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase): self._cursor_location = Rectangle() self._entry_location = Rectangle() self._default_placement = default_placement or \ - CaribouWindowPlacement() + AntlerWindowPlacement() self.connect('show', self._on_window_show) @@ -111,12 +169,12 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase): self.keyboard.destroy() super(Gtk.Window, self).destroy() - def set_cursor_location(self, cursor_location): - self._cursor_location = cursor_location + def set_cursor_location(self, x, y, w, h): + self._cursor_location = Rectangle(x, y, w, h) self._update_position() - def set_entry_location(self, entry_location): - self._entry_location = entry_location + def set_entry_location(self, x, y, w, h): + self._entry_location = Rectangle(x, y, w, h) self._update_position() def set_default_placement(self, default_placement): @@ -174,25 +232,25 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase): def _calculate_axis(self, axis_placement, root_bbox): bbox = root_bbox - if axis_placement.stickto == CaribouWindowPlacement.CURSOR: + if axis_placement.stickto == AntlerWindowPlacement.CURSOR: bbox = self._cursor_location - elif axis_placement.stickto == CaribouWindowPlacement.ENTRY: + elif axis_placement.stickto == AntlerWindowPlacement.ENTRY: bbox = self._entry_location offset = axis_placement.get_offset(bbox.x, bbox.y) - if axis_placement.align == CaribouWindowPlacement.END: + if axis_placement.align == AntlerWindowPlacement.END: offset += axis_placement.get_length(bbox.width, bbox.height) - if axis_placement.gravitate == CaribouWindowPlacement.INSIDE: + if axis_placement.gravitate == AntlerWindowPlacement.INSIDE: offset -= axis_placement.get_length( self.get_allocated_width(), self.get_allocated_height()) - elif axis_placement.align == CaribouWindowPlacement.START: - if axis_placement.gravitate == CaribouWindowPlacement.OUTSIDE: + elif axis_placement.align == AntlerWindowPlacement.START: + if axis_placement.gravitate == AntlerWindowPlacement.OUTSIDE: offset -= axis_placement.get_length( self.get_allocated_width(), self.get_allocated_height()) - elif axis_placement.align == CaribouWindowPlacement.CENTER: + elif axis_placement.align == AntlerWindowPlacement.CENTER: offset += axis_placement.get_length(bbox.width, bbox.height)/2 return offset @@ -211,18 +269,18 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase): req = child.size_request() self.resize(req.width + border, req.height + border) -class CaribouWindowDocked(CaribouWindow): - __gtype_name__ = "CaribouWindowDocked" +class AntlerWindowDocked(AntlerWindow): + __gtype_name__ = "AntlerWindowDocked" def __init__(self, text_entry_mech): - placement = CaribouWindowPlacement( - xalign=CaribouWindowPlacement.END, - yalign=CaribouWindowPlacement.START, - xstickto=CaribouWindowPlacement.SCREEN, - ystickto=CaribouWindowPlacement.SCREEN, - xgravitate=CaribouWindowPlacement.INSIDE) + placement = AntlerWindowPlacement( + xalign=AntlerWindowPlacement.END, + yalign=AntlerWindowPlacement.START, + xstickto=AntlerWindowPlacement.SCREEN, + ystickto=AntlerWindowPlacement.SCREEN, + xgravitate=AntlerWindowPlacement.INSIDE) - CaribouWindow.__init__(self, text_entry_mech, placement) + AntlerWindow.__init__(self, text_entry_mech, placement) self.connect('map-event', self.__onmapped) @@ -240,32 +298,32 @@ class CaribouWindowDocked(CaribouWindow): def hide(self): animation = self._roll_out() - animation.connect('completed', lambda x: CaribouWindow.hide(self)) + animation.connect('completed', lambda x: AntlerWindow.hide(self)) -class CaribouWindowEntry(CaribouWindow): - __gtype_name__ = "CaribouWindowEntry" +class AntlerWindowEntry(AntlerWindow): + __gtype_name__ = "AntlerWindowEntry" def __init__(self, text_entry_mech): - placement = CaribouWindowPlacement( - xalign=CaribouWindowPlacement.START, - xstickto=CaribouWindowPlacement.ENTRY, - ystickto=CaribouWindowPlacement.ENTRY, - xgravitate=CaribouWindowPlacement.INSIDE, - ygravitate=CaribouWindowPlacement.OUTSIDE) + placement = AntlerWindowPlacement( + xalign=AntlerWindowPlacement.START, + xstickto=AntlerWindowPlacement.ENTRY, + ystickto=AntlerWindowPlacement.ENTRY, + xgravitate=AntlerWindowPlacement.INSIDE, + ygravitate=AntlerWindowPlacement.OUTSIDE) - CaribouWindow.__init__(self, text_entry_mech, placement) + AntlerWindow.__init__(self, text_entry_mech, placement) def _calculate_axis(self, axis_placement, root_bbox): - offset = CaribouWindow._calculate_axis(self, axis_placement, root_bbox) + offset = AntlerWindow._calculate_axis(self, axis_placement, root_bbox) if axis_placement.axis == 'y': if offset + self.get_allocated_height() > root_bbox.height + root_bbox.y: - new_axis_placement = axis_placement.copy(align=CaribouWindowPlacement.START) - offset = CaribouWindow._calculate_axis(self, new_axis_placement, root_bbox) + new_axis_placement = axis_placement.copy(align=AntlerWindowPlacement.START) + offset = AntlerWindow._calculate_axis(self, new_axis_placement, root_bbox) return offset -class CaribouWindowPlacement(object): +class AntlerWindowPlacement(object): START = 'start' END = 'end' CENTER = 'center' @@ -326,7 +384,6 @@ class CaribouWindowPlacement(object): ystickto or self.CURSOR, ygravitate or self.OUTSIDE) - class Rectangle(object): def __init__(self, x=0, y=0, width=0, height=0): self.x = x @@ -335,11 +392,11 @@ class Rectangle(object): self.height = height if __name__ == "__main__": - import keyboard + import keyboard_view import signal signal.signal(signal.SIGINT, signal.SIG_DFL) - w = CaribouWindowDocked(keyboard.CaribouKeyboard()) + w = AntlerWindowDocked(keyboard_view.AntlerKeyboardView()) w.show_all() try: diff --git a/caribou/common/Makefile.am b/caribou/common/Makefile.am deleted file mode 100644 index 74805af..0000000 --- a/caribou/common/Makefile.am +++ /dev/null @@ -1,11 +0,0 @@ -caribou_commondir = $(pkgpythondir)/common/ - -caribou_common_PYTHON = \ - __init__.py \ - const.py \ - settings_manager.py \ - settings.py \ - setting_types.py - -clean-local: - rm -rf *.pyc *.pyo diff --git a/caribou/common/__init__.py b/caribou/common/__init__.py deleted file mode 100644 index 8d1c8b6..0000000 --- a/caribou/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/caribou/common/const.py b/caribou/common/const.py deleted file mode 100644 index 0591e5d..0000000 --- a/caribou/common/const.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Caribou - text entry and UI navigation application -# -# Copyright (C) 2010 Warp Networks S.L. -# * Contributor: David Pellicer <dpellicer@warp.es> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by the -# Free Software Foundation; either version 2.1 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License -# for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -from os.path import join -from os.path import dirname - -from caribou import data_path - -# Application name -APP_NAME = 'Caribou' -APP_SLUG_NAME = 'caribou' - -# Paths -DATA_DIR = data_path -LAYOUTS_DIR = join(DATA_DIR, 'layouts') - -# Preferences -CARIBOU_GCONF = '/org/gnome/caribou/osk' - -# Key types -NORMAL_KEY_TYPE = 'normal' -LAYOUT_SWITCHER_KEY_TYPE = 'layout_switcher' -PREFERENCES_KEY_TYPE = 'preferences' -DUMMY_KEY_TYPE = 'dummy' -MASK_KEY_TYPE = 'mask' diff --git a/caribou/common/settings.py b/caribou/common/settings.py deleted file mode 100644 index 7837508..0000000 --- a/caribou/common/settings.py +++ /dev/null @@ -1,161 +0,0 @@ -import os -from setting_types import * -from gettext import gettext as _ -import caribou.common.const as const -import caribou.ui.i18n -import xml.dom.minidom -import json - -GSETTINGS_SCHEMA = "org.gnome.caribou" - -settings = SettingsGroup("_top", "", [ - SettingsGroup("keyboard", _("Keyboard"), [ - SettingsGroup("general", _("General"), [ - StringSetting( - "geometry", _("Keyboard geometry"), "natural", - _("The keyboard geometery Caribou should use"), - _("The keyboard geometery determines the shape " - "and complexity of the keyboard, it could range from " - "a 'natural' look and feel good for composing simple " - "text, to a fullscale keyboard."), - allowed=[(('natural'), _('Natural'))])]), - SettingsGroup("color", _("Color"), [ - BooleanSetting( - "default_colors", _("Use system theme"), True, - _("Use the default theme colors"), - insensitive_when_true=["normal_color", - "mouse_over_color"]), - ColorSetting( - "normal_color", _("Normal state"), "grey80", - _("Color of the keys when there is no " - "event on them")), - ColorSetting( - "mouse_over_color", _("Mouse over"), "yellow", - _("Color of the keys when the mouse goes " - "over the key"))]), - SettingsGroup("fontandsize", _("Font and size"), [ - BooleanSetting( - "default_font", _("Use system fonts"), True, - _("Use the default system font for keyboard"), - insensitive_when_true=["key_font"]), - FontSetting("key_font", _("Key font"), "Sans 12", - _("Custom font for keyboard")) - ]) - ]), - SettingsGroup("scanning", _("Scanning"), [ - BooleanSetting( - "scan_enabled", _("Enable scanning"), False, - _("Enable switch scanning"), - insensitive_when_false=["scanning_general", - "scanning_input", - "scanning_color"]), - SettingsGroup("scanning_general", _("General"), [ - StringSetting("scanning_type", _("Scanning mode"), - "block", - _("Scanning type, block or row"), - allowed=[("block", _("Block")), - ("row", _("Row"))]), - FloatSetting("step_time", _("Step time"), 1.0, - _("Time between key transitions"), - min=0.1, max=60.0), - BooleanSetting("reverse_scanning", - _("Reverse scanning"), False, - _("Scan in reverse order")) - ]), - SettingsGroup("scanning_input", _("Input"), [ - StringSetting("switch_type", _("Switch device"), - "keyboard", - _("Switch device, keyboard or mouse"), - entry_type=ENTRY_RADIO, - allowed=[("keyboard", _("Keyboard")), - ("mouse", _("Mouse"))], - children=[ - StringSetting("keyboard_key", _("Switch key"), - "Shift_R", - _( - "Key to use with scanning mode"), - allowed=[ - ("Shift_R", _("Right shift")), - ("Shift_L", _("Left shift")), - ("ISO_Level3_Shift", _("Alt Gr")), - ("Num_Lock", _("Num lock"))]), - StringSetting("mouse_button", _("Switch button"), - "2", - _( - "Mouse button to use in the scanning " - "mode"), - allowed=[("1", _("Button 1")), - ("2", _("Button 2")), - ("3", _("Button 3"))]) - ]), - ]), - SettingsGroup("scanning_color", _("Color"), [ - ColorSetting("block_scanning_color", _("Block color"), - "purple", _("Color of block scans")), - ColorSetting("row_scanning_color", _("Row color"), - "green", _("Color of row scans")), - ColorSetting("button_scanning_color", _("Key color"), - "cyan", _("Color of key scans")), - ColorSetting("cancel_scanning_color", - _("Cancel color"), - "red", _("Color of cancel scan")) - ]) - ]) - ]) - -if __name__ == "__main__": - from gi.repository import GLib - - class SchemasMaker: - def create_schemas(self): - doc = xml.dom.minidom.Document() - schemafile = doc.createElement('schemalist') - schema = doc.createElement('schema') - schema.setAttribute("id", GSETTINGS_SCHEMA) - schema.setAttribute("path", "/org/gnome/caribou/osk/") - schemafile.appendChild(schema) - self._create_schema(settings, doc, schema) - - self._pretty_xml(schemafile) - - def _attribs(self, e): - if not e.attributes.items(): - return "" - return ' ' + ' '.join(['%s="%s"' % (k,v) \ - for k,v in e.attributes.items()]) - - def _pretty_xml(self, e, indent=0): - if not e.childNodes or \ - (len(e.childNodes) == 1 and \ - e.firstChild.nodeType == e.TEXT_NODE): - print '%s%s' % (' '*indent*2, e.toxml().strip()) - else: - print '%s<%s%s>' % (' '*indent*2, e.tagName, self._attribs(e)) - for c in e.childNodes: - self._pretty_xml(c, indent + 1) - print '%s</%s>' % (' '*indent*2, e.tagName) - - def _append_children_element_value_pairs(self, doc, element, pairs): - for e, t in pairs: - el = doc.createElement(e) - te = doc.createTextNode(str(t)) - el.appendChild(te) - element.appendChild(el) - - def _create_schema(self, setting, doc, schemalist): - if hasattr(setting, 'gsettings_key'): - key = doc.createElement('key') - key.setAttribute('name', setting.gsettings_key) - key.setAttribute('type', setting.variant_type) - schemalist.appendChild(key) - self._append_children_element_value_pairs( - doc, key, [('default', - getattr(setting.gvariant, "print")(False)), - ('_summary', setting.short_desc), - ('_description', setting.long_desc)]) - - for s in setting: - self._create_schema(s, doc, schemalist) - - maker = SchemasMaker() - maker.create_schemas() diff --git a/caribou/daemon/Makefile.am b/caribou/daemon/Makefile.am new file mode 100644 index 0000000..859c29e --- /dev/null +++ b/caribou/daemon/Makefile.am @@ -0,0 +1,8 @@ +caribou_daemondir = $(pkgpythondir)/daemon/ + +caribou_daemon_PYTHON = \ + __init__.py \ + main.py + +clean-local: + rm -rf *.pyc *.pyo diff --git a/caribou/daemon/__init__.py b/caribou/daemon/__init__.py new file mode 100644 index 0000000..fd9812d --- /dev/null +++ b/caribou/daemon/__init__.py @@ -0,0 +1 @@ +from main import CaribouDaemon diff --git a/caribou/daemon/main.py b/caribou/daemon/main.py new file mode 100644 index 0000000..b9df5cf --- /dev/null +++ b/caribou/daemon/main.py @@ -0,0 +1,150 @@ +import pyatspi +import dbus +from gi.repository import Gio +from string import Template + +from caribou.i18n import _ +from caribou import APP_NAME + +debug = False + +class CaribouDaemon: + def __init__(self, keyboard_name="Antler"): + if not self._get_a11y_enabled(): + self._show_no_a11y_dialogs() + bus = dbus.SessionBus() + try: + dbus_obj = bus.get_object("org.gnome.Caribou.%s" % keyboard_name, + "/org/gnome/Caribou/%s" % keyboard_name) + except dbus.DBusException: + print "%s is not running, and is not provided by any .service file" % \ + keyboard_name + return + self.keyboard_proxy = dbus.Interface(dbus_obj, "org.gnome.Caribou.Keyboard") + self._current_acc = None + self._register_event_listeners() + + def _show_no_a11y_dialogs(self): + from gi.repository import Gtk + msgdialog = Gtk.MessageDialog(None, + Gtk.DialogFlags.MODAL, + Gtk.MessageType.QUESTION, + Gtk.ButtonsType.YES_NO, + _("In order to use %s, accessibility needs " + "to be enabled. Do you want to enable " + "it now?") % APP_NAME) + resp = msgdialog.run() + if resp == Gtk.ResponseType.NO: + msgdialog.destroy() + quit() + if resp == Gtk.ResponseType.YES: + settings = Gio.Settings('org.gnome.desktop.interface') + atspi = settings.set_boolean("toolkit-accessibility", True) + msgdialog2 = Gtk.MessageDialog(msgdialog, + Gtk.DialogFlags.MODAL, + Gtk.MessageType.INFO, + Gtk.ButtonsType.OK, + _("Accessibility has been enabled. " + "Log out and back in again to use " + "%s." % APP_NAME)) + msgdialog2.run() + msgdialog2.destroy() + msgdialog.destroy() + quit() + + + def _register_event_listeners(self): + pyatspi.Registry.registerEventListener( + self.on_focus, "object:state-changed:focused") + pyatspi.Registry.registerEventListener(self.on_focus, "focus") + pyatspi.Registry.registerEventListener( + self.on_text_caret_moved, "object:text-caret-moved") + + def _deregister_event_listeners(self): + pyatspi.Registry.deregisterEventListener( + self.on_focus, "object:state-changed:focused") + pyatspi.Registry.deregisterEventListener(self.on_focus, "focus") + pyatspi.Registry.deregisterEventListener( + self.on_text_caret_moved, "object:text-caret-moved") + + def _get_a11y_enabled(self): + try: + try: + settings = Gio.Settings('org.gnome.desktop.interface') + atspi = settings.get_boolean("toolkit-accessibility") + return atspi + except: + raise + from gi.repository import GConf + gconfc = GConf.Client.get_default() + atspi1 = gconfc.get_bool( + "/desktop/gnome/interface/accessibility") + atspi2 = gconfc.get_bool( + "/desktop/gnome/interface/accessibility2") + return atspi1 or atspi2 + except: + raise + return False + + def on_text_caret_moved(self, event): + if self._current_acc == event.source: + text = self._current_acc.queryText() + x, y, w, h = text.getCharacterExtents(text.caretOffset, + pyatspi.DESKTOP_COORDS) + if (x, y, w, h) == (0, 0, 0, 0): + component = self._current_acc.queryComponent() + bb = component.getExtents(pyatspi.DESKTOP_COORDS) + x, y, w, h = bb.x, bb.y, bb.width, bb.height + + self.keyboard_proxy.SetCursorLocation(x, y, w, h) + if debug == True: + print "object:text-caret-moved in", event.host_application.name, + print event.detail1, event.source.description + + def _set_entry_location(self, acc): + text = acc.queryText() + bx, by, bw, bh = text.getCharacterExtents(text.caretOffset, + pyatspi.DESKTOP_COORDS) + + component = acc.queryComponent() + entry_bb = component.getExtents(pyatspi.DESKTOP_COORDS) + + if (bx, by, bw, bh) == (0, 0, 0, 0): + bx, by, bw, bh = entry_bb.x, entry_bb.y, entry_bb.width, entry_bb.height + + self.keyboard_proxy.SetCursorLocation(bx, by, bw, bh) + + self.keyboard_proxy.SetEntryLocation(entry_bb.x, entry_bb.y, + entry_bb.width, entry_bb.height) + + self.keyboard_proxy.Show() + + def on_focus(self, event): + acc = event.source + source_role = acc.getRole() + if acc.getState().contains(pyatspi.STATE_EDITABLE) or \ + source_role == pyatspi.ROLE_TERMINAL: + if source_role in (pyatspi.ROLE_TEXT, + pyatspi.ROLE_PARAGRAPH, + pyatspi.ROLE_PASSWORD_TEXT, + pyatspi.ROLE_TERMINAL, + pyatspi.ROLE_ENTRY): + if event.type.startswith("focus") or event.detail1 == 1: + self._set_entry_location(acc) + self._current_acc = event.source + if debug == True: + print "enter text widget in", event.host_application.name + elif event.detail1 == 0 and acc == self._current_acc: + self.keyboard_proxy.Hide() + self._current_acc = None + if debug == True: + print "leave text widget in", event.host_application.name + else: + if debug == True: + print _("WARNING - Caribou: unhandled editable widget:"), \ + event.source + + def clean_exit(self): + self._deregister_event_listeners() + + diff --git a/caribou/ui/i18n.py.in b/caribou/i18n.py.in index bf7ad0f..bf7ad0f 100644 --- a/caribou/ui/i18n.py.in +++ b/caribou/i18n.py.in diff --git a/caribou/settings/Makefile.am b/caribou/settings/Makefile.am new file mode 100644 index 0000000..468adab --- /dev/null +++ b/caribou/settings/Makefile.am @@ -0,0 +1,11 @@ +caribou_settingsdir = $(pkgpythondir)/settings/ + +caribou_settings_PYTHON = \ + __init__.py \ + preferences_window.py \ + settings_manager.py \ + caribou_settings.py \ + setting_types.py + +clean-local: + rm -rf *.pyc *.pyo diff --git a/caribou/settings/__init__.py b/caribou/settings/__init__.py new file mode 100644 index 0000000..0ec8b40 --- /dev/null +++ b/caribou/settings/__init__.py @@ -0,0 +1,5 @@ +GSETTINGS_SCHEMA = "org.gnome.caribou" + +from caribou_settings import CaribouSettings + +AllSettings = [CaribouSettings] diff --git a/caribou/settings/caribou_settings.py b/caribou/settings/caribou_settings.py new file mode 100644 index 0000000..638e1c4 --- /dev/null +++ b/caribou/settings/caribou_settings.py @@ -0,0 +1,64 @@ +from caribou.settings.setting_types import * +from caribou.i18n import _ + +CaribouSettings = SettingsTopGroup( + _("Caribou Preferences"), "/org/gnome/caribou/", "org.gnome.caribou", + [SettingsGroup("keyboard", _("Keyboard"), [ + SettingsGroup("general", _("General"), [ + StringSetting( + "keyboard_type", _("Keyboard Type"), "touch", + _("The keyboard geometery Caribou should use"), + _("The keyboard geometery determines the shape " + "and complexity of the keyboard, it could range from " + "a 'natural' look and feel good for composing simple " + "text, to a fullscale keyboard."), + allowed=[(('touch'), _('Touch'))])]), + ]), + SettingsGroup("scanning", _("Scanning"), [ + BooleanSetting( + "scan_enabled", _("Enable scanning"), False, + _("Enable switch scanning"), + insensitive_when_false=["scanning_general", + "scanning_input"]), + SettingsGroup("scanning_general", _("General"), [ + StringSetting("scanning_type", _("Scanning mode"), + "block", + _("Scanning type, block or row"), + allowed=[("block", _("Block")), + ("row", _("Row"))]), + FloatSetting("step_time", _("Step time"), 1.0, + _("Time between key transitions"), + min=0.1, max=60.0), + BooleanSetting("reverse_scanning", + _("Reverse scanning"), False, + _("Scan in reverse order")) + ]), + SettingsGroup("scanning_input", _("Input"), [ + StringSetting("switch_type", _("Switch device"), + "keyboard", + _("Switch device, keyboard or mouse"), + entry_type=ENTRY_RADIO, + allowed=[("keyboard", _("Keyboard")), + ("mouse", _("Mouse"))], + children=[ + StringSetting("keyboard_key", _("Switch key"), + "Shift_R", + _( + "Key to use with scanning mode"), + allowed=[ + ("Shift_R", _("Right shift")), + ("Shift_L", _("Left shift")), + ("ISO_Level3_Shift", _("Alt Gr")), + ("Num_Lock", _("Num lock"))]), + StringSetting("mouse_button", _("Switch button"), + "2", + _( + "Mouse button to use in the scanning " + "mode"), + allowed=[("1", _("Button 1")), + ("2", _("Button 2")), + ("3", _("Button 3"))]) + ]), + ]), + ]) + ]) diff --git a/caribou/ui/preferences_window.py b/caribou/settings/preferences_window.py index 0158ffb..58d1b4c 100644 --- a/caribou/ui/preferences_window.py +++ b/caribou/settings/preferences_window.py @@ -18,47 +18,24 @@ # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -import caribou.common.const as const -from caribou.common.setting_types import * -from caribou.common.settings_manager import SettingsManager +from caribou.settings.setting_types import * -from gi.repository import GConf import gobject from gi.repository import Gdk from gi.repository import Gtk -from gi.repository import Pango -import sys -import virtkey -import os -import traceback -from i18n import _ -try: - import json -except ImportError: - HAS_JSON = False -else: - HAS_JSON = True -import xml.etree.ElementTree as ET -from xml.dom import minidom -import gettext -import i18n - -class PreferencesWindow(Gtk.Dialog): - __gtype_name__ = "PreferencesWindow" - - def __init__(self): - gobject.GObject.__init__(self) - self.set_title(_("Caribou Preferences")) - self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) - self.set_border_width(6) +class AbstractPreferencesUI: + def populate_settings(self, groups): notebook = Gtk.Notebook() - vbox = self.get_content_area() - vbox.add(notebook) - self._populate_settings(notebook, SettingsManager.groups) + self._populate_settings(notebook, groups) + if notebook.get_n_pages() == 1: + notebook.set_show_tabs(False) + + return notebook def _populate_settings(self, parent, setting, level=0): if level == 0: + self.set_title(setting.label) for s in setting: vbox = Gtk.VBox() parent.append_page(vbox, Gtk.Label(label=s.label)) @@ -248,11 +225,38 @@ class PreferencesWindow(Gtk.Dialog): self._update_setting(setting, combo.get_active_id(), handler_id) +class PreferencesDialog(Gtk.Dialog, AbstractPreferencesUI): + __gtype_name__ = "PreferencesDialog" + + def __init__(self, settings_manager): + gobject.GObject.__init__(self) + self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE) + self.set_border_width(6) + + notebook = self.populate_settings(settings_manager.groups) + vbox = self.get_content_area() + vbox.add(notebook) + +class PreferencesWindow(Gtk.Window, AbstractPreferencesUI): + __gtype_name__ = "PreferencesWindow" + + def __init__(self, settings_manager): + gobject.GObject.__init__(self) + self.set_border_width(6) + + notebook = self.populate_settings(settings_manager.groups) + self.add(notebook) + if __name__ == "__main__": + from caribou.settings.settings_manager import SettingsManager + from caribou.settings import CaribouSettings + import signal signal.signal(signal.SIGINT, signal.SIG_DFL) - w = PreferencesWindow() + + w = PreferencesDialog(CaribouSettings()) w.show_all() + try: w.run() except KeyboardInterrupt: diff --git a/caribou/common/setting_types.py b/caribou/settings/setting_types.py index 109bf1b..b0e374d 100644 --- a/caribou/common/setting_types.py +++ b/caribou/settings/setting_types.py @@ -53,6 +53,16 @@ class Setting(gobject.GObject): class SettingsGroup(Setting): pass +class SettingsTopGroup(SettingsGroup): + def __init__(self, label, path, schema_id, children=[]): + SettingsGroup.__init__(self, "_top", label, children) + self.path = path + self.schema_id = schema_id + + def __call__(self): + from caribou.settings.settings_manager import SettingsManager + return SettingsManager(self) + class ValueSetting(Setting): variant_type = '' entry_type=ENTRY_DEFAULT diff --git a/caribou/common/settings_manager.py b/caribou/settings/settings_manager.py index 66119f8..1368f3a 100644 --- a/caribou/common/settings_manager.py +++ b/caribou/settings/settings_manager.py @@ -1,10 +1,9 @@ import os from gi.repository import Gio -from setting_types import * -from settings import settings, GSETTINGS_SCHEMA -import const +from caribou.settings.setting_types import * +from caribou.settings import GSETTINGS_SCHEMA -class _SettingsManager(object): +class SettingsManager(object): def __init__(self, settings): self.groups = settings self._gsettings = Gio.Settings(GSETTINGS_SCHEMA) @@ -66,5 +65,3 @@ class _SettingsManager(object): def __call__(self): return self - -SettingsManager = _SettingsManager(settings) diff --git a/caribou/ui/Makefile.am b/caribou/ui/Makefile.am deleted file mode 100644 index fb24db4..0000000 --- a/caribou/ui/Makefile.am +++ /dev/null @@ -1,16 +0,0 @@ -caribou_uidir = $(pkgpythondir)/ui/ - -caribou_ui_PYTHON = \ - __init__.py \ - i18n.py \ - keyboard.py \ - scan.py \ - main.py \ - opacity.py \ - preferences_window.py \ - window.py - -DISTCLEANFILES = i18n.py - -clean-local: - rm -rf *.pyc *.pyo diff --git a/caribou/ui/__init__.py b/caribou/ui/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/caribou/ui/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/caribou/ui/keyboard.py b/caribou/ui/keyboard.py deleted file mode 100644 index 60c9ae1..0000000 --- a/caribou/ui/keyboard.py +++ /dev/null @@ -1,535 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Caribou - text entry and UI navigation application -# -# Copyright (C) 2009 Adaptive Technology Resource Centre -# * Contributor: Ben Konrath <ben@bagu.org> -# Copyright (C) 2009 Eitan Isaacson <eitan@monotonous.org> -# Copyright (C) 2010 Igalia S.L. -# * Contributor: Joaquim Rocha <jrocha@igalia.com> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by the -# Free Software Foundation; either version 2.1 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License -# for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -import caribou.common.const as const -from caribou.common.settings_manager import SettingsManager -from preferences_window import PreferencesWindow -from caribou import data_path -from gi.repository import GConf -import gobject -from gi.repository import Gdk -from gi.repository import Gtk -from gi.repository import Pango -from gi.repository import Caribou -import sys -import os -import traceback -from caribou.ui.i18n import _ -from caribou.common.setting_types import * - -try: - import json -except ImportError: - HAS_JSON = False -else: - HAS_JSON = True -import xml.etree.ElementTree as ET -from xml.dom import minidom -import gettext -import i18n - -PRETTY_LABELS = { - "BackSpace" : u'\u232b', - "space" : u' ', - "Return" : u'\u23ce', - 'Caribou_Prefs' : u'\u2328', - 'Caribou_ShiftUp' : u'\u2b06', - 'Caribou_ShiftDown' : u'\u2b07', - 'Caribou_Emoticons' : u'\u263a', - 'Caribou_Symbols' : u'123', - 'Caribou_Symbols_More' : u'{#*', - 'Caribou_Alpha' : u'Abc' -} - -class BaseKey(object): - '''An abstract class the represents a key on the keyboard. - Inheriting classes also need to inherit from Gtk.Button or any - of it's subclasses.''' - - def __init__(self, **kwargs): - if not kwargs.has_key("name"): - raise TypeError, "%r requires a 'name' parameter" % self.__class__ - self.margin_left = 0 - self.width = 1 - - for k, v in kwargs.items(): - setattr(self, k, v) - if hasattr(self, "extended_names"): - self.extended_keys = \ - [self.__class__(name=n) for n in [self.name] + self.extended_names] - else: - self.extended_keys = [] - - self.keyval, self.key_label = self._get_keyval_and_label(self.name) - self.set_label(self.key_label) - - ctx = self.get_style_context() - ctx.add_class("caribou-keyboard-button") - - for name in ["default_font", "key_font"]: - getattr(SettingsManager, name).connect("value-changed", - self._key_font_changed) - - if not SettingsManager.default_font.value: - self._key_font_changed(None, None) - - def _get_keyval_and_label(self, name): - keyval = Gdk.keyval_from_name(name) - if PRETTY_LABELS.has_key(name): - label = PRETTY_LABELS[name] - elif name.startswith('Caribou_'): - label = name.replace('Caribou_', '') - else: - unichar = unichr(Gdk.keyval_to_unicode(keyval)) - if unichar.isspace() or unichar == u'\x00': - label = name - else: - label = unichar - - return keyval, label - - def _key_font_changed(self, setting, value): - if SettingsManager.default_font.value: - self.reset_font() - else: - self.set_font(SettingsManager.key_font.value) - - def scan_highlight_key(self): - raise NotImplemented - - def scan_highlight_row(self): - raise NotImplemented - - def scan_highlight_block(self): - raise NotImplemented - - def scan_highlight_cancel(self): - raise NotImplemented - - def scan_highlight_clear(self): - raise NotImplemented - - def set_font(self, font): - raise NotImplemented - - def reset_font(self): - raise NotImplemented - -class CaribouSubKeys(Gtk.Window): - def __init__(self, keys): - gobject.GObject.__init__(self, type=Gtk.WindowType.POPUP) - self.set_decorated(False) - self.set_resizable(False) - self.set_accept_focus(False) - self.set_position(Gtk.WindowPosition.MOUSE) - self.set_type_hint(Gdk.WindowTypeHint.DIALOG) - - for key in keys: - key.connect("clicked", self._on_key_clicked) - - layout = KeyboardLayout(key.name, "subkeyboard") - layout.add_row(keys) - self.add(layout) - - def show_subkeys(self, parent): - self.set_transient_for(parent) - self._parent = parent - self._parent.set_sensitive(False) - self.show_all() - - def _on_key_clicked(self, key): - self._parent.set_sensitive(True) - self.hide() - -class Key(Gtk.Button, BaseKey): - def __init__(self, **kwargs): - gobject.GObject.__init__(self) - BaseKey.__init__(self, **kwargs) - if self.extended_keys: - self.sub_keys = CaribouSubKeys(self.extended_keys) - else: - self.sub_keys = None - - child = self.get_child() - child.set_padding(4, 4) - - def do_get_preferred_width_for_height(self, w): - return (w, w) - - def do_get_request_mode(self): - return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH - - def set_font(self, font): - child = self.get_child() - if isinstance(child, Gtk.Label): - child.modify_font(Pango.font_description_from_string(font)) - if child is not None: - child.queue_resize() - - def reset_font(self): - label = self.get_child() - if not isinstance(label, Gtk.Label): - return - label.modify_font(None) - - def _replace_scan_class_style(self, scan_class=None): - ctx = self.get_style_context() - for cls in ctx.list_classes(): - if cls.startswith('caribou-scan'): - ctx.remove_class(cls) - if scan_class: - ctx.add_class(scan_class) - self.queue_draw() - - def scan_highlight_key(self): - self._replace_scan_class_style("caribou-scan-key") - - def scan_highlight_row(self): - self._replace_scan_class_style("caribou-scan-row") - - def scan_highlight_block(self): - self._replace_scan_class_style("caribou-scan-block") - - def scan_highlight_cancel(self): - self._replace_scan_class_style("caribou-scan-cancel") - - def scan_highlight_clear(self): - self._replace_scan_class_style() - -class ModifierKey(Gtk.ToggleButton, Key): - pass - -class KeyboardLayout(Gtk.Grid): - KEY_SPAN = 4 - - def __init__(self, name, mode): - gobject.GObject.__init__(self) - self.layout_name = name - self.mode = mode - self.rows = [] - self.set_column_homogeneous(True) - self.set_row_homogeneous(True) - self.set_row_spacing(4) - self.set_column_spacing(4) - - def add_row(self, row): - row_num = len(self.rows) - self.rows.append(row) - col_num = 0 - for i, key in enumerate(row): - self.attach(key, - col_num + int(key.margin_left * self.KEY_SPAN), - row_num * self.KEY_SPAN, - int(self.KEY_SPAN * key.width), - self.KEY_SPAN) - col_num += int((key.width + key.margin_left ) * self.KEY_SPAN) - - def get_scan_rows(self): - return [filter(lambda x: x.is_sensitive(), row) for row in self.rows] - - def get_scan_blocks(self, optimal_block_size=8): - # TODO: smarter division using optimal block size. - scan_rows = self.get_scan_rows() - col_num = max([len(row) for row in scan_rows]) - blocks = [] - - for row_index in xrange(len(scan_rows)): - for col_index in xrange(max([len(row) for row in scan_rows])): - try: - key = scan_rows[row_index][col_index] - except IndexError: - continue - - try: - group = blocks[row_index/2] - except IndexError: - group = [] - blocks.append(group) - - try: - group[col_index/3].append(key) - except IndexError: - block = [] - block.append(key) - group.append(block) - - return reduce(lambda a, b: a + b, blocks) - -class KbLayoutDeserializer(object): - def _get_layout_file(self, group, variant): - layout_path = os.path.join(data_path, "layouts", - SettingsManager.geometry.value) - for fn in ('%s_%s.json' % (group, variant), - '%s.json' % group): - layout_file = os.path.join(layout_path, fn) - if os.path.exists(layout_file): - return layout_file - return None - - def deserialize(self, group, variant): - kb_file = self._get_layout_file(group, variant) - if not kb_file: - return [] - kb_file_obj = open(kb_file) - contents = kb_file_obj.read() - kb_file_obj.close() - basename, ext = os.path.splitext(kb_file) - try: - kb_layouts = self._deserialize_from_format(ext, contents) - except Exception, e: - traceback.print_exc() - else: - return kb_layouts - return [] - - def _deserialize_from_format(self, format, contents): - if format == '.xml': - return self._deserialize_from_xml(contents) - if HAS_JSON and format == '.json': - return self._deserialize_from_json(contents) - return [] - - def _deserialize_from_json(self, contents): - contents_dict = json.loads(contents) - layouts = self._create_kb_layout_from_dict(contents_dict) - return layouts - - def _convert_xml_to_dict(self, element): - if element.text and element.text.strip(): - return element.text - attributes = element.attrib - for child in element.getchildren(): - if attributes.get(child.tag): - attributes[child.tag] += [self._convert_xml_to_dict(child)] - else: - attributes[child.tag] = [self._convert_xml_to_dict(child)] - for key, value in attributes.items(): - if isinstance(value, list) and len(value) == 1: - attributes[key] = value[0] - return attributes - - def _deserialize_from_xml(self, xml_string): - element = ET.fromstring(xml_string) - layout_dict = self._convert_xml_to_dict(element) - return self._create_kb_layout_from_dict(layout_dict) - - def _create_kb_layout_from_dict(self, dictionary): - if not isinstance(dictionary, dict): - return None - layouts_encoded = [] - for name, level in dictionary.items(): - kb_layout = KeyboardLayout(name, level.get("mode", "locked")) - rows_list = self._get_dict_value_as_list(level, 'rows') - for row in rows_list: - keys = self._get_keys_from_list(row) - kb_layout.add_row(keys) - layouts_encoded.append(kb_layout) - return layouts_encoded - - def _get_keys_from_list(self, keys_list): - keys = [] - for key_vars in keys_list: - vars = {} - for key, value in key_vars.items(): - vars[str(key)] = value - if vars.get('modifier', False) == const.MASK_KEY_TYPE: - key = ModifierKey(self.vk, **vars) - else: - key = Key(**vars) - keys.append(key) - return keys - - def _get_dict_value_as_list(self, dictionary, key): - if isinstance(dictionary, list): - return dictionary - value = dictionary.get(key) - if not value: - return None - if isinstance(value, list): - return value - return [value] - -class CaribouKeyboard(Gtk.Notebook): - __gtype_name__ = "CaribouKeyboard" - - def __init__(self): - gobject.GObject.__init__(self) - self.set_show_tabs(False) - self.vk = Caribou.VirtualKeyboard() - self.key_size = 30 - self.current_page = 0 - self.depressed_mods = [] - self.layouts = {} - self._load_kb() - self.vk.connect('group-changed', self._on_group_changed) - grpid, group, variant = self.vk.get_current_group() - self._on_group_changed(self.vk, grpid, group, variant) - self._key_hold_tid = 0 - - def _load_kb(self): - kb_deserializer = KbLayoutDeserializer() - groups, variants = self.vk.get_groups() - for group, variant in zip(groups, variants): - levels = kb_deserializer.deserialize(group, variant) - self._add_levels('%s_%s' % (group, variant), levels) - - def _connect_key_signals(self, key): - if hasattr(key, "toggle"): - key.connect('clicked', - self._pressed_layout_switcher_key) - elif key.name == "Caribou_Prefs": - key.connect('clicked', self._pressed_preferences_key) - elif key.keyval != 0: - if False: # We should enable this for hardware emulation - key.connect('pressed', - self._pressed_normal_key) - key.connect('released', - self._released_normal_key) - else: - key.connect('clicked', - self._clicked_normal_key) - key.connect('pressed', - self._key_hold_start) - key.connect('released', - self._key_hold_end) - - - def _add_levels(self, group, level_list): - self.layouts[group] = {} - for level in level_list: - level.show() - self.layouts[group][level.layout_name] = self.append_page(level, None) - if level.mode == "default": - self.layouts[group]["default"] = \ - self.layouts[group][level.layout_name] - for row in level.rows: - for key in row: - self._connect_key_signals(key) - for k in key.extended_keys: - self._connect_key_signals(k) - - def _clear(self): - n_pages = self.get_n_pages() - for i in range(n_pages): - self.remove_page(i) - - def _key_hold_start(self, key): - self._key_hold_tid = gobject.timeout_add(1000, self._on_key_held, key) - - def _key_hold_end(self, key): - if self._key_hold_tid != 0: - gobject.source_remove(self._key_hold_tid) - self._key_hold_tid = 0 - - def _on_key_held(self, key): - self._key_hold_tid = 0 - if key.sub_keys: - key.sub_keys.show_subkeys(self.get_toplevel()) - return False - - def _clicked_normal_key(self, key): - if self._key_hold_tid == 0: - return - self._pressed_normal_key(key) - self._released_normal_key(key) - - def _pressed_normal_key(self, key): - self.vk.keyval_press(key.keyval) - - def _released_normal_key(self, key): - self.vk.keyval_release(key.keyval) - layout = self.get_nth_page(self.get_current_page()) - if layout.mode == "latched": - self._switch_to_layout() - while True: - try: - mod = self.depressed_mods.pop() - except IndexError: - break - mod.set_active (False) - - def _pressed_layout_switcher_key(self, key): - self._switch_to_layout(level=key.toggle) - - def _on_group_changed(self, vk, groupid, group, variant): - self._switch_to_layout('%s_%s' % (group, variant)) - - def _toggled_mask_key(self, key): - if key.get_active(): - self.vk.keyval_press(key.value) - self.depressed_mods.append(key) - else: - self.vk.keyval_release(key.value) - try: - mod = self.depressed_mods.remove(key) - except ValueError: - pass - - def show_all_(self): - self.set_current_page(self.current_page) - Gtk.Notebook.show_all(self) - - def _pressed_preferences_key(self, key): - p = PreferencesWindow() - p.show_all() - p.run() - p.destroy() - - def _switch_to_fallback(self, group): - try: - i = min(self.layouts[group].values()) - except KeyError: - i = 0 - self.set_current_page(i) - - def _switch_to_layout(self, group=None, level="default"): - if group is None: - _, _group, _variant = self.vk.get_current_group() - group = '%s_%s' % (_group, _variant) - if self.layouts.has_key(group): - if self.layouts[group].has_key(level): - self.set_current_page(self.layouts[group][level]) - return - self._switch_to_fallback(group) - - def get_current_layout(self): - i = self.get_current_page() - return self.get_nth_page(i) - - def get_layouts(self): - return [self.get_nth_page(i) for i in xrange(self.get_n_pages())] - -if __name__ == "__main__": - import signal - signal.signal(signal.SIGINT, signal.SIG_DFL) - - w = Gtk.Window() - w.set_accept_focus(False) - - kb = CaribouKeyboard() - w.add(kb) - - w.show_all() - - Gtk.main() diff --git a/caribou/ui/main.py b/caribou/ui/main.py deleted file mode 100644 index de2302c..0000000 --- a/caribou/ui/main.py +++ /dev/null @@ -1,275 +0,0 @@ -import pyatspi -from gi.repository import Gtk -from gi.repository import Gdk -from gi.repository import Gio -from string import Template - -from window import CaribouWindowEntry, Rectangle -from keyboard import CaribouKeyboard -from caribou.common.settings_manager import SettingsManager -from caribou.ui.i18n import _ -import caribou.common.const as const -from scan import ScanMaster - -debug = False - -CSS_TEMPLATE = """ -.caribou-keyboard-button { -background-image: none; -background-color: $normal_bg; -} - -.caribou-keyboard-button:hover { -background-image: none; -background-color: $mouseover_bg; -} -""" - -SCAN_CSS_TEMPLATE = """ -.caribou-scan-key { -background-image: none; -background-color: $button_scan; -} - -.caribou-scan-row { -background-image: none; -background-color: $row_scan; -} - -.caribou-scan-block { -background-image: none; -background-color: $block_scan; -} - -.caribou-scan-cancel { -background-image: none; -background-color: $cancel_scan; -} -""" - -class Caribou: - def __init__(self, - kb_factory=CaribouKeyboard, - window_factory=CaribouWindowEntry): - if not self._get_a11y_enabled(): - msgdialog = Gtk.MessageDialog(None, - Gtk.DialogFlags.MODAL, - Gtk.MessageType.QUESTION, - Gtk.ButtonsType.YES_NO, - _("In order to use %s, accessibility needs " - "to be enabled. Do you want to enable " - "it now?") % const.APP_NAME) - resp = msgdialog.run() - if resp == Gtk.ResponseType.NO: - msgdialog.destroy() - quit() - if resp == Gtk.ResponseType.YES: - settings = Gio.Settings('org.gnome.desktop.interface') - atspi = settings.set_boolean("toolkit-accessibility", True) - msgdialog2 = Gtk.MessageDialog(msgdialog, - Gtk.DialogFlags.MODAL, - Gtk.MessageType.INFO, - Gtk.ButtonsType.OK, - _("Accessibility has been enabled. " - "Log out and back in again to use " - "%s.") % const.APP_NAME) - msgdialog2.run() - msgdialog2.destroy() - msgdialog.destroy() - quit() - self.__current_acc = None - self.window_factory = window_factory - self.kb_factory = kb_factory - kb = kb_factory() - self.window = window_factory(kb) - self._register_event_listeners() - SettingsManager.geometry.connect("value-changed", - self._on_geometry_changed) - - # Scanning - self.scan_master = ScanMaster(self.window, kb) - SettingsManager.scan_enabled.connect("value-changed", - self._on_scan_toggled) - if SettingsManager.scan_enabled.value: - self.scan_master.start() - - self._custom_css_provider = Gtk.CssProvider() - - for name in ["normal_color", "mouse_over_color", "default_colors"]: - getattr(SettingsManager, name).connect("value-changed", - self._colors_changed) - self._colors_changed(None, None) - - self._scan_css_provider = Gtk.CssProvider() - Gtk.StyleContext.add_provider_for_screen( - Gdk.Screen.get_default(), - self._scan_css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) - - for name in ["button_scanning_color", - "row_scanning_color", - "block_scanning_color", - "cancel_scanning_color"]: - getattr(SettingsManager, name).connect("value-changed", - self._scan_colors_changed) - self._scan_colors_changed(None, None) - - def _colors_changed(self, setting, value): - if SettingsManager.default_colors.value: - Gtk.StyleContext.remove_provider_for_screen( - Gdk.Screen.get_default(), - self._custom_css_provider) - else: - Gtk.StyleContext.add_provider_for_screen( - Gdk.Screen.get_default(), - self._custom_css_provider, - Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) - self._custom_css_provider.load_from_data( - Template(CSS_TEMPLATE).substitute( - normal_bg=SettingsManager.normal_color.value, - mouseover_bg=SettingsManager.mouse_over_color.value), -1) - - def _scan_colors_changed(self, setting, value): - self._scan_css_provider.load_from_data(Template(SCAN_CSS_TEMPLATE).substitute( - button_scan=SettingsManager.button_scanning_color.value, - row_scan=SettingsManager.row_scanning_color.value, - block_scan=SettingsManager.block_scanning_color.value, - cancel_scan=SettingsManager.cancel_scanning_color.value), -1) - - - def _register_event_listeners(self): - pyatspi.Registry.registerEventListener( - self.on_focus, "object:state-changed:focused") - pyatspi.Registry.registerEventListener(self.on_focus, "focus") - pyatspi.Registry.registerEventListener( - self.on_text_caret_moved, "object:text-caret-moved") - - def _deregister_event_listeners(self): - pyatspi.Registry.deregisterEventListener( - self.on_focus, "object:state-changed:focused") - pyatspi.Registry.deregisterEventListener(self.on_focus, "focus") - pyatspi.Registry.deregisterEventListener( - self.on_text_caret_moved, "object:text-caret-moved") - - def _on_scan_toggled(self, setting, val): - if val: - self.scan_master.start() - else: - self.scan_master.stop() - - def _on_geometry_changed(self, setting, val): - self._deregister_event_listeners() - self.window.destroy() - self._update_window() - self._register_event_listeners() - - def _update_window(self): - kb = self.kb_factory() - self.scan_master.set_keyboard(kb) - self.window = self.window_factory(kb) - - def _get_a11y_enabled(self): - try: - try: - settings = Gio.Settings('org.gnome.desktop.interface') - atspi = settings.get_boolean("toolkit-accessibility") - print "->", atspi - return atspi - except: - raise - from gi.repository import GConf - gconfc = GConf.Client.get_default() - atspi1 = gconfc.get_bool( - "/desktop/gnome/interface/accessibility") - atspi2 = gconfc.get_bool( - "/desktop/gnome/interface/accessibility2") - return atspi1 or atspi2 - except: - raise - return False - - def on_text_caret_moved(self, event): - if self.__current_acc == event.source: - self.__set_location(event.source) - if debug == True: - print "object:text-caret-moved in", event.host_application.name, - print event.detail1, event.source.description - - def __set_text_location(self, acc): - text = acc.queryText() - [x, y, width, height] = text.getCharacterExtents(text.caretOffset, pyatspi.DESKTOP_COORDS) - self.window.set_cursor_location(Rectangle(x, y, width, height)) - - component = acc.queryComponent() - entry_bb = component.getExtents(pyatspi.DESKTOP_COORDS) - self.window.set_entry_location(entry_bb) - self.window.show_all() - - def __set_entry_location(self, acc): - text = acc.queryText() - cursor_bb = Rectangle( - *text.getCharacterExtents(text.caretOffset, - pyatspi.DESKTOP_COORDS)) - - component = acc.queryComponent() - entry_bb = component.getExtents(pyatspi.DESKTOP_COORDS) - - if cursor_bb == Rectangle(0, 0, 0, 0): - cursor_bb = entry_bb - - self.window.set_cursor_location(cursor_bb) - self.window.set_entry_location(entry_bb) - - self.window.show_all() - - def on_focus(self, event): - acc = event.source - source_role = acc.getRole() - if acc.getState().contains(pyatspi.STATE_EDITABLE) or \ - source_role == pyatspi.ROLE_TERMINAL: - if source_role in (pyatspi.ROLE_TEXT, - pyatspi.ROLE_PARAGRAPH, - pyatspi.ROLE_PASSWORD_TEXT, - pyatspi.ROLE_TERMINAL): - if event.type.startswith("focus") or event.detail1 == 1: - self.__set_text_location(acc) - self.__current_acc = event.source - self.__set_location = self.__set_text_location - if debug == True: - print "enter text widget in", event.host_application.name - elif event.detail1 == 0 and acc == self.__current_acc: - self.window.hide() - self.__current_acc = None - self.__set_location = None - if debug == True: - print "leave text widget in", event.host_application.name - - elif source_role == pyatspi.ROLE_ENTRY: - if event.type.startswith("focus") or event.detail1 == 1: - self.__set_entry_location(acc) - self.__current_acc = event.source - self.__set_location = self.__set_entry_location - if debug == True: - print "enter entry widget in", event.host_application.name - elif event.detail1 == 0: - self.window.hide() - self.__current_acc = None - self.__set_location = None - if debug == True: - print "leave entry widget in", event.host_application.name - else: - if debug == True: - print _("WARNING - Caribou: unhandled editable widget:"), event.source - - # Firefox does not report leave entry widget events. - # This could be a way to get the entry widget leave events. - #else: - # if event.detail1 == 1: - # self.window.hide() - # print "--> LEAVE EDITABLE TEXT <--" - - def clean_exit(self): - self.scan_master.stop() - self._deregister_event_listeners() - - diff --git a/caribou/ui/opacity.py b/caribou/ui/opacity.py deleted file mode 100644 index be742d8..0000000 --- a/caribou/ui/opacity.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Caribou - text entry and UI navigation application -# -# Copyright (C) 2009 Eitan Isaacson <eitan@monotonous.org> -# -# This program is free software; you can redistribute it and/or modify it -# under the terms of the GNU Lesser General Public License as published by the -# Free Software Foundation; either version 2.1 of the License, or (at your -# option) any later version. -# -# This program is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License -# for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - -import glib -from math import sqrt - -class ProximityWindowBase(object): - def __init__(self, min_alpha=1.0, max_alpha=1.0, max_distance=100): - if self.__class__ == ProximityWindowBase: - raise TypeError, \ - "ProximityWindowBase is an abstract class, " \ - "must be subclassed with a Gtk.Window" - self.connect('map-event', self.__onmapped) - self.max_distance = max_distance - if max_alpha < min_alpha: - raise ValueError, "min_alpha can't be larger than max_alpha" - self.min_alpha = min_alpha - self.max_alpha = max_alpha - - def __onmapped(self, obj, event): - if self.is_composited(): - self.set_opacity(self.max_alpha) - if self.max_alpha != self.min_alpha: - # Don't waste CPU if the max and min are equal. - glib.timeout_add(80, self._proximity_check) - - def _proximity_check(self): - px, py = self.get_pointer() - - ww = self.get_allocated_width() - wh = self.get_allocated_height() - - distance = self._get_distance_to_bbox(px, py, ww, wh) - - opacity = (self.max_alpha - self.min_alpha) * \ - (1 - min(distance, self.max_distance)/self.max_distance) - opacity += self.min_alpha - - self.set_opacity(opacity) - return self.props.visible - - def _get_distance_to_bbox(self, px, py, bw, bh): - if px < 0: - x_distance = float(abs(px)) - elif px > bw: - x_distance = float(px - bw) - else: - x_distance = 0.0 - - if py < 0: - y_distance = float(abs(px)) - elif py > bh: - y_distance = float(py - bh) - else: - y_distance = 0.0 - - if y_distance == 0 and x_distance == 0: - return 0.0 - elif y_distance != 0 and x_distance == 0: - return y_distance - elif y_distance == 0 and x_distance != 0: - return x_distance - else: - x2 = 0 if x_distance > 0 else bw - y2 = 0 if y_distance > 0 else bh - return sqrt((px - x2)**2 + (py - y2)**2) diff --git a/caribou/ui/scan.py b/caribou/ui/scan.py deleted file mode 100644 index 94aa8c2..0000000 --- a/caribou/ui/scan.py +++ /dev/null @@ -1,216 +0,0 @@ -import gobject -import pyatspi -from gi.repository import Gdk -from gi.repository import Gtk -import caribou.common.const as const -from caribou.common.settings_manager import SettingsManager - -# Scan constants -BUTTON = 'button' -ROW = 'row' -BLOCK = 'block' -CANCEL = 'cancel' -REVERSE = 'reverse' -MOUSE_SWITCH_TYPE = 'mouse' -KEYBOARD_SWITCH_TYPE = 'keyboard' -KEYBOARD_KEY_LIST = {"Shift R" : "Shift_R", - "Shift L" : "Shift_L", - "Alt Gr" : "ISO_Level3_Shift", - "Num Lock": "Num_Lock"} -DEFAULT_KEYBOARD_KEY = 'Shift R' -DEFAULT_MOUSE_BUTTON = '1' -MIN_STEP_TIME = 50 -MAX_STEP_TIME = 5000 -TIME_SINGLE_INCREMENT = 1 -TIME_MULTI_INCREMENT = 10 -DEFAULT_STEP_TIME = 1000 -DEFAULT_SCANNING_TYPE = ROW -DEFAULT_SWITCH_TYPE = KEYBOARD_SWITCH_TYPE - -class ScanMaster(): - def __init__(self, root_window, keyboard=None): - self.root_window = root_window - self._timer = 0 - self._scan_path = None - self.started = False - - SettingsManager.step_time.connect("value-changed", - self._on_step_time_changed) - SettingsManager.scanning_type.connect("value-changed", - self._on_scanning_type_changed) - if keyboard: - self.set_keyboard(keyboard) - - def start(self): - if self.started: return - - if self._timer == 0: - self._timer = gobject.timeout_add( - int(SettingsManager.step_time.value*1000), self._scan) - - self._grab_mouse_events() - - pyatspi.Registry.registerKeystrokeListener( - self._on_key_pressed, mask=0, kind=(pyatspi.KEY_PRESSED_EVENT,)) - - def stop(self): - if self.started: return - - self._ungrab_mouse_events() - - if self._last_block is not None: - self._multi_map(lambda x: x.scan_highlight_clear(), self._last_block) - - if self._timer != 0: - gobject.source_remove(self._timer) - self._timer = 0 - - pyatspi.Registry.deregisterKeystrokeListener( - self._on_key_pressed, mask=0, kind=pyatspi.KEY_PRESSED_EVENT) - - def _on_scanning_type_changed(self, setting, val): - layout = self.keyboard.get_current_layout() - if SettingsManager.scanning_type.value == ROW: - self._blocks = layout.get_scan_rows() - else: - self._blocks = layout.get_scan_blocks() - - def _on_step_time_changed(self, setting, val): - if self._timer != 0: - gobject.source_remove(self._timer) - self._timer = gobject.timeout_add(int(1000*val), self._scan) - - def _on_layout_activated(self, keyboard, layout, num): - if SettingsManager.scanning_type.value == ROW: - self._blocks = layout.get_scan_rows() - else: - self._blocks = layout.get_scan_blocks() - if SettingsManager.reverse_scanning.value: - self._scan_path = [0] - else: - self._scan_path = [-1] - - def set_keyboard(self, keyboard): - self._last_block = None - keyboard.connect("switch-page", self._on_layout_activated) - self.keyboard = keyboard - - def _multi_map(self, func, array): - if isinstance(array, list): - for item in array: - self._multi_map(func, item) - else: - func(array) - - def _get_element_at_path(self, array, path): - element = array - for index in path: - element = element[index] - return element - - def _get_next_reverse_block(self): - cancel = False - - if self._scan_path[-1] > 0: - self._scan_path = self._scan_path[:-1] + [0] - - self._scan_path += [self._scan_path.pop() - 1] - - try: - block = self._get_element_at_path(self._blocks, - self._scan_path) - except IndexError: - if len(self._scan_path) == 1: - block = self._blocks[-1] - self._scan_path = [-1] - else: - block = self._get_element_at_path( - self._blocks, self._scan_path[:-1]) - self._scan_path = self._scan_path[:-1] + [0] - cancel = True - - return cancel, block - - - def _get_next_block(self): - cancel = False - self._scan_path += [self._scan_path.pop() + 1] - try: - block = self._get_element_at_path(self._blocks, - self._scan_path) - except IndexError: - if len(self._scan_path) == 1: - block = self._blocks[0] - self._scan_path = [0] - else: - block = self._get_element_at_path( - self._blocks, self._scan_path[:-1]) - self._scan_path = self._scan_path[:-1] + [-1] - cancel = True - - return cancel, block - - def _scan(self): - if self._scan_path is None: return True - - if self._last_block is not None: - self._multi_map(lambda x: x.scan_highlight_clear(), self._last_block) - - if SettingsManager.reverse_scanning.value: - self._cancel, next_block = self._get_next_reverse_block() - else: - self._cancel, next_block = self._get_next_block() - - if self._cancel: - self._multi_map(lambda x: x.scan_highlight_cancel(), next_block) - elif isinstance(next_block, list): - if SettingsManager.scanning_type.value == ROW: - self._multi_map(lambda x: x.scan_highlight_row(), next_block) - else: - self._multi_map(lambda x: x.scan_highlight_block(), next_block) - else: - self._multi_map(lambda x: x.scan_highlight_key(), next_block) - - self._last_block = next_block - return True - - def _do_switch(self): - if self._cancel: - assert(len(self._scan_path) > 1) - self._scan_path.pop() - elif isinstance(self._last_block, list): - assert(len(self._last_block) > 0) - if SettingsManager.reverse_scanning.value: - self._scan_path.append(0) - else: - self._scan_path.append(-1) - else: - self._last_block.clicked() - - def _grab_mouse_events(self): - Gdk.event_handler_set(self._mouse_handler, None) - - def _ungrab_mouse_events(self): - Gdk.event_handler_set(Gtk.main_do_event, None) - - def _mouse_handler(self, event, user_data): - if SettingsManager.switch_type.value != MOUSE_SWITCH_TYPE or \ - event.type not in (Gdk.EventType.BUTTON_PRESS, - Gdk.EventType.ENTER_NOTIFY): - Gtk.main_do_event(event) - return - - if self.root_window.get_window().is_visible(): - if event.type == Gdk.EventType.BUTTON_PRESS and \ - str(event.button.button) == \ - SettingsManager.mouse_button.value: - self._do_switch() - elif event.type != Gdk.EventType.ENTER_NOTIFY: - Gtk.main_do_event(event) - else: - Gtk.main_do_event(event) - - def _on_key_pressed(self, event): - if SettingsManager.switch_type.value == KEYBOARD_SWITCH_TYPE and \ - SettingsManager.keyboard_key.value == event.event_string: - self._do_switch() |