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) 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().__init__(**kwargs)
 | |
|         if self.get(_KEY_NAME) != device.name:
 | |
|             self[_KEY_NAME] = device.name
 | |
|         self.update(device)
 | |
| 
 | |
|     def __setitem__(self, key, value):
 | |
|         super().__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
 |