linuxOS_AP05/debian/test/usr/lib/python3/dist-packages/Onboard/Config.py
2025-09-26 09:40:02 +08:00

2231 lines
85 KiB
Python

# -*- coding: utf-8 -*-
# Copyright © 2008-2010 Chris Jones <tortoise@tortuga>
# Copyright © 2008-2011 Francesco Fumanti <francesco.fumanti@gmx.net>
# Copyright © 2012 Gerd Kohlberger <lowfi@chello.at>
# Copyright © 2009, 2011-2017 marmuta <marmvta@gmail.com>
#
# This file is part of Onboard.
#
# Onboard is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Onboard 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
File containing Config singleton.
"""
from __future__ import division, print_function, unicode_literals
import os
import sys
import locale
from shutil import copytree
from optparse import OptionParser, OptionGroup
from Onboard.Version import require_gi_versions
require_gi_versions()
from gi.repository import Gtk, Gio, GLib
from Onboard.WindowUtils import show_confirmation_dialog
from Onboard.utils import Version, \
unicode_str, XDGDirs, chmodtree, \
Process, hexcolor_to_rgba, TermColors
from Onboard.definitions import DesktopEnvironmentEnum, \
StatusIconProviderEnum, \
InputEventSourceEnum, \
TouchInputEnum, \
LearningBehavior, \
RepositionMethodEnum, \
Handle, DockingEdge, DockingMonitor
from Onboard.ConfigUtils import ConfigObject
from Onboard.ClickSimulator import CSMousetweaks0, CSMousetweaks1
from Onboard.Exceptions import SchemaError
### Logging ###
import logging
_logger = logging.getLogger("Config")
###############
# gsettings schemas
SCHEMA_ONBOARD = "org.onboard"
SCHEMA_KEYBOARD = "org.onboard.keyboard"
SCHEMA_WINDOW = "org.onboard.window"
SCHEMA_WINDOW_LANDSCAPE = "org.onboard.window.landscape"
SCHEMA_WINDOW_PORTRAIT = "org.onboard.window.portrait"
SCHEMA_ICP = "org.onboard.icon-palette"
SCHEMA_ICP_LANDSCAPE = "org.onboard.icon-palette.landscape"
SCHEMA_ICP_PORTRAIT = "org.onboard.icon-palette.portrait"
SCHEMA_AUTO_SHOW = "org.onboard.auto-show"
SCHEMA_UNIVERSAL_ACCESS = "org.onboard.universal-access"
SCHEMA_THEME = "org.onboard.theme-settings"
SCHEMA_LOCKDOWN = "org.onboard.lockdown"
SCHEMA_SCANNER = "org.onboard.scanner"
SCHEMA_TYPING_ASSISTANCE = "org.onboard.typing-assistance"
SCHEMA_WORD_SUGGESTIONS = "org.onboard.typing-assistance.word-suggestions"
SCHEMA_GSS = "org.gnome.desktop.screensaver"
SCHEMA_GDI = "org.gnome.desktop.interface"
SCHEMA_GDA = "org.gnome.desktop.a11y.applications"
SCHEMA_UNITY_GREETER = "com.canonical.unity-greeter"
MODELESS_GKSU_KEY = "/apps/gksu/disable-grab" # old gconf key, unused
# hard coded defaults
DEFAULT_X = 100 # Make sure these match the schema defaults,
DEFAULT_Y = 50 # else dconf data migration won't happen.
DEFAULT_WIDTH = 700
DEFAULT_HEIGHT = 205
# Default rect on Nexus 7
# landscape x=65, y=500, w=1215 h=300
# portrait x=55, y=343, w=736 h=295
DEFAULT_ICP_X = 100 # Make sure these match the schema defaults,
DEFAULT_ICP_Y = 50 # else dconf data migration won't happen.
DEFAULT_ICP_HEIGHT = 64
DEFAULT_ICP_WIDTH = 64
DEFAULT_LAYOUT = "Compact"
DEFAULT_THEME = "Classic Onboard"
DEFAULT_COLOR_SCHEME = "Classic Onboard"
START_ONBOARD_XEMBED_COMMAND = "onboard --xid"
INSTALL_DIR = "/usr/share/onboard"
LOCAL_INSTALL_DIR = "/usr/local/share/onboard"
USER_DIR = "onboard"
SYSTEM_DEFAULTS_FILENAME = "onboard-defaults.conf"
DEFAULT_WINDOW_HANDLES = list(Handle.RESIZE_MOVE)
DEFAULT_FREQUENCY_TIME_RATIO = 75 # 0=100% frequency, 100=100% time (last use)
SCHEMA_VERSION_0_97 = Version(1, 0) # Onboard 0.97
SCHEMA_VERSION_0_98 = Version(2, 0) # Onboard 0.97.1
SCHEMA_VERSION_0_99 = Version(2, 1) # Onboard 0.99.0
SCHEMA_VERSION_1_1 = Version(2, 2)
SCHEMA_VERSION_1_2 = Version(2, 3)
SCHEMA_VERSION = SCHEMA_VERSION_1_2
# enum for simplified number of window_handles
class NumResizeHandles:
NONE = 0
NORESIZE = 1
SOME = 2
ALL = 3
class Config(ConfigObject):
"""
Singleton Class to encapsulate the gsettings stuff and check values.
"""
# launched by ...
(LAUNCHER_NONE,
LAUNCHER_GSS,
LAUNCHER_UNITY_GREETER) = range(3)
# extension of layout files
LAYOUT_FILE_EXTENSION = ".onboard"
# A copy of snippets so that when the list changes in gsettings we can
# tell which items have changed.
_last_snippets = None
# Margin to leave around labels
LABEL_MARGIN = (1, 1)
# Horizontal label alignment
DEFAULT_LABEL_X_ALIGN = 0.5
# Vertical label alignment
DEFAULT_LABEL_Y_ALIGN = 0.5
# layout group for independently sized superkey labels
SUPERKEY_SIZE_GROUP = "super"
# width of frame around onboard when window decoration is disabled
UNDECORATED_FRAME_WIDTH = 6.0
# width of frame around popup windows
POPUP_FRAME_WIDTH = 5.0
# radius of the rounded window corners
CORNER_RADIUS = 10
# y displacement of the key face of dish keys
DISH_KEY_Y_OFFSET = 0.8
# raised border size of dish keys
DISH_KEY_BORDER = (2.5, 2.5)
# minimum time keys are drawn in pressed state
UNPRESS_DELAY = 0.15
# Margin to leave around wordlist labels; smaller margins leave
# more room for prediction choices
WORDLIST_LABEL_MARGIN = (2, 2)
# Gap between wordlist buttons
WORDLIST_BUTTON_SPACING = (0.5, 0.5)
# Gap between wordlist predictions and correctios
WORDLIST_ENTRY_SPACING = (1.0, 1.0)
# index of currently active pane, not stored in gsettings
active_layer_index = 0
# threshold protect window move/resize
drag_protection = True
# Allow to iconify onboard when neither icon-palette nor
# status-icon are enabled, else hide and show the window.
# Iconifying is shaky in unity and gnome-shell. After unhiding
# from launcher once, the WM won't allow to unminimize onboard
# itself anymore for auto-show. (Precise)
allow_iconifying = False
# Gdk window scaling factor
window_scaling_factor = 1.0
_xembed_background_rgba = None
_desktop_environment = None
_source_path = None
def __new__(cls, *args, **kwargs):
"""
Singleton magic.
"""
if not hasattr(cls, "self"):
cls.self = object.__new__(cls, *args, **kwargs)
cls.self.construct()
return cls.self
def __init__(self):
"""
This constructor is still called multiple times.
Do nothing here and use the singleton constructor construct() instead.
Don't call base class constructors.
"""
pass
def construct(self):
"""
Singleton constructor, runs only once.
First intialization stage that runs before the
single instance check. Only do the bare minimum here.
"""
# parse command line
parser = OptionParser()
group = OptionGroup(parser, "General Options")
group.add_option("-l", "--layout", dest="layout",
help=_format("Layout file ({}) or name",
self.LAYOUT_FILE_EXTENSION))
group.add_option("-t", "--theme", dest="theme",
help=_("Theme file (.theme) or name"))
group.add_option("-s", "--size", dest="size",
help=_("Window size, widthxheight"))
group.add_option("-x", type="int", dest="x", help=_("Window x position"))
group.add_option("-y", type="int", dest="y", help=_("Window y position"))
parser.add_option_group(group)
group = OptionGroup(parser, "Advanced Options")
group.add_option("-e", "--xid", action="store_true", dest="xid_mode",
help=_("Start in XEmbed mode, e.g. for gnome-screensaver"))
group.add_option("-m", "--allow-multiple-instances",
action="store_true", dest="allow_multiple_instances",
help=_("Allow multiple Onboard instances"))
group.add_option("--not-show-in", dest="not_show_in",
metavar="DESKTOPS",
help=_("Silently fail to start in the given desktop "
"environments. DESKTOPS is a comma-separated list of "
"XDG desktop names, e.g. GNOME for GNOME Shell."
))
group.add_option("-D", "--startup-delay",
type="float", dest="startup_delay",
help=_("Delay showing the initial keyboard window "
"by STARTUP_DELAY seconds (default 0.0)."))
group.add_option("-a", "--keep-aspect", action="store_true",
dest="keep_aspect_ratio",
help=_("Keep aspect ratio when resizing the window"))
group.add_option("-q", "--quirks", dest="quirks_name",
help=_("Override auto-detection and manually select quirks\n"
"QUIRKS={metacity|compiz|mutter}"))
parser.add_option_group(group)
group = OptionGroup(parser, "Debug Options")
group.add_option("-d", "--debug", type="str", dest="debug",
metavar="ARG",
help="Set logging level or range of levels \n"
"ARG=MIN_LEVEL[-MAX_LEVEL]\n"
"LEVEL={all|event|atspi|debug|info|warning|error|\n"
"critical}\n")
group.add_option("", "--launched-by", type="str", dest="launched_by",
metavar="ARG",
help="Simulate being launched by certain XEmbed sockets. \n"
"Use this together with option --xid. \n"
"ARG={unity-greeter|gnome-screen-saver}\n")
group.add_option("-g", "--log-learning",
action="store_true", dest="log_learn", default=False,
help="log all learned text; off by default")
parser.add_option_group(group)
options = parser.parse_args()[0]
self.options = options
self.xid_mode = options.xid_mode
self.log_learn = options.log_learn
self.quirks_name = options.quirks_name
self.quirks = None # WMQuirks instance, provided by KbdWindow for now
self.startup_delay = options.startup_delay
# optionally log to file; everything, including stack traces
if 0:
options.debug = "debug"
logfile = open("/tmp/onboard.log", "w")
sys.stdout = logfile
sys.stderr = logfile
# setup logging
min_level_name = None
max_level_name = None
if options.debug:
level_range = options.debug.split("-")
if len(level_range) >= 2:
min_level_name, max_level_name = level_range[:2]
else:
min_level_name = level_range[0]
self._init_logging(min_level_name, max_level_name)
# Add basic config children for usage before the single instance check.
# All the others are added in self._init_keys().
self.children = []
self.gnome_a11y = self.add_optional_child(ConfigGDA)
if self.gnome_a11y: # schema may not exist on Gentoo (LP: #1402558)
self.gnome_a11y.init_from_gsettings()
# detect who launched us
self.launched_by = self.LAUNCHER_NONE
if self.xid_mode:
if options.launched_by:
if options.launched_by == "gnome-screensaver":
self.launched_by = self.LAUNCHER_GSS
elif options.launched_by == "unity-greeter":
self.launched_by = self.LAUNCHER_UNITY_GREETER
else:
if Process.was_launched_by("gnome-screensaver"):
self.launched_by = self.LAUNCHER_GSS
elif "UNITY_GREETER_DBUS_NAME" in os.environ:
self.launched_by = self.LAUNCHER_UNITY_GREETER
self.is_running_from_source = self._is_running_from_source()
if self.is_running_from_source:
_logger.warning("Starting in project directory, "
"importing local packages and extensions.")
def init(self):
"""
Second initialization stage.
Call this after single instance checking on application start.
"""
# call base class constructor once logging is available
try:
ConfigObject.__init__(self)
except SchemaError as e:
_logger.error(unicode_str(e))
sys.exit()
# log desktop environment
_logger.debug("Desktop environment: {}"
.format(self.get_desktop_environment().name))
# log how we were launched
if _logger.isEnabledFor(logging.DEBUG):
_logger.debug("command line: " + str(sys.argv))
#_logger.debug("environment: " + str(os.environ))
cmdline = Process.get_launch_process_cmdline()
_logger.debug("lauched by, process: '{}'".format(cmdline))
_logger.debug("lauched by, detected: {}".format(self.launched_by))
# init paths
self.install_dir = self._get_install_dir()
self.user_dir = self._get_user_dir()
self._image_search_paths = []
# migrate old user dir ".sok" or ".onboard" to XDG data home
if not os.path.exists(self.user_dir):
old_user_dir = os.path.join(os.path.expanduser("~"), ".onboard")
if not os.path.exists(old_user_dir):
old_user_dir = os.path.join(os.path.expanduser("~"), ".sok")
if os.path.exists(old_user_dir):
_logger.info(_format("Migrating user directory '{}' to '{}'.", \
old_user_dir, self.user_dir))
# Copy the data directory
try:
copytree(old_user_dir, self.user_dir)
chmodtree(self.user_dir, 0o700, True) # honor XDG spec
except OSError as ex: # python >2.5
_logger.error(_format("failed to migrate user directory. ") + \
unicode_str(ex))
# Return paths in gsettings to basenames. Custom path
# information will be lost.
try:
filter = lambda x: os.path.splitext(os.path.basename(x))[0]
s = Gio.Settings.new(SCHEMA_ONBOARD)
s["layout"] = filter(s["layout"])
s["theme"] = filter(s["theme"])
s["system-theme-associations"] = \
{k : filter(v) \
for k, v in s["system-theme-associations"].items()}
except Exception as ex:
_logger.warning("migrating gsettings paths failed: " + \
unicode_str(ex))
# Migrate old user language model to language specific
# model for Onboard 1.0.
from Onboard.WPEngine import ModelCache
old_fn = ModelCache.get_filename("lm:user:user")
if os.path.exists(old_fn):
lang_id = self.get_system_default_lang_id()
new_fn = ModelCache.get_filename("lm:user:" + lang_id)
old_bak = ModelCache.get_backup_filename(old_fn)
new_bak = ModelCache.get_backup_filename(new_fn)
_logger.info("Starting migration, user.lm has been deprecated.")
for old, new in [[old_fn, new_fn], [old_bak, new_bak]]:
if os.path.exists(old):
if os.path.exists(new):
_logger.info("Migration target already exists, "
"skipping renaming "
"'{}' to '{}'." \
.format(old, new))
break # skip backup
_logger.info("Migrating user language model "
"'{}' to '{}'." \
.format(old, new))
try:
os.rename(old, new)
except OSError as ex:
_logger.error("Failed to migrate "
"user language model. " + \
unicode_str(ex))
break
# Load system defaults (if there are any, not required).
# Used for distribution specific settings, aka branding.
paths = XDGDirs.get_all_config_dirs(USER_DIR) + \
[self.install_dir, "/etc/onboard"]
paths = [os.path.join(p, SYSTEM_DEFAULTS_FILENAME) for p in paths]
self.load_system_defaults(paths)
# initialize all property values
used_system_defaults = self.init_properties(self.options)
self._update_xembed_background_rgba()
# Make sure there is a 'Default' entry when tracking the system theme.
# 'Default' is the theme used when encountering a so far unknown
# gtk-theme.
theme_assocs = self.system_theme_associations.copy()
if not "Default" in theme_assocs:
theme_assocs["Default"] = ""
self.system_theme_associations = theme_assocs
# Remember command line theme for system theme tracking.
if self.options.theme:
self.theme_key.modified = True # force writing on next occasion
self.remember_theme(self.theme)
# load theme
global Theme
from Onboard.Appearance import Theme
_logger.info("Theme candidates {}" \
.format(self._get_theme_candidates()))
self.load_theme()
# misc initializations
self._last_snippets = dict(self.snippets) # store a copy
# init state of mousetweaks' click-type window
if self.mousetweaks:
self.mousetweaks.init_click_type_window_visible(
self.universal_access.hide_click_type_window)
# remember if we are running under GDM
self.running_under_gdm = 'RUNNING_UNDER_GDM' in os.environ
# tell config objects that their properties are valid now
self.on_properties_initialized()
# Work around changes in preferences having no effect in Saucy.
# If there is an unbalanced self.delay() somewhere I haven't found it.
self.apply()
_logger.debug("Leaving init")
def cleanup(self):
# This used to stop dangling main windows from responding
# when restarting. Restarts don't happen anymore, keep
# this for now anyway.
self.disconnect_notifications()
if self.mousetweaks:
self.mousetweaks.cleanup()
def final_cleanup(self):
if self.mousetweaks:
self.mousetweaks.restore_click_type_window_visible(
self.universal_access.enable_click_type_window_on_exit and \
not self.xid_mode)
def _init_logging(self, min_level_name, max_level_name):
LEVEL_ATSPI = 6
LEVEL_EVENT = 5
levels = {
# builtin levels
"CRITICAL" : (50, TermColors.RED),
"ERROR" : (40, TermColors.RED),
"WARNING" : (30, TermColors.YELLOW),
"INFO" : (20, TermColors.GREEN),
"DEBUG" : (10, TermColors.BLUE),
# custom levels
"ATSPI" : (LEVEL_ATSPI, TermColors.CYAN),
"EVENT" : (LEVEL_EVENT, TermColors.MAGENTA),
"ALL" : ( 1, None),
"NOTSET" : ( 0, None),
}
for name, (level, color) in levels.items():
if logging.getLevelName(level) != name:
logging.addLevelName(level, name)
class CustomLogger(logging.Logger):
def __init__(self, *args):
self.LEVEL_ATSPI = LEVEL_ATSPI
self.LEVEL_EVENT = LEVEL_EVENT
logging.Logger.__init__(self, *args)
def atspi(self, msg, *args, **kwargs):
if self.isEnabledFor(LEVEL_ATSPI):
self._log(LEVEL_ATSPI, msg, args, **kwargs)
def event(self, msg, *args, **kwargs):
if self.isEnabledFor(LEVEL_EVENT):
self._log(LEVEL_EVENT, msg, args, **kwargs)
class CustomFilter(object):
def __init__(self, max_level):
self._max_level = max_level
def filter(self, logRecord):
return logRecord.levelno <= self._max_level
class ColorFormatter(logging.Formatter):
def __init__(self, msgfmt, log_levels, use_colors):
self._log_levels = log_levels
self._use_colors = use_colors
self._tcs = TermColors() if use_colors else None
msg = self._substitute_variables(msgfmt, use_colors)
logging.Formatter.__init__(self, msg,
datefmt="%H:%M:%S",
style="{")
def _substitute_variables(self, msg, use_colors):
if use_colors:
msg = msg.replace("{RESET}",
self._tcs.get(TermColors.RESET))
msg = msg.replace("{BOLD}",
self._tcs.get(TermColors.BOLD))
else:
msg = msg.replace("{RESET}", "")
msg = msg.replace("{BOLD}", "")
return msg
def formatMessage(self, record):
level_name = record.levelname
if self._use_colors and \
level_name in self._log_levels:
level, color = self._log_levels[level_name]
color_seq = self._tcs.get(color)
else:
color_seq = ""
record.__dict__["levelcolor"] = color_seq
debug = bool(min_level_name)
record.__dict__["name_width"] = 21 if debug else 1
record.__dict__["level_width"] = 7 if debug else 1
record.__dict__["name_"] = record.name + \
("" if debug else ":")
return logging.Formatter.formatMessage(self, record)
msgfmt = ("{asctime}.{msecs:03.0f} "
"{levelcolor}{levelname:{level_width}}{RESET} "
"{BOLD}{name_:{name_width}}{RESET} "
"{message}")
handler = logging.StreamHandler()
try:
is_tty = handler.stream.isatty() # color only in interact. terminal
except:
is_tty = False
handler.setFormatter(ColorFormatter(msgfmt, levels, is_tty))
root = logging.getLogger()
root.addHandler(handler)
logging.setLoggerClass(CustomLogger)
if min_level_name:
root.setLevel(min_level_name.upper())
if max_level_name:
max_level_name = max_level_name.upper()
if max_level_name in levels:
max_level, color = levels[max_level_name]
handler.addFilter(CustomFilter(max_level))
def _init_keys(self):
""" Create key descriptions """
self.schema = SCHEMA_ONBOARD
self.sysdef_section = "main"
self.add_key("schema-version", "") # is assigned SCHEMA_VERSION on first start
self.add_key("use-system-defaults", False)
self.layout_key = \
self.add_key("layout", DEFAULT_LAYOUT)
self.theme_key = \
self.add_key("theme", DEFAULT_THEME)
self.add_key("system-theme-tracking-enabled", True)
self.add_key("system-theme-associations", {}, 'a{ss}')
self.add_key("snippets", {}, "as")
self.add_key("show-status-icon", True)
self.add_key("status-icon-provider", StatusIconProviderEnum.AppIndicator,
enum={"auto" : 0,
"GtkStatusIcon" : 1,
"AppIndicator" : 2,
})
self.add_key("start-minimized", False)
self.add_key("show-tooltips", True)
self.add_key("key-label-font", "") # default font for all themes
self.add_key("key-label-overrides", {}, "as") # default labels for all themes
self.add_key("current-settings-page", 0)
self.add_key("xembed-onboard", False, prop="onboard_xembed_enabled")
self.add_key("xembed-aspect-change-range", [0, 1.6])
self.add_key("xembed-background-color", "#0000007F")
self.add_key("xembed-background-image-enabled", True)
self.add_key("xembed-unity-greeter-offset-x", 85.0)
self.keyboard = ConfigKeyboard()
self.window = ConfigWindow()
self.icp = ConfigICP(self)
self.auto_show = ConfigAutoShow()
self.universal_access = ConfigUniversalAccess(self)
self.theme_settings = ConfigTheme(self)
self.lockdown = ConfigLockdown(self)
self.scanner = ConfigScanner(self)
self.typing_assistance = ConfigTypingAssistance(self)
self.gss = ConfigGSS(self)
self.gdi = ConfigGDI(self)
self.children += [self.keyboard,
self.window,
self.icp,
self.auto_show,
self.universal_access,
self.theme_settings,
self.lockdown,
self.gss,
self.gdi,
self.scanner,
self.typing_assistance]
# mousetweaks (optional)
for _class in [CSMousetweaks1, CSMousetweaks0]:
try:
self.mousetweaks = _class()
self.children.append(self.mousetweaks)
break
except (SchemaError, ImportError, RuntimeError) as e:
_logger.info(unicode_str(e))
self.mousetweaks = None
if self.mousetweaks is None:
_logger.warning("mousetweaks GSettings schema not found, "
"mousetweaks integration disabled.")
# unity greeter (very optional)
self.unity_greeter = None
if self.launched_by == self.LAUNCHER_UNITY_GREETER:
try:
self.unity_greeter = ConfigUnityGreeter(self)
self.children.append(self.unity_greeter)
except (SchemaError, ImportError) as e:
_logger.warning(unicode_str(e))
def init_from_gsettings(self):
"""
Overloaded to migrate old dconf data to new gsettings schemas
"""
ConfigObject.init_from_gsettings(self)
# --- onboard 0.97 -> 0.98 --------------------------------------------
format = Version.from_string(self.schema_version)
if format < SCHEMA_VERSION_0_98:
_logger.info("Migrating dconf values from before v0.98: "
"/apps/onboard -> /org/onboard")
self.migrate_dconf_tree("apps.", "org.")
# --- onboard 0.96 -> 0.97 ----------------------------------------
format = Version.from_string(self.schema_version)
if format < SCHEMA_VERSION_0_97:
_logger.info("Migrating dconfs values from before v0.97")
self._migrate_to_0_97()
# --- onboard 1.0.0 -> 1.1.0-------------------------------------------
format = Version.from_string(self.schema_version)
if format < SCHEMA_VERSION_1_1:
_logger.info("Migrating dconf values from before v1.1.0 ")
self._migrate_to_1_1()
# --- onboard 1.1.0 -> 1.2.0-------------------------------------------
format = Version.from_string(self.schema_version)
if format < SCHEMA_VERSION_1_2:
_logger.info("Migrating dconf values from before v1.2.0 ")
self._migrate_to_1_2()
self.schema_version = SCHEMA_VERSION.to_string()
def _migrate_to_1_2(self):
"""
reposition-method split into reposition-method-floating
and reposition-method-docked
"""
co = self.auto_show
co.delay()
co.migrate_dconf_key("/org/onboard/auto-show/reposition-method",
"reposition-method-floating")
co.migrate_dconf_key("/org/onboard/auto-show/reposition-method",
"reposition-method-docked")
co.apply()
def _migrate_to_1_1(self):
""" resize_handles renamed to window_handles and movement added """
def migrate_resize_handles(co, dconf_path, key):
gskey = co.find_key(key)
co.delay()
co.migrate_dconf_value(dconf_path, gskey)
# add new "MOVE" entry
handles = getattr(co, gskey.prop)[:]
if not Handle.MOVE in handles:
handles += (Handle.MOVE,)
setattr(co, gskey.prop, handles)
co.apply()
migrate_resize_handles(self.window,
"/org/onboard/window/resize-handles",
"window-handles")
migrate_resize_handles(self.icp,
"/org/onboard/icon-palette/resize-handles",
"window-handles")
def _migrate_to_0_97(self):
# window rect moves from org.onboard to
# org.onboard.window.landscape/portrait
co = self.window.landscape
if co.gskeys["x"].is_default() and \
co.gskeys["y"].is_default() and \
co.gskeys["width"].is_default() and \
co.gskeys["height"].is_default():
co.delay()
co.migrate_dconf_value("/apps/onboard/x", co.gskeys["x"])
co.migrate_dconf_value("/apps/onboard/y", co.gskeys["y"])
co.migrate_dconf_value("/apps/onboard/width", co.gskeys["width"])
co.migrate_dconf_value("/apps/onboard/height", co.gskeys["height"])
co.apply()
# icon-palette rect moves from org.onboard.icon-palette to
# org.onboard.icon-palette.landscape/portrait
co = self.icp.landscape
if co.gskeys["x"].is_default() and \
co.gskeys["y"].is_default() and \
co.gskeys["width"].is_default() and \
co.gskeys["height"].is_default():
co.delay()
co.migrate_dconf_value("/apps/onboard/icon-palette/x", co.gskeys["x"])
co.migrate_dconf_value("/apps/onboard/icon-palette/y", co.gskeys["y"])
co.migrate_dconf_value("/apps/onboard/icon-palette/width", co.gskeys["width"])
co.migrate_dconf_value("/apps/onboard/icon-palette/height", co.gskeys["height"])
co.apply()
# move keys from root to window
co = self.window
co.migrate_dconf_key("/apps/onboard/window-decoration", "window-decoration")
co.migrate_dconf_key("/apps/onboard/force-to-top", "force-to-top")
co.migrate_dconf_key("/apps/onboard/transparent-background", "transparent-background")
co.migrate_dconf_key("/apps/onboard/transparency", "transparency")
co.migrate_dconf_key("/apps/onboard/background-transparency", "background-transparency")
co.migrate_dconf_key("/apps/onboard/enable-inactive-transparency", "enable-inactive-transparency")
co.migrate_dconf_key("/apps/onboard/inactive-transparency", "inactive-transparency")
co.migrate_dconf_key("/apps/onboard/inactive-transparency-delay", "inactive-transparency-delay")
# accessibility keys move from root to universal-access
co = self.universal_access
co.migrate_dconf_key("/apps/onboard/hide-click-type-window", "hide-click-type-window")
co.migrate_dconf_key("/apps/onboard/enable-click-type-window-on-exit", "enable-click-type-window-on-exit")
# move keys from root to keyboard
co = self.keyboard
co.migrate_dconf_key("/apps/onboard/show-click-buttons", "show-click-buttons")
##### handle special keys only valid in system defaults #####
def _read_sysdef_section(self, parser):
super(self.__class__, self)._read_sysdef_section(parser)
# Convert the simplified superkey_label setting into
# the more general key_label_overrides setting.
sds = self.system_defaults
if "superkey_label" in sds:
overrides = sds.get( "key_label_overrides", {})
group = self.SUPERKEY_SIZE_GROUP \
if sds.get("superkey_label_independent_size") else ""
for key_id in ["LWIN", "RWIN"]:
overrides[key_id] = (sds["superkey_label"], group)
sds["key_label_overrides"] = overrides
def _convert_sysdef_key(self, gskey, sysdef, value):
# key exclusive to system defaults?
if sysdef in ["superkey-label",
"superkey-label-independent-size"]:
return value
else:
return super(self.__class__, self). \
_convert_sysdef_key(gskey, sysdef, value)
##### property helpers #####
def _unpack_key_label_overrides(self, value):
return self.unpack_string_list(value, "a{s[ss]}")
def _pack_key_label_overrides(self, value):
return self.pack_string_list(value)
def _unpack_snippets(self, value):
return self.unpack_string_list(value, "a{i[ss]}")
def _pack_snippets(self, value):
return self.pack_string_list(value)
def _post_notify_layout(self):
self._invalidate_layout_resource_search_paths()
# Property layout_filename, linked to gsettings key "layout".
# layout_filename may only get/set a valid filename,
# whereas layout also allows to get/set only the basename of a layout.
def layout_filename_notify_add(self, callback):
self.layout_notify_add(callback)
def get_layout_filename(self):
gskey = self.layout_key
return self.find_layout_filename(gskey.value, gskey.key,
self.LAYOUT_FILE_EXTENSION,
os.path.join(self.install_dir,
"layouts", DEFAULT_LAYOUT +
self.LAYOUT_FILE_EXTENSION))
def set_layout_filename(self, filename):
if filename and os.path.exists(filename):
self.layout = filename
else:
_logger.warning(_format("layout '{filename}' does not exist", \
filename=filename))
layout_filename = property(get_layout_filename, set_layout_filename)
def get_fallback_layout_filename(self):
""" Layout file to fallback to when the initial layout won't load """
return self.find_layout_filename(DEFAULT_LAYOUT, "layout",
self.LAYOUT_FILE_EXTENSION)
def find_layout_filename(self, filename, description,
extension = "", final_fallback = ""):
""" Find layout file, either the root layout or an include file. """
return self._get_user_sys_filename(
filename = filename,
description = description,
user_filename_func = lambda x: \
os.path.join(self.user_dir, "layouts", x) + extension,
system_filename_func = lambda x: \
os.path.join(self.install_dir, "layouts", x) + extension,
final_fallback = final_fallback)
# Property theme_filename, linked to gsettings key "theme".
# theme_filename may only get/set a valid filename,
# whereas theme also allows to get/set only the basename of a theme.
def theme_filename_notify_add(self, callback):
self.theme_notify_add(callback)
def get_theme_filename(self):
candidates = self._get_theme_candidates()
for theme in candidates:
if theme:
filename = self._expand_theme_filename(theme)
if filename:
return filename
return ""
def set_theme_filename(self, filename):
if filename and os.path.exists(filename):
self.remember_theme(filename)
else:
_logger.warning(_format("theme '{filename}' does not exist", \
filename=filename))
theme_filename = property(get_theme_filename, set_theme_filename)
def _expand_theme_filename(self, theme):
""" expand generic theme name """
return self._expand_user_sys_filename(theme,
user_filename_func = Theme.build_user_filename,
system_filename_func = Theme.build_system_filename)
def remember_theme(self, theme_filename):
if self.system_theme_tracking_enabled and \
self.gdi: # be defensive
gtk_theme = self.get_gtk_theme()
theme_assocs = self.system_theme_associations.copy()
theme_assocs[gtk_theme] = theme_filename
self.system_theme_associations = theme_assocs
self.set_theme(theme_filename)
def apply_theme(self, filename = None):
if not filename:
filename = self.get_theme_filename()
_logger.info(_format("Loading theme from '{}'", filename))
theme = Theme.load(filename)
if not theme:
_logger.error(_format("Unable to read theme '{}'", filename))
else:
# Save to gsettings
# Make sure gsettings is in sync with onboard (LP: 877601)
#self.theme = filename
theme.apply()
# Fix theme not saved to gesettings when switching
# system contrast themes.
# Possible gsettings bug in Precise (wasn't in Oneiric).
#self.apply()
return bool(theme)
def load_theme(self):
"""
Figure out which theme to load and load it.
"""
self.apply_theme()
def _get_theme_candidates(self):
"""
Return a list of themes to consider for loading.
Highest priority first.
"""
candidates = []
if self.system_theme_tracking_enabled:
theme_assocs = self.system_theme_associations
gtk_theme = self.get_gtk_theme()
candidates += [theme_assocs.get(gtk_theme, ""),
theme_assocs.get("Default", ""),
self.theme]
else:
candidates += ["",
"",
self.theme]
if self.system_defaults:
theme = self.system_defaults.get("theme", "")
else:
theme = ""
candidates.append(theme)
candidates.append(DEFAULT_THEME)
return candidates
def get_gtk_theme(self):
gtk_settings = Gtk.Settings.get_default()
if gtk_settings: # be defensive, don't know if this can fail
gtk_theme = gtk_settings.get_property('gtk-theme-name')
return gtk_theme
return None
def get_image_filename(self, image_filename):
"""
Returns an absolute path for a label image.
This function isn't linked to any gsettings key.
"""
if not self._image_search_paths:
self._image_search_paths = \
self._get_layout_resource_search_paths("images")
return self._get_resource_filename(image_filename, "image",
self._image_search_paths)
def _invalidate_layout_resource_search_paths(self):
""" Force re-generation of search paths at next opportunity. """
self._image_search_paths = []
def _get_layout_resource_search_paths(self, subdir):
"""
Look for files in directory of current layout, user- and
system directory, in that order.
"""
user_dir = os.path.join(self.user_dir, "layouts")
sys_dir = os.path.join(self.install_dir, "layouts")
if subdir:
user_dir = os.path.join(user_dir, subdir)
sys_dir = os.path.join(sys_dir, subdir)
paths = [user_dir, sys_dir]
# In case a full path was given for the layout file on command line,
# look relative to there first.
if self._is_valid_filename(self.layout):
dir_ = os.path.dirname(self.layout)
if subdir:
dir_ = os.path.join(dir_, "images")
try:
# make sure it isn't one of those we already know
same = os.path.isdir(user_dir) and \
os.path.samefile(dir_, sys_dir) or \
os.path.isdir(user_dir) and \
os.path.samefile(dir_, user_dir)
except Exception as ex:
_logger.error("samefile failed with '{}': {}" \
.format(dir_, unicode_str(ex)))
same = True
if not same:
paths = [dir_] + paths
return paths
def get_user_model_dir(self):
return os.path.join(self.user_dir, "models")
def get_system_model_dir(self):
return os.path.join(self.install_dir, "models")
def enable_hover_click(self, enable):
hide = self.universal_access.hide_click_type_window
if enable:
self.mousetweaks.allow_system_click_type_window(False, hide)
self.mousetweaks.set_active(True)
else:
self.mousetweaks.set_active(False)
self.mousetweaks.allow_system_click_type_window(True, hide)
def is_hover_click_active(self):
return bool(self.mousetweaks) and self.mousetweaks.is_active()
def is_visible_on_start(self):
return self.xid_mode or \
not self.start_minimized and \
not self.auto_show.enabled
def is_auto_show_enabled(self):
return not self.xid_mode and \
self.auto_show.enabled
def is_auto_hide_enabled(self):
return self.is_auto_hide_on_keypress_enabled() or \
self.is_tablet_mode_detection_enabled() or \
self.is_keyboard_device_detection_enabled()
def is_auto_hide_on_keypress_enabled(self):
return self.can_set_auto_hide() and \
self.auto_show.hide_on_key_press
def is_tablet_mode_detection_enabled(self):
return self.can_set_auto_hide() and \
self.auto_show.tablet_mode_detection_enabled
def is_keyboard_device_detection_enabled(self):
return self.can_set_auto_hide() and \
self.auto_show.keyboard_device_detection_enabled
def can_auto_show_reposition(self):
return self.is_auto_show_enabled() and \
self.get_auto_show_reposition_method() != RepositionMethodEnum.NONE
def get_auto_show_reposition_method(self):
if self.is_docking_enabled():
return self.auto_show.reposition_method_docked
else:
return self.auto_show.reposition_method_floating
def can_set_auto_hide(self):
""" Allowed to change auto hide? """
return self.is_auto_show_enabled() and \
self.is_event_source_xinput()
def is_force_to_top(self):
return self.window.force_to_top or self.is_docking_enabled()
def is_override_redirect(self):
return self.is_force_to_top() and \
self.quirks.can_set_override_redirect(self)
def is_docking_enabled(self):
return self.window.docking_enabled
def is_dock_expanded(self, orientation_co):
return self.window.docking_enabled and orientation_co.dock_expand
def are_word_suggestions_enabled(self):
return self.word_suggestions.enabled and not self.xid_mode
def are_spelling_suggestions_enabled(self):
return self.are_word_suggestions_enabled() and \
self.word_suggestions.spelling_suggestions_enabled
def is_spell_checker_enabled(self):
return self.are_spelling_suggestions_enabled() or \
self.typing_assistance.auto_capitalization or \
self.typing_assistance.auto_correction
def is_auto_capitalization_enabled(self):
return self.typing_assistance.auto_capitalization and not self.xid_mode
def is_typing_assistance_enabled(self):
return self.are_word_suggestions_enabled() or \
self.is_auto_capitalization_enabled()
def is_event_source_gtk(self):
return self.keyboard.input_event_source == InputEventSourceEnum.GTK
def is_event_source_xinput(self):
return self.keyboard.input_event_source == InputEventSourceEnum.XINPUT
def check_gnome_accessibility(self, parent = None):
if not self.xid_mode and \
not self.gdi.toolkit_accessibility:
question = _("Enabling auto-show requires Gnome Accessibility.\n\n"
"Onboard can turn on accessiblity now, however it is "
"recommended that you log out and back in "
"for it to reach its full potential.\n\n"
"Enable accessibility now?")
reply = show_confirmation_dialog(question, parent,
self.is_force_to_top())
if not reply == True:
return False
self.gdi.toolkit_accessibility = True
return True
def get_drag_threshold(self):
threshold = self.universal_access.gskeys["drag_threshold"].value
if threshold == -1:
# get the systems DND threshold
threshold = Gtk.Settings.get_default(). \
get_property("gtk-dnd-drag-threshold")
return threshold
def is_icon_palette_in_use(self):
"""
Show icon palette when there is no other means to unhide onboard.
Unhiding by unity launcher isn't available in force-to-top mode.
"""
return self.icp.in_use or self.is_icon_palette_last_unhide_option()
def is_icon_palette_last_unhide_option(self):
"""
Is the icon palette the last remaining way to unhide onboard?
Consider single instance check a way to always unhide onboard.
"""
return False
def has_unhide_option(self):
"""
No native ui visible to unhide onboard?
There might still be the launcher to unminimize it.
"""
return self.is_icon_palette_in_use() or self.show_status_icon
def has_window_decoration(self):
""" Force-to-top mode doesn't support window decoration """
return self.window.window_decoration and not self.is_force_to_top()
def get_sticky_state(self):
return not self.xid_mode and \
(self.window.window_state_sticky or self.is_force_to_top())
def is_inactive_transparency_enabled(self):
return self.window.enable_inactive_transparency and \
not self.scanner.enabled
def is_keep_window_aspect_ratio_enabled(self, orientation_co):
"""
Keep aspect ratio of the whole keyboard window?
Not recommended, no effect in MATE and elsewhere the
keyboard tends to shrink with each interaction.
"""
return ((self.window.keep_aspect_ratio or
self.options.keep_aspect_ratio) and
not self.xid_mode and
not self.is_docking_enabled())
def is_keep_frame_aspect_ratio_enabled(self, orientation_co):
"""
Keep aspect ratio of only the frame (keyboard area)
inside the keyboard window?
"""
return \
self.is_keep_xembed_frame_aspect_ratio_enabled() or \
self.is_keep_docking_frame_aspect_ratio_enabled(orientation_co)
def is_keep_xembed_frame_aspect_ratio_enabled(self):
return self.xid_mode and self.launched_by != self.LAUNCHER_NONE
def is_keep_docking_frame_aspect_ratio_enabled(self, orientation_co):
return (not self.xid_mode and
self.is_docking_enabled() and
self.is_dock_expanded(orientation_co))
def is_mousetweaks_active(self):
return self.mousetweaks and self.mousetweaks.is_active()
####### window handles (resize & move handles) #######
def window_handles_notify_add(self, callback):
self.window.window_handles_notify_add(callback)
self.icp.window_handles_notify_add(callback)
def window_handles_to_num_handles(self, handles):
""" Translate array of handles to simplified NumResizeHandles enum """
if len(handles) == 0:
return NumResizeHandles.NONE
if len(handles) == 1 and handles[0] == Handle.MOVE:
return NumResizeHandles.NORESIZE
if len(handles) == 8 + 1:
return NumResizeHandles.ALL
return NumResizeHandles.SOME
def num_handles_to_window_handles(self, num):
if num == NumResizeHandles.ALL:
handles = list(Handle.RESIZE_MOVE)
elif num == NumResizeHandles.NORESIZE:
handles = [Handle.MOVE]
elif num == NumResizeHandles.NONE:
handles = []
else: # NumResizeHandles.SOME
handles = list(Handle.CORNERS + (Handle.MOVE, ))
return handles
def num_handles_to_icon_palette_handles(self, num):
handles = self.num_handles_to_window_handles(num)
if num == NumResizeHandles.SOME:
handles = [Handle.SOUTH_EAST, Handle.MOVE]
return handles
@staticmethod
def _string_to_handles(string):
""" String of handle ids to array of Handle enums """
ids = string.split()
handles = []
for id in ids:
handle = Handle.RIDS.get(id)
if not handle is None:
handles.append(handle)
return handles
@staticmethod
def _handles_to_string(handles):
""" Array of handle enums to string of handle ids """
ids = []
for handle in handles:
ids.append(Handle.IDS[handle])
return " ".join(ids)
####### Snippets editing #######
def set_snippet(self, index, value):
"""
Set a snippet in the snippet list. Enlarge the list if not big
enough.
@type index: int
@param index: index of the snippet to set.
@type value: str
@param value: Contents of the new snippet.
"""
if value == None:
raise TypeError("Snippet text must be str")
label, text = value
snippets = dict(self.snippets) # copy to enable callbacks
_logger.info("Setting snippet %d to '%s', '%s'" % (index, label, text))
snippets[index] = (label, text)
self.snippets = snippets
def del_snippet(self, index):
"""
Delete a snippet.
@type index: int
@param index: index of the snippet to delete.
"""
_logger.info("Deleting snippet %d" % index)
snippets = dict(self.snippets) # copy to enable callbacks
del snippets[index]
self.snippets = snippets
###### gnome-screensaver, xembedding #####
def enable_gss_embedding(self, enable):
if enable:
self.onboard_xembed_enabled = True
self.gss.embedded_keyboard_enabled = True
self.set_xembed_command_string_to_onboard()
else:
self.onboard_xembed_enabled = False
self.gss.embedded_keyboard_enabled = False
def is_onboard_in_xembed_command_string(self):
"""
Checks whether the gsettings key for the embeded application command
contains the entry defined by onboard.
Returns True if it is set to onboard and False otherwise.
"""
if self.gss.embedded_keyboard_command.startswith(START_ONBOARD_XEMBED_COMMAND):
return True
else:
return False
def set_xembed_command_string_to_onboard(self):
"""
Write command to start the embedded onboard into the corresponding
gsettings key.
"""
self.gss.embedded_keyboard_command = START_ONBOARD_XEMBED_COMMAND
def _get_kbd_render_mixin(self):
__import__(self._kbd_render_mixin_mod)
return getattr(sys.modules[self._kbd_render_mixin_mod],
self._kbd_render_mixin_cls)
kbd_render_mixin = property(_get_kbd_render_mixin)
# modeless gksu - disabled until gksu moves to gsettings
def modeless_gksu_notify_add(self, callback):
pass
modeless_gksu = property(lambda self: False)
def get_desktop_background_filename(self):
fn = ""
# Starting with Vivid's unity greeter try to get the filename
# from the greeter's schema.
if self.launched_by == self.LAUNCHER_UNITY_GREETER:
fn = self._get_desktop_background_filename_from_schema(
"com.canonical.unity-greeter", "background")
# Elsewhere (old Ubuntu releases, gnome-screen-saver) get it
# from the gnome key.
if not fn:
fn = self._get_desktop_background_filename_from_schema(
"org.gnome.desktop.background", "picture-uri")
return fn
def _get_desktop_background_filename_from_schema(self, schema, key):
try:
s = Gio.Settings.new(schema)
fn = s.get_string(key)
except Exception as ex: # private exception gi._glib.GError
fn = ""
_logger.error("failed to read desktop background from {} {}: {}" \
.format(schema, key, unicode_str(ex)))
if fn:
try:
# Valid file URI?
# Prevents error 'not an absolute URI using the "file" scheme'
# when using the unity-greeter schema.
if GLib.uri_parse_scheme(fn) == "file":
try:
fn, error = GLib.filename_from_uri(fn)
except TypeError: # broken introspection on Precise
fn = GLib.filename_from_uri(fn, "")
error = ""
if error:
fn = ""
except Exception as ex: # private exception gi._glib.GError
_logger.error("failed to unescape URI for desktop background "
"'{}': {}" \
.format(fn, unicode_str(ex)))
return fn
def get_xembed_unity_greeter_offset_x(self):
value = self.gskeys["xembed_unity_greeter_offset_x"].value
if value < 0:
value = None
return value
def get_xembed_background_rgba(self):
return self._xembed_background_rgba
def _update_xembed_background_rgba(self):
value = self.xembed_background_color
self._xembed_background_rgba = hexcolor_to_rgba(value)
def _is_running_from_source(self):
return bool(self._get_source_path())
def _get_source_path(self):
if self._source_path is None:
candidates = [
os.path.abspath(os.path.curdir),
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
]
self._source_path = ""
for path in candidates:
data_path = os.path.join(path, "data")
fn = os.path.join(data_path, "org.onboard.gschema.xml")
if os.path.isfile(fn):
self._source_path = path
break
return self._source_path
def _get_install_dir(self):
result = None
# when run from source
if self._is_running_from_source():
# Add the data directory to the icon search path
icon_theme = Gtk.IconTheme.get_default()
src_path = self._get_source_path()
src_icon_path = os.path.join(src_path, "icons")
icon_theme.append_search_path(src_icon_path)
result = src_path
# when installed to /usr/local
elif os.path.isdir(LOCAL_INSTALL_DIR):
result = LOCAL_INSTALL_DIR
# when installed to /usr
elif os.path.isdir(INSTALL_DIR):
result = INSTALL_DIR
assert(result) # warn early when the installation dir wasn't found
return result
def _get_user_dir(self):
return XDGDirs.get_data_home(USER_DIR)
def get_user_layout_dir(self):
return os.path.join(self.user_dir, "layouts/")
def get_system_default_lang_id(self):
lang_id = locale.getdefaultlocale()[0]
if not lang_id: # None e.g. with LANG=C
lang_id = "en_US"
return lang_id
def get_desktop_environment(self):
"""
Return current desktop environment.
"""
if not self._desktop_environment:
self._desktop_environment = self._detect_desktop_environment()
return self._desktop_environment
@staticmethod
def _detect_desktop_environment():
"""
Detect current desktop environment. Extend as needed.
"""
xdg_desktop = os.environ.get("XDG_CURRENT_DESKTOP", "")
desktop = os.environ.get("DESKTOP_SESSION", "")
def istrcmp(s1, s2):
if sys.version_info < (3,3):
return s1.lower() == s2.lower()
return s1.casefold() == s2.casefold()
def isdesktop(id):
return istrcmp(xdg_desktop, id) or istrcmp(desktop, id)
if isdesktop("X-Cinnamon") or isdesktop("cinnamon"):
return DesktopEnvironmentEnum.Cinnamon
if isdesktop("GNOME"):
return DesktopEnvironmentEnum.GNOME_Shell
if isdesktop("GNOME-Classic:GNOME"):
return DesktopEnvironmentEnum.GNOME_Classic
if isdesktop("KDE"):
return DesktopEnvironmentEnum.KDE
if isdesktop("lxde"):
return DesktopEnvironmentEnum.LXDE
if isdesktop("LXQt"):
return DesktopEnvironmentEnum.LXQT
if isdesktop("mate"):
return DesktopEnvironmentEnum.MATE
if isdesktop("Unity"):
return DesktopEnvironmentEnum.Unity
if isdesktop("xfce"):
return DesktopEnvironmentEnum.XFCE
return DesktopEnvironmentEnum.Unknown
def get_preferred_statusicon_provider(self):
"""
Auto-detect if we should fall back to GtkStatusIcon.
"""
result = StatusIconProviderEnum.AppIndicator
de = self.get_desktop_environment()
# Gnome-shell annoys with sliding in their legacy icon panel.
# We have our indicator extension now, so turn the status icon off.
if de in (DesktopEnvironmentEnum.GNOME_Shell, ):
result = None
elif de in (
# AppIndicator is supported in XUbuntu 16.04, but w/o left click
# activation. GtkStatusIcon works well and allows left click.
DesktopEnvironmentEnum.Cinnamon,
# AppIndicator does nothing in 16.04.
DesktopEnvironmentEnum.MATE,
# AppIndicator does nothing in 16.04.
DesktopEnvironmentEnum.XFCE,
# AppIndicator is supported in Lubuntu 16.04, but w/o left
# click activation. GtkStatusIcon works well, but is placed
# into a legacy system tray applet with tiny 16x16 icon.
# DesktopEnvironmentEnum.LXDE,
# AppIndicator is supported in LXQt 16.04, including left
# click activation. GtkStatusIcon works well too.
# DesktopEnvironmentEnum.LXQT,
):
result = StatusIconProviderEnum.GtkStatusIcon
return result
class ConfigKeyboard(ConfigObject):
"""Window configuration """
DEFAULT_KEY_ACTION = 1 # Release-only, supports long press
DEFAULT_KEY_SYNTH = 0 # Auto
DEFAULT_TOUCH_INPUT = TouchInputEnum.MULTI
DEFAULT_INPUT_EVENT_SOURCE = InputEventSourceEnum.XINPUT
def _init_keys(self):
self.schema = SCHEMA_KEYBOARD
self.sysdef_section = "keyboard"
self.add_key("show-click-buttons", False)
self.add_key("sticky-key-release-delay", 0.0)
self.add_key("sticky-key-release-on-hide-delay", 5.0)
self.add_key("sticky-key-behavior", {"all" : "cycle"}, 'a{ss}')
self.add_key("long-press-delay", 0.5)
self.add_key("default-key-action", self.DEFAULT_KEY_ACTION,
enum={"single-stroke" : 0,
"delayed-stroke" : 1,
})
self.add_key("key-synth", self.DEFAULT_KEY_SYNTH,
enum={"auto" : 0,
"XTest" : 1,
"uinput" : 2,
"AT-SPI" : 3,
})
self.add_key("touch-input", self.DEFAULT_TOUCH_INPUT,
enum={"none" : 0,
"single" : 1,
"multi" : 2,
})
self.add_key("input-event-source", self.DEFAULT_INPUT_EVENT_SOURCE,
enum={"GTK" : 0,
"XInput" : 1,
})
self.add_key("touch-feedback-enabled", False)
self.add_key("touch-feedback-size", 0)
self.add_key("audio-feedback-enabled", False)
self.add_key("audio-feedback-place-in-space", False)
self.add_key("show-secondary-labels", False)
self.add_key("inter-key-stroke-delay", 0.0)
self.add_key("modifier-update-delay", 1.0)
self.add_key("key-press-modifiers", {"button3" : "SHIFT"}, 'a{ss}')
def can_upper_case_on_button(self, button):
kpms = self.key_press_modifiers
if not kpms: # speed up the empty case
return False
key = "button" + str(button)
return kpms.get(key) == "SHIFT"
def set_upper_case_on_button(self, button, on):
key = "button" + str(button)
values = dict(self.key_press_modifiers) # must be a copy
if on:
values[key] = "SHIFT"
else:
if key in values:
del values[key]
self.key_press_modifiers = values
class ConfigWindow(ConfigObject):
"""Window configuration """
DEFAULT_DOCKING_EDGE = DockingEdge.BOTTOM
DEFAULT_DOCKING_MONITOR = DockingMonitor.ACTIVE
def _init_keys(self):
self.schema = SCHEMA_WINDOW
self.sysdef_section = "window"
self.add_key("window-state-sticky", True)
self.add_key("window-decoration", False)
self.add_key("force-to-top", False)
self.add_key("keep-aspect-ratio", False)
self.add_key("transparent-background", False)
self.add_key("transparency", 0.0)
self.add_key("background-transparency", 10.0)
self.add_key("enable-inactive-transparency", False)
self.add_key("inactive-transparency", 50.0)
self.add_key("inactive-transparency-delay", 1.0)
self.add_key("window-handles", DEFAULT_WINDOW_HANDLES)
self.add_key("docking-enabled", False)
self.add_key("docking-edge", self.DEFAULT_DOCKING_EDGE,
enum={"top" : DockingEdge.TOP,
"bottom" : DockingEdge.BOTTOM,
})
self.add_key("docking-monitor", self.DEFAULT_DOCKING_MONITOR,
enum={"active" : DockingMonitor.ACTIVE,
"primary" : DockingMonitor.PRIMARY,
"monitor0" : DockingMonitor.MONITOR0,
"monitor1" : DockingMonitor.MONITOR1,
"monitor2" : DockingMonitor.MONITOR2,
"monitor3" : DockingMonitor.MONITOR3,
"monitor4" : DockingMonitor.MONITOR4,
"monitor5" : DockingMonitor.MONITOR5,
"monitor6" : DockingMonitor.MONITOR6,
"monitor7" : DockingMonitor.MONITOR7,
"monitor8" : DockingMonitor.MONITOR8,
})
self.add_key("docking-shrink-workarea", True)
self.add_key("docking-aspect-change-range", [0, 1.6], "ad")
self.landscape = ConfigWindow.Landscape(self)
self.portrait = ConfigWindow.Portrait(self)
self.children = [self.landscape, self.portrait]
##### property helpers #####
def _convert_sysdef_key(self, gskey, sysdef, value):
if sysdef == "resize-handles" or \
sysdef == "window-handles":
return Config._string_to_handles(value)
else:
return ConfigObject._convert_sysdef_key(self, gskey, sysdef, value)
def _unpack_window_handles(self, value):
return Config._string_to_handles(value)
def _pack_window_handles(self, value):
return Config._handles_to_string(value)
def position_notify_add(self, callback):
self.landscape.x_notify_add(callback)
self.landscape.y_notify_add(callback)
self.portrait.x_notify_add(callback)
self.portrait.y_notify_add(callback)
def size_notify_add(self, callback):
self.landscape.width_notify_add(callback)
self.landscape.height_notify_add(callback)
self.portrait.width_notify_add(callback)
self.portrait.height_notify_add(callback)
def dock_size_notify_add(self, callback):
self.landscape.dock_width_notify_add(callback)
self.landscape.dock_height_notify_add(callback)
self.portrait.dock_width_notify_add(callback)
self.portrait.dock_height_notify_add(callback)
def docking_notify_add(self, callback):
self.docking_enabled_notify_add(callback)
self.docking_edge_notify_add(callback)
self.docking_monitor_notify_add(callback)
self.docking_shrink_workarea_notify_add(callback)
self.landscape.dock_expand_notify_add(callback)
self.portrait.dock_expand_notify_add(callback)
def get_active_opacity(self):
return 1.0 - self.transparency / 100.0
def get_inactive_opacity(self):
return 1.0 - self.inactive_transparency / 100.0
def get_minimal_opacity(self):
# Return the lowest opacity the window can have when visible.
return min(self.get_active_opacity(), self.get_inactive_opacity())
def get_background_opacity(self):
return 1.0 - self.background_transparency / 100.0
class Landscape(ConfigObject):
def _init_keys(self):
self.schema = SCHEMA_WINDOW_LANDSCAPE
self.sysdef_section = "window.landscape"
self.add_key("x", DEFAULT_X)
self.add_key("y", DEFAULT_Y)
self.add_key("width", DEFAULT_WIDTH)
self.add_key("height", DEFAULT_HEIGHT)
self.add_key("dock-width", DEFAULT_WIDTH)
self.add_key("dock-height", DEFAULT_HEIGHT)
self.add_key("dock-expand", True)
class Portrait(ConfigObject):
def _init_keys(self):
self.schema = SCHEMA_WINDOW_PORTRAIT
self.sysdef_section = "window.portrait"
self.add_key("x", DEFAULT_X)
self.add_key("y", DEFAULT_Y)
self.add_key("width", DEFAULT_WIDTH)
self.add_key("height", DEFAULT_HEIGHT)
self.add_key("dock-width", DEFAULT_WIDTH)
self.add_key("dock-height", DEFAULT_HEIGHT)
self.add_key("dock-expand", True)
class ConfigICP(ConfigObject):
""" Icon palette configuration """
def _init_keys(self):
self.schema = SCHEMA_ICP
self.sysdef_section = "icon-palette"
self.add_key("in-use", False)
self.add_key("window-handles", DEFAULT_WINDOW_HANDLES)
self.landscape = ConfigICP.Landscape(self)
self.portrait = ConfigICP.Portrait(self)
self.children = [self.landscape, self.portrait]
##### property helpers #####
def _convert_sysdef_key(self, gskey, sysdef, value):
if sysdef == "resize-handles" or \
sysdef == "window-handles":
return Config._string_to_handles(value)
else:
return ConfigObject._convert_sysdef_key(self, gskey, sysdef, value)
def _unpack_window_handles(self, value):
return Config._string_to_handles(value)
def _pack_window_handles(self, value):
return Config._handles_to_string(value)
def position_notify_add(self, callback):
self.landscape.x_notify_add(callback)
self.landscape.y_notify_add(callback)
self.portrait.x_notify_add(callback)
self.portrait.y_notify_add(callback)
def size_notify_add(self, callback):
self.landscape.width_notify_add(callback)
self.landscape.height_notify_add(callback)
self.portrait.width_notify_add(callback)
self.portrait.height_notify_add(callback)
class Landscape(ConfigObject):
def _init_keys(self):
self.schema = SCHEMA_ICP_LANDSCAPE
self.sysdef_section = "icon-palette.landscape"
self.add_key("x", DEFAULT_ICP_X)
self.add_key("y", DEFAULT_ICP_Y)
self.add_key("width", DEFAULT_ICP_WIDTH)
self.add_key("height", DEFAULT_ICP_HEIGHT)
class Portrait(ConfigObject):
def _init_keys(self):
self.schema = SCHEMA_ICP_PORTRAIT
self.sysdef_section = "icon-palette.portrait"
self.add_key("x", DEFAULT_ICP_X)
self.add_key("y", DEFAULT_ICP_Y)
self.add_key("width", DEFAULT_ICP_WIDTH)
self.add_key("height", DEFAULT_ICP_HEIGHT)
class ConfigAutoShow(ConfigObject):
""" auto_show configuration """
DEFAULT_REPOSITION_METHOD = RepositionMethodEnum.PREVENT_OCCLUSION
def _init_keys(self):
self.schema = SCHEMA_AUTO_SHOW
self.sysdef_section = "auto-show"
self.add_key("enabled", False)
enum = {
"none" : 0,
"prevent-occlusion" : RepositionMethodEnum.PREVENT_OCCLUSION,
"reduce-travel" : RepositionMethodEnum.REDUCE_POINTER_TRAVEL,
}
self.add_key("reposition-method-floating", self.DEFAULT_REPOSITION_METHOD,
enum=enum)
self.add_key("reposition-method-docked", self.DEFAULT_REPOSITION_METHOD,
enum=enum)
self.add_key("widget-clearance", (25.0, 55.0, 25.0, 40.0), '(dddd)')
self.add_key("hide-on-key-press", True)
self.add_key("hide-on-key-press-pause", 1800.0)
self.add_key("tablet-mode-detection-enabled", True)
self.add_key("tablet-mode-enter-key", 0)
self.add_key("tablet-mode-leave-key", 0)
self.add_key("tablet-mode-state-file", "")
self.add_key("tablet-mode-state-file-pattern", "1")
self.add_key("keyboard-device-detection-enabled", False)
self.add_key("keyboard-device-detection-exceptions", [])
def tablet_mode_detection_notify_add(self, callback):
self.tablet_mode_detection_enabled_notify_add(callback)
self.tablet_mode_enter_key_notify_add(callback)
self.tablet_mode_leave_key_notify_add(callback)
class ConfigUniversalAccess(ConfigObject):
""" universal_access configuration """
def _init_keys(self):
self.schema = SCHEMA_UNIVERSAL_ACCESS
self.sysdef_section = "universal-access"
self.add_key("drag-threshold", -1)
self.add_key("hide-click-type-window", True)
self.add_key("enable-click-type-window-on-exit", True)
def _post_notify_hide_click_type_window(self):
""" called when changed in gsettings (preferences window) """
mousetweaks = self.parent.mousetweaks
if mousetweaks:
mousetweaks.on_hide_click_type_window_changed(
self.hide_click_type_window)
class ConfigTheme(ConfigObject):
""" Theme configuration """
def _init_keys(self):
self.schema = SCHEMA_THEME
self.sysdef_section = "theme-settings"
self.add_key("color-scheme", DEFAULT_COLOR_SCHEME,
prop="color_scheme_filename")
self.add_key("background-gradient", 0.0)
self.add_key("key-style", "flat")
self.add_key("roundrect-radius", 0.0)
self.add_key("key-size", 100.0)
self.add_key("key-stroke-width", 100.0)
self.add_key("key-fill-gradient", 0.0)
self.add_key("key-stroke-gradient", 0.0)
self.add_key("key-gradient-direction", 0.0)
self.key_label_font_key = \
self.add_key("key-label-font", "") # font for current theme
self.key_label_overrides_key = \
self.add_key("key-label-overrides", {}, "as") # labels for current theme
self.add_key("key-shadow-strength", 20.0)
self.add_key("key-shadow-size", 5.0)
##### property helpers #####
def theme_attributes_notify_add(self, callback):
self.background_gradient_notify_add(callback)
self.key_style_notify_add(callback)
self.roundrect_radius_notify_add(callback)
self.key_size_notify_add(callback)
self.key_stroke_width_notify_add(callback)
self.key_fill_gradient_notify_add(callback)
self.key_stroke_gradient_notify_add(callback)
self.key_gradient_direction_notify_add(callback)
self.key_label_font_notify_add(callback)
self.key_label_overrides_notify_add(callback)
self.key_style_notify_add(callback)
self.key_shadow_strength_notify_add(callback)
self.key_shadow_size_notify_add(callback)
def _can_set_color_scheme_filename(self, filename):
if not os.path.exists(filename):
_logger.warning(_format("color scheme '{filename}' does not exist", \
filename=filename))
return False
return True
def _unpack_key_label_overrides(self, value):
return self.unpack_string_list(value, "a{s[ss]}")
def _pack_key_label_overrides(self, value):
return self.pack_string_list(value)
def get_key_label_overrides(self):
gskey = self.key_label_overrides_key
# merge with default value from onboard base config
value = dict(self.parent.key_label_overrides)
value.update(gskey.value)
return value
_font_attributes = ("bold", "italic", "condensed")
_cached_key_label_font = None
def _post_notify_key_label_font(self):
self._cached_key_label_font = None
def get_key_label_font(self):
if self._cached_key_label_font is None:
gskey = self.key_label_font_key
value = gskey.value
if value:
items = value.split()
if items:
# If no font family was provided merge in system font family
if items[0] in self._font_attributes:
system_items = self.parent.key_label_font.split()
if system_items and \
system_items[0] not in self._font_attributes:
items.insert(0, system_items[0])
value = " ".join(items)
else:
# get default value from onboard base config instead
value = self.parent.key_label_font
self._cached_key_label_font = value
return self._cached_key_label_font
class ConfigLockdown(ConfigObject):
""" Lockdown/Kiosk mode configuration """
def _init_keys(self):
self.schema = SCHEMA_LOCKDOWN
self.sysdef_section = "lockdown"
self.add_key("disable-click-buttons", False)
self.add_key("disable-hover-click", False)
self.add_key("disable-dwell-activation", False)
self.add_key("disable-preferences", False)
self.add_key("disable-quit", False)
self.add_key("disable-touch-handles", False)
self.add_key("disable-keys", [["CTRL", "LALT", "F[0-9]+"]], 'aas')
def lockdown_notify_add(self, callback):
self.disable_click_buttons_notify_add(callback)
self.disable_hover_click_notify_add(callback)
self.disable_preferences_notify_add(callback)
self.disable_quit_notify_add(callback)
class ConfigGSS(ConfigObject):
""" gnome-screen-saver configuration keys"""
def _init_keys(self):
self.schema = SCHEMA_GSS
self.sysdef_section = "gnome-screen-saver"
self.add_key("embedded-keyboard-enabled", True)
self.add_key("embedded-keyboard-command", "")
class ConfigGDI(ConfigObject):
""" Key to enable Gnome Accessibility"""
def _init_keys(self):
self.schema = SCHEMA_GDI
self.sysdef_section = "gnome-desktop-interface"
self.add_key("toolkit-accessibility", False)
self.add_key("gtk-theme", "", writable=False) # read-only for safety
class ConfigGDA(ConfigObject):
""" Key to check if a11y keyboard is enabled """
def _init_keys(self):
self.schema = SCHEMA_GDA
self.sysdef_section = "gnome-desktop-a11y-applications"
# read-only for safety
self.add_key("screen-keyboard-enabled", False, writable=False)
class ConfigUnityGreeter(ConfigObject):
""" Key to hide onboard when embedded into unity-greeter """
def _init_keys(self):
self.schema = SCHEMA_UNITY_GREETER
self.sysdef_section = "unity-greeter"
self.add_key("onscreen-keyboard", False)
class ConfigScanner(ConfigObject):
""" Scanner configuration """
DEFAULT_INTERVAL = 1.20
DEFAULT_INTERVAL_FAST = 0.05
DEFAULT_MODE = 0 # AutoScan
DEFAULT_CYCLES = 2
DEFAULT_BACKTRACK = 5
DEFAULT_ALTERNATE = False
DEFAULT_USER_SCAN = False
DEFAULT_DEVICE_NAME = "Default"
DEFAULT_DEVICE_KEY_MAP = {}
DEFAULT_DEVICE_BUTTON_MAP = { 1: 0, 3: 5 } # Button 1: Step, Button 3: Activate
DEFAULT_FEEDBACK_FLASH = True
def _init_keys(self):
self.schema = SCHEMA_SCANNER
self.sysdef_section = "scanner"
self.add_key("enabled", False)
self.add_key("mode", self.DEFAULT_MODE, enum={"Autoscan" : 0,
"Overscan" : 1,
"Stepscan" : 2,
"Directed" : 3})
self.add_key("interval", self.DEFAULT_INTERVAL)
self.add_key("interval-fast", self.DEFAULT_INTERVAL_FAST)
self.add_key("cycles", self.DEFAULT_CYCLES)
self.add_key("backtrack", self.DEFAULT_BACKTRACK)
self.add_key("alternate", self.DEFAULT_ALTERNATE)
self.add_key("user-scan", self.DEFAULT_USER_SCAN)
self.add_key("device-name", self.DEFAULT_DEVICE_NAME)
self.add_key("device-detach", False)
self.add_key("device-key-map", self.DEFAULT_DEVICE_KEY_MAP, 'a{ii}')
self.add_key("device-button-map", self.DEFAULT_DEVICE_BUTTON_MAP, 'a{ii}')
self.add_key("feedback-flash", self.DEFAULT_FEEDBACK_FLASH)
class ConfigTypingAssistance(ConfigObject):
""" typing-assistance configuration keys"""
DEFAULT_BACKEND = 0
def _init_keys(self):
self.schema = SCHEMA_TYPING_ASSISTANCE
self.sysdef_section = "typing-assistance"
self.add_key("active-language", "")
self.add_key("recent-languages", [], 'as')
self.add_key("max-recent-languages", 5)
self.add_key("spell-check-backend", self.DEFAULT_BACKEND,
enum={"hunspell" : 0,
"aspell" : 1})
self.add_key("auto-capitalization", False)
self.add_key("auto-correction", False)
self.word_suggestions = ConfigWordSuggestions(self)
self.children = [self.word_suggestions]
# shortcuts in the root for convenient access
self.get_root().wp = self.word_suggestions
self.get_root().word_suggestions = self.word_suggestions
class ConfigWordSuggestions(ConfigObject):
""" word-suggestions configuration keys"""
# wordlist_buttons
KEY_ID_PREVIOUS_PREDICTIONS = "previous-predictions"
KEY_ID_NEXT_PREDICTIONS = "next-predictions"
KEY_ID_LANGUAGE = "language"
KEY_ID_PAUSE_LEARNING = "pause-learning"
KEY_ID_HIDE = "hide"
KEY_ORDER = [KEY_ID_PREVIOUS_PREDICTIONS,
KEY_ID_NEXT_PREDICTIONS,
KEY_ID_PAUSE_LEARNING,
KEY_ID_LANGUAGE,
KEY_ID_HIDE]
def _init_keys(self):
self.schema = SCHEMA_WORD_SUGGESTIONS
self.sysdef_section = "word-suggestions"
self.add_key("enabled", False)
self.add_key("auto-learn", True)
self.add_key("punctuation-assistance", True)
self.add_key("delayed-word-separators-enabled", False)
self.add_key("accent-insensitive", True)
self.add_key("max-word-choices", 5)
self.add_key("spelling-suggestions-enabled", True)
self.add_key("wordlist-buttons",
[self.KEY_ID_PREVIOUS_PREDICTIONS,
self.KEY_ID_NEXT_PREDICTIONS,
self.KEY_ID_LANGUAGE,
self.KEY_ID_HIDE])
self.add_key("pause-learning-locked", False)
self.add_key("learning-behavior-paused", LearningBehavior.NOTHING,
enum={"nothing" : LearningBehavior.NOTHING,
"known-only" : LearningBehavior.KNOWN_ONLY})
# 0=off, 1=latched, 2=locked; not in gsettings
self._pause_learning = 0
# deprecated
self.add_key("stealth-mode", False)
self.add_key("show-context-line", False)
def word_prediction_notify_add(self, callback):
self.auto_learn_notify_add(callback)
self.punctuation_assistance_notify_add(callback)
self.stealth_mode_notify_add(callback)
def can_auto_learn(self):
return self.enabled and \
self.auto_learn and \
(not self.is_learning_paused() or \
self.learning_behavior_paused != \
LearningBehavior.NOTHING) and \
not self.stealth_mode
def is_learning_paused(self):
return self.get_pause_learning() > 0
def get_pause_learning(self):
if self.pause_learning_locked:
return 2
else:
return self._pause_learning
def set_pause_learning(self, value):
self._pause_learning = value
self.pause_learning_locked = value == 2
def _post_notify_pause_learning_locked(self):
self._pause_learning = 2 if self.pause_learning_locked else 0
def get_shown_wordlist_button_ids(self):
result = []
for button_id in self.wordlist_buttons:
if button_id != self.KEY_ID_PAUSE_LEARNING or \
self.can_show_pause_learning_button():
result.append(button_id)
return result
def show_wordlist_button(self, key_id, show):
"""
Doctests:
>>> co = ConfigWordSuggestions()
>>> ConfigWordSuggestions.wordlist_buttons = []
>>> co.wordlist_buttons = []
>>> co.show_wordlist_button(co.KEY_ID_HIDE, True)
>>> print(co.wordlist_buttons)
['hide']
>>> co.wordlist_buttons = []
>>> co.show_wordlist_button(co.KEY_ID_LANGUAGE, True)
>>> print(co.wordlist_buttons)
['language']
>>> co.wordlist_buttons = []
>>> co.show_wordlist_button("notabutton", True)
>>> print(co.wordlist_buttons)
[]
>>> co.wordlist_buttons = [co.KEY_ID_PAUSE_LEARNING, co.KEY_ID_HIDE]
>>> co.show_wordlist_button(co.KEY_ID_LANGUAGE, True)
>>> print(co.wordlist_buttons)
['pause-learning', 'language', 'hide']
>>> co.wordlist_buttons = [co.KEY_ID_LANGUAGE, co.KEY_ID_HIDE]
>>> co.show_wordlist_button(co.KEY_ID_PAUSE_LEARNING, True)
>>> print(co.wordlist_buttons)
['pause-learning', 'language', 'hide']
"""
buttons = self.wordlist_buttons[:]
if show:
if key_id not in buttons:
priority = self._get_button_priority(key_id)
if priority >= 0:
for i, button in enumerate(buttons):
p = self._get_button_priority(button)
if priority < p:
buttons.insert(i, key_id)
break
else:
buttons.append(key_id)
self.wordlist_buttons = buttons
else:
if key_id in buttons:
buttons.remove(key_id)
self.wordlist_buttons = buttons
@staticmethod
def _get_button_priority(key_id):
try:
return ConfigWordSuggestions.KEY_ORDER.index(key_id)
except ValueError:
return -1
def can_show_language_button(self):
return self.KEY_ID_LANGUAGE in self.wordlist_buttons
def can_show_pause_learning_button(self):
return self.auto_learn and \
self.KEY_ID_PAUSE_LEARNING in self.wordlist_buttons
def can_show_more_predictions_button(self):
return self.KEY_ID_NEXT_PREDICTIONS in self.wordlist_buttons
def can_learn_new_words(self):
return not self.is_learning_paused()