186 lines
6.5 KiB
Python
186 lines
6.5 KiB
Python
# -*- python-mode -*-
|
|
|
|
## Copyright (C) 2012-2013 Daniel Pavel
|
|
##
|
|
## This program 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 2 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 General Public License for more details.
|
|
##
|
|
## You should have received a copy of the GNU 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 os as _os
|
|
import os.path as _path
|
|
|
|
from json import dump as _json_save
|
|
from json import load as _json_load
|
|
from logging import DEBUG as _DEBUG
|
|
from logging import INFO as _INFO
|
|
from logging import getLogger
|
|
|
|
from solaar import __version__
|
|
|
|
_log = getLogger(__name__)
|
|
del getLogger
|
|
|
|
_XDG_CONFIG_HOME = _os.environ.get('XDG_CONFIG_HOME') or _path.expanduser(_path.join('~', '.config'))
|
|
_file_path = _path.join(_XDG_CONFIG_HOME, 'solaar', 'config.json')
|
|
|
|
_KEY_VERSION = '_version'
|
|
_KEY_NAME = '_name'
|
|
_KEY_SERIAL = '_serial'
|
|
_KEY_MODEL_ID = '_modelId'
|
|
_KEY_UNIT_ID = '_unitId'
|
|
_configuration = {}
|
|
|
|
|
|
def _load():
|
|
if _path.isfile(_file_path):
|
|
loaded_configuration = {}
|
|
try:
|
|
with open(_file_path, 'r') as config_file:
|
|
loaded_configuration = _json_load(config_file)
|
|
except Exception:
|
|
_log.error('failed to load from %s', _file_path)
|
|
|
|
# loaded_configuration.update(_configuration)
|
|
_configuration.clear()
|
|
_configuration.update(loaded_configuration)
|
|
|
|
if _log.isEnabledFor(_DEBUG):
|
|
_log.debug('load => %s', _configuration)
|
|
|
|
_cleanup(_configuration)
|
|
_cleanup_load(_configuration)
|
|
_configuration[_KEY_VERSION] = __version__
|
|
return _configuration
|
|
|
|
|
|
def save():
|
|
# don't save if the configuration hasn't been loaded
|
|
if _KEY_VERSION not in _configuration:
|
|
return
|
|
|
|
dirname = _os.path.dirname(_file_path)
|
|
if not _path.isdir(dirname):
|
|
try:
|
|
_os.makedirs(dirname)
|
|
except Exception:
|
|
_log.error('failed to create %s', dirname)
|
|
return False
|
|
|
|
_cleanup(_configuration)
|
|
|
|
try:
|
|
with open(_file_path, 'w') as config_file:
|
|
_json_save(_configuration, config_file, skipkeys=True, indent=2, sort_keys=True)
|
|
|
|
if _log.isEnabledFor(_INFO):
|
|
_log.info('saved %s to %s', _configuration, _file_path)
|
|
return True
|
|
except Exception as e:
|
|
_log.error('failed to save to %s: %s', _file_path, e)
|
|
|
|
|
|
def _cleanup(d):
|
|
# remove None values from the dict
|
|
for key in list(d.keys()):
|
|
value = d.get(key)
|
|
if value is None:
|
|
del d[key]
|
|
elif isinstance(value, dict):
|
|
_cleanup(value)
|
|
|
|
|
|
def _cleanup_load(d):
|
|
# remove boolean values for mouse-gesture and dpi-sliding
|
|
for device in d.values():
|
|
if isinstance(device, dict):
|
|
for setting in ['mouse-gestures', 'dpi-sliding']:
|
|
mg = device.get(setting, None)
|
|
if mg is True or mg is False:
|
|
del device[setting]
|
|
|
|
|
|
class _DeviceEntry(dict):
|
|
def __init__(self, device, **kwargs):
|
|
super(_DeviceEntry, self).__init__(**kwargs)
|
|
if self.get(_KEY_NAME) != device.name:
|
|
self[_KEY_NAME] = device.name
|
|
self.update(device)
|
|
|
|
def __setitem__(self, key, value):
|
|
super(_DeviceEntry, self).__setitem__(key, value)
|
|
save()
|
|
|
|
def update(self, device):
|
|
if device.modelId and device.modelId != self.get(_KEY_MODEL_ID):
|
|
self[_KEY_MODEL_ID] = device.modelId
|
|
if device.unitId and device.unitId != self.get(_KEY_UNIT_ID):
|
|
self[_KEY_UNIT_ID] = device.unitId
|
|
if device.serial and device.serial != '?' and device.serial != self.get(_KEY_SERIAL):
|
|
self[_KEY_SERIAL] = device.serial
|
|
|
|
def get_sensitivity(self, name):
|
|
return self.get('_sensitive', {}).get(name, False)
|
|
|
|
def set_sensitivity(self, name, value):
|
|
sensitives = self.get('_sensitive', {})
|
|
if sensitives.get(name) != value:
|
|
sensitives[name] = value
|
|
self.__setitem__('_sensitive', sensitives)
|
|
|
|
|
|
# This is neccessarily complicate because the same device can be attached in several different ways.
|
|
# All HID++ 2.0 devices have a modelId and unitId, which can be accessed when they are connected.
|
|
# When paired via a receiver the receiver provides a WPID and a serial number.
|
|
# The unitId and serial number are supposed to be the same, but for some models they are not
|
|
# so even though the modelId includes the WPID it is not always possible to determine the identity of a
|
|
# paired but not receiver-connected device for which the unitId is not known.
|
|
# This only happens is Solaar has never seen the device while it is paired and connected through a receiver.
|
|
def persister(device):
|
|
if not _configuration:
|
|
_load()
|
|
|
|
entry = {}
|
|
key = None
|
|
if device.wpid: # connected via receiver
|
|
entry = _configuration.get('%s:%s' % (device.wpid, device.serial), {})
|
|
if entry or device.protocol == 1.0: # found entry or create entry for old-style devices
|
|
key = '%s:%s' % (device.wpid, device.serial)
|
|
elif not entry and device.modelId: # online new-style device so look for modelId and unitId
|
|
for k, c in _configuration.items():
|
|
if isinstance(c, dict) and c.get(_KEY_MODEL_ID) == device.modelId and c.get(_KEY_UNIT_ID) == device.unitId:
|
|
entry = c # use the entry that matches modelId and unitId
|
|
key = k
|
|
break
|
|
if device.wpid and entry: # move entry to wpid:serial
|
|
del _configuration[key]
|
|
key = '%s:%s' % (device.wpid, device.serial)
|
|
_configuration[key] = entry
|
|
elif device.wpid and not entry: # create now with wpid:serial
|
|
key = '%s:%s' % (device.wpid, device.serial)
|
|
elif not entry: # create now with modelId:unitId
|
|
key = '%s:%s' % (device.modelId, device.unitId)
|
|
else: # defer until more is known (i.e., device comes on line)
|
|
return
|
|
|
|
if key and not isinstance(entry, _DeviceEntry):
|
|
entry = _DeviceEntry(device, **entry)
|
|
_configuration[key] = entry
|
|
if isinstance(entry, _DeviceEntry):
|
|
entry.update(device)
|
|
|
|
return entry
|
|
|
|
|
|
def attach_to(device):
|
|
pass
|