1
# -*- coding: utf-8 -*-
2
# Copyright (c) 2010, 2011, Sebastian Wiesner <lunaryorn@googlemail.com>
5
# Redistribution and use in source and binary forms, with or without
6
# modification, are permitted provided that the following conditions are met:
8
# 1. Redistributions of source code must retain the above copyright notice,
9
# this list of conditions and the following disclaimer.
10
# 2. Redistributions in binary form must reproduce the above copyright
11
# notice, this list of conditions and the following disclaimer in the
12
# documentation and/or other materials provided with the distribution.
14
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24
# POSSIBILITY OF SUCH DAMAGE.
34
This module provides the configuration classes for the touchpad and the
35
touchpad manager. These configuration classes are simply mappings, which
36
expose the *current* state of the associated objects (and *not* the state
37
of the configuration on disk). This allows the user to configure the live
38
state of these objects, which is especially important for touchpad
39
configuration, which can be changed from outside of **synaptiks** using
40
utilities like :program:`xinput`.
42
To save the configuration permanently, these mappings, and consequently the
43
current configuration state of the associated objects, is simply dumped in
44
JSON format to the configuration directory (see
45
:func:`get_configuration_directory()`).
47
To apply a dumped configuration, it is loaded as standard dict from the
48
JSON dump, and then the corresponding configuration mapping is updated with
49
this dict. All configuration mappings provide a convenient
50
:meth:`~TouchpadConfiguration.load()` method to create a new configuration
51
mapping updated with the dumped configuration.
53
Handling of default values
54
--------------------------
56
The touchpad manager configuration (see :class:`ManagerConfiguration`)
57
provides explicit defaults. The touchpad configuration (see
58
:class:`TouchpadConfiguration`) however uses defaults provided by the
61
As the touchpad driver doesn't expose special access to the default values,
62
**synaptiks** simply creates a :class:`TouchpadConfiguration` after session
63
startup, but *before* loading the actual configuration from disk. At this
64
point, the touchpad still uses the driver default setting, which are now
65
dumped to a special file in JSON format (see
66
:func:`get_touchpad_defaults_file_path()`). They can later be loaded using
67
:func:`get_touchpad_defaults()`.
72
.. program:: synaptikscfg
74
This module is usable as script, available also as :program:`synaptikscfg`
75
in the ``$PATH``. It provides three different, of which ``load`` and
76
``save`` are really self-explanatory. ``init`` however deserves some
77
detailled explanation.
79
The `init` action is supposed to run automatically as script during session
80
startup. To do this, the installation script installs an autostart entry
81
to execute ``synaptikscfg init`` at KDE startup. This action first dumps
82
the default settings from the touchpad driver as described above, and then
83
loads and applies the actual touchpad configuration stored on disk.
85
The command line parsing of the script is implemented with :mod:`argparse`,
86
so you can expected standard semantics, and an extensive ``--help`` option.
88
.. moduleauthor:: Sebastian Wiesner <lunaryorn@googlemail.com>
91
from __future__ import (print_function, division, unicode_literals,
95
from collections import MutableMapping
97
from synaptiks.util import ensure_directory, save_json, load_json_with_default
100
PACKAGE_DIRECTORY = os.path.dirname(__file__)
103
def get_configuration_directory():
105
Get the configuration directory of synaptiks according to the `XDG base
106
directory specification`_ as string. The directory is guaranteed to exist.
108
The configuration directory is a sub-directory of ``$XDG_CONFIG_HOME``
109
(which defaults to ``$HOME/.config``).
111
.. _`XDG base directory specification`: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
113
xdg_config_home = os.environ.get('XDG_CONFIG_HOME', os.path.expandvars(
114
os.path.join('$HOME', '.config')))
115
return ensure_directory(os.path.join(xdg_config_home, 'synaptiks'))
118
def get_touchpad_config_file_path():
120
Get the path to the file which stores the touchpad configuration.
122
return os.path.join(get_configuration_directory(), 'touchpad-config.json')
125
def get_touchpad_defaults_file_path():
127
Get the path to the file which stores the default touchpad configuration as
128
setup by the touchpad driver.
130
return os.path.join(get_configuration_directory(), 'touchpad-defaults.json')
133
def get_management_config_file_path():
135
Get the path to the file which stores the touchpad management configuration.
137
return os.path.join(get_configuration_directory(), 'management.json')
140
def get_touchpad_defaults(filename=None):
142
Get the default touchpad settings as :func:`dict` *without* applying it to
146
filename = get_touchpad_defaults_file_path()
147
return load_json_with_default(filename, {})
150
class TouchpadConfiguration(MutableMapping):
152
A mutable mapping class representing the current configuration of the
156
CONFIG_KEYS = frozenset([
157
'minimum_speed', 'maximum_speed', 'acceleration_factor',
158
'edge_motion_always', 'fast_taps',
159
'rt_tap_action', 'rb_tap_action', 'lt_tap_action', 'lb_tap_action',
160
'f1_tap_action', 'f2_tap_action', 'f3_tap_action',
161
'tap_and_drag_gesture', 'locked_drags', 'locked_drags_timeout',
162
'vertical_edge_scrolling', 'horizontal_edge_scrolling',
163
'corner_coasting', 'coasting_speed',
164
'vertical_scrolling_distance', 'horizontal_scrolling_distance',
165
'vertical_two_finger_scrolling', 'horizontal_two_finger_scrolling',
166
'circular_scrolling', 'circular_scrolling_trigger',
167
'circular_scrolling_distance', 'circular_touchpad'])
170
def load(cls, touchpad, filename=None):
172
Load the configuration for the given ``touchpad`` from disc.
174
If no ``filename`` is given, the configuration is loaded from the
175
default configuration file as returned by
176
:func:`get_touchpad_config_file_path`. Otherwise the configuration is
177
loaded from the given file. If the file doesn't exist, an empty
178
configuration is loaded.
180
After the configuration is loaded, it is applied to the given
183
``touchpad`` is a :class:`~synaptiks.touchpad.Touchpad` object.
184
``filename`` is either ``None`` or a string containing the path to a
187
Return a :class:`TouchpadConfiguration` object. Raise
188
:exc:`~exceptions.EnvironmentError`, if the file could not be loaded,
189
but *not* in case of a non-existing file.
192
filename = get_touchpad_config_file_path()
193
config = cls(touchpad)
194
config.update(load_json_with_default(filename, {}))
197
def __init__(self, touchpad):
199
Create a new configuration from the given ``touchpad``.
201
``touchpad`` is a :class:`~synaptiks.touchpad.Touchpad` object.
203
self.touchpad = touchpad
205
def __contains__(self, key):
206
return key in self.CONFIG_KEYS
209
return len(self.CONFIG_KEYS)
212
return iter(self.CONFIG_KEYS)
214
def __getitem__(self, key):
217
value = getattr(self.touchpad, key)
218
if isinstance(value, float):
219
# round floats for the sake of comparability and readability
220
value = round(value, 5)
223
def __setitem__(self, key, value):
226
setattr(self.touchpad, key, value)
228
def __delitem__(self, key):
229
raise NotImplementedError
231
def save(self, filename=None):
233
Save the configuration.
235
If no ``filename`` is given, the configuration is saved to the default
236
configuration file as returned by
237
:func:`get_touchpad_config_file_path`. Otherwise the configuration is
238
saved to the given file.
240
``filename`` is either ``None`` or a string containing the path to a
243
Raise :exc:`~exceptions.EnvironmentError`, if the file could not be
247
filename = get_touchpad_config_file_path()
248
save_json(filename, dict(self))
251
class ManagerConfiguration(MutableMapping):
253
A mutable mapping class representing the configuration of a
254
:class:`~synaptiks.management.TouchpadManager`.
257
#: A mapping with the default values for all configuration keys
258
DEFAULTS = {'monitor_mouses': False, 'ignored_mouses': [],
259
'monitor_keyboard': False, 'idle_time': 2.0,
262
#: config keys to be applied to the mouse_manager
263
MOUSE_MANAGER_KEYS = frozenset(['ignored_mouses'])
264
#: config keys to be applied to the keyboard monitor
265
KEYBOARD_MONITOR_KEYS = frozenset(['idle_time', 'keys_to_ignore'])
268
def load(cls, touchpad_manager, filename=None):
270
Load the configuration for the given ``touchpad_manager`` from disc.
272
If no ``filename`` is given, the configuration is loaded from the
273
default configuration file as returned by
274
:func:`get_management_config_file_path`. Otherwise the configuration
275
is loaded from the given file. If the file doesn't exist, the default
276
config as given by :attr:`DEFAULTS` is loaded.
278
After the configuration is loaded, it is applied to the given
279
``touchpad_manager``.
281
``touchpad_manager`` is a
282
:class:`~synaptiks.management.TouchpadManager` object. ``filename`` is
283
either ``None`` or a string containing the path to a file.
285
Return a :class:`ManagerConfiguration` object. Raise
286
:exc:`~exceptions.EnvironmentError`, if the file could not be loaded,
287
but *not* in case of a non-existing file.
290
filename = get_management_config_file_path()
291
config = cls(touchpad_manager)
292
# use defaults for all non-existing settings
293
loaded_config = dict(cls.DEFAULTS)
294
loaded_config.update(load_json_with_default(filename, {}))
295
config.update(loaded_config)
298
def __init__(self, touchpad_manager):
299
self.touchpad_manager = touchpad_manager
302
def mouse_manager(self):
303
return self.touchpad_manager.mouse_manager
306
def keyboard_monitor(self):
307
return self.touchpad_manager.keyboard_monitor
309
def __contains__(self, key):
310
return key in self.DEFAULTS
313
return len(self.DEFAULTS)
316
return iter(self.DEFAULTS)
318
def __getitem__(self, key):
321
target = self.touchpad_manager
322
if key in self.MOUSE_MANAGER_KEYS:
323
target = self.mouse_manager
324
elif key in self.KEYBOARD_MONITOR_KEYS:
325
target = self.keyboard_monitor
326
return getattr(target, key)
328
def __setitem__(self, key, value):
331
target = self.touchpad_manager
332
if key in self.MOUSE_MANAGER_KEYS:
333
target = self.mouse_manager
334
elif key in self.KEYBOARD_MONITOR_KEYS:
335
target = self.keyboard_monitor
336
setattr(target, key, value)
338
def __delitem__(self, key):
339
raise NotImplementedError
341
def update(self, other):
342
ignored_mouses = other.pop('ignored_mouses')
343
if other.pop('monitor_mouses'):
344
self.mouse_manager.ignored_mouses = ignored_mouses
345
self.touchpad_manager.monitor_mouses = True
347
self.touchpad_manager.monitor_mouses = False
348
self.mouse_manager.ignored_mouses = ignored_mouses
349
super(ManagerConfiguration, self).update(other)
351
def save(self, filename=None):
353
Save the configuration.
355
If no ``filename`` is given, the configuration is saved to the default
356
configuration file as returned by
357
:func:`get_management_config_file_path`. Otherwise the configuration
358
is saved to the given file.
360
``filename`` is either ``None`` or a string containing the path to a
363
Raise :exc:`~exceptions.EnvironmentError`, if the file could not be
367
filename = get_management_config_file_path()
368
save_json(filename, dict(self))
372
from argparse import ArgumentParser
374
from synaptiks import __version__
375
from synaptiks.touchpad import Touchpad
376
from synaptiks._bindings import xlib
378
parser = ArgumentParser(
379
description='synaptiks touchpad configuration utility',
381
Copyright (C) 2010 Sebastian Wiesner <lunaryorn@googlemail.com>,
382
distributed under the terms of the BSD License""")
383
parser.add_argument('--version', help='Show synaptiks version',
384
action='version', version=__version__)
385
actions = parser.add_subparsers(title='Actions')
387
init_act = actions.add_parser(
388
'init', help='Initialize touchpad configuration. Should not be '
389
'called manually, but automatically at session startup.')
390
init_act.set_defaults(action='init')
392
load_act = actions.add_parser(
393
'load', help='Load the touchpad configuration')
394
load_act.add_argument(
395
'filename', nargs='?', help='File to load the configuration from. If '
396
'empty, the default configuration file is loaded.')
397
load_act.set_defaults(action='load')
399
save_act = actions.add_parser(
400
'save', help='Save the current touchpad configuration')
401
save_act.add_argument(
402
'filename', nargs='?', help='File to save the configuration to. If '
403
'empty, the default configuration file is used.')
404
save_act.set_defaults(action='save')
406
# default filename to load configuration from
407
parser.set_defaults(filename=None)
409
# we don't have any arguments, but need to make sure, that the builtin
410
# arguments (--help mainly) are handled
411
args = parser.parse_args()
413
with xlib.display() as display:
414
touchpad = Touchpad.find_first(display)
416
if args.action == 'init':
417
driver_defaults = TouchpadConfiguration(touchpad)
418
driver_defaults.save(get_touchpad_defaults_file_path())
419
if args.action in ('init', 'load'):
420
TouchpadConfiguration.load(touchpad, filename=args.filename)
421
if args.action == 'save':
422
current_config = TouchpadConfiguration(touchpad)
423
current_config.save(filename=args.filename)
426
if __name__ == '__main__':