1#!/usr/bin/env python3 2# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- 3# LightDM GTK Greeter Settings 4# Copyright (C) 2015 Andrew P. <pan.pav.7c5@gmail.com> 5# 6# This program is free software: you can redistribute it and/or modify it 7# under the terms of the GNU General Public License version 3, as published 8# by the Free Software Foundation. 9# 10# This program is distributed in the hope that it will be useful, but 11# WITHOUT ANY WARRANTY; without even the implied warranties of 12# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 13# PURPOSE. See the GNU General Public License for more details. 14# 15# You should have received a copy of the GNU General Public License along 16# with this program. If not, see <http://www.gnu.org/licenses/>. 17 18import configparser 19import os 20import sys 21from collections import OrderedDict 22from glob import iglob 23 24from gi.repository import GLib 25 26from lightdm_gtk_greeter_settings import helpers 27 28 29class Config: 30 31 class ConfigGroup: 32 33 def __init__(self, config): 34 self._config = config 35 self._items = OrderedDict() 36 37 def __iter__(self): 38 return iter(self._items) 39 40 def __contains__(self, item): 41 return item in self._items 42 43 def __getitem__(self, item): 44 values = self._items.get(item) 45 return values[-1][1] if values else None 46 47 def __setitem__(self, item, value): 48 if isinstance(value, tuple): 49 value, default = value 50 else: 51 default = None 52 53 values = self._items.get(item) 54 55 if values and values[-1][1] == value: 56 return 57 58 if values and values[-1][0] == self._config._output_path: 59 if len(values) > 1 and values[-2][1] == value: 60 del values[-1] 61 elif default is not None and value == default and len(values) == 1: 62 values.clear() 63 else: 64 values[-1] = (self._config._output_path, value) 65 elif values is not None: 66 if default is None or value != default or (values and values[-1][1] != default): 67 values.append((self._config._output_path, value)) 68 else: 69 if default is None or value != default: 70 self._items[item] = [(self._config._output_path, value)] 71 72 def __delitem__(self, item): 73 values = self._items.get(item) 74 if values is not None: 75 if values and values[-1][0] == self._config._output_path: 76 del values[-1] 77 if not values: 78 del self._items[item] 79 80 def __init__(self, base_dir='lightdm', base_name='lightdm-gtk-greeter.conf'): 81 self._base_dir = base_dir 82 self._base_name = base_name 83 self._output_path = helpers.get_config_path() 84 self._groups = OrderedDict() 85 self._key_values = helpers.SimpleDictWrapper(getter=self._get_key_values) 86 87 def read(self): 88 self._groups.clear() 89 90 pathes = [] 91 pathes += GLib.get_system_data_dirs() 92 pathes += GLib.get_system_config_dirs() 93 pathes.append(os.path.dirname(os.path.dirname(self._output_path))) 94 95 files = [] 96 for path in pathes: 97 files += sorted(iglob(os.path.join(path, self._base_dir, 98 self._base_name + '.d', '*.conf'))) 99 files.append(os.path.join(path, self._base_dir, self._base_name)) 100 101 for path in filter(os.path.isfile, files): 102 config_file = configparser.RawConfigParser(strict=False, allow_no_value=True) 103 try: 104 if not config_file.read(path): 105 continue 106 except configparser.Error as e: 107 print(e, file=sys.stderr) 108 continue 109 110 for groupname, values in config_file.items(): 111 if groupname == 'DEFAULT': 112 continue 113 114 if groupname not in self._groups: 115 self._groups[groupname] = Config.ConfigGroup(self) 116 group = self._groups[groupname] 117 118 for key, value in values.items(): 119 if value is None: 120 print('[{group}] {key}: Keys without values are not allowed'.format( 121 group=groupname, key=key), file=sys.stderr) 122 continue 123 if key.startswith('-'): 124 key = key[1:] 125 value = None 126 127 if key in group._items: 128 values = group._items[key] 129 if value is not None or values: 130 values.append((path, value)) 131 elif value is not None: 132 group._items[key] = [(path, value)] 133 134 def write(self): 135 config_file = configparser.RawConfigParser(strict=False) 136 137 for groupname, group in self._groups.items(): 138 config_section = None 139 for key, values in group._items.items(): 140 if not values or values[-1][0] != self._output_path: 141 continue 142 143 if values[-1][1] is not None or len(values) > 1: 144 if not config_section: 145 config_file.add_section(groupname) 146 config_section = config_file[groupname] 147 if values[-1][1] is None: 148 config_section['-' + key] = '' 149 else: 150 config_section[key] = values[-1][1] 151 152 with open(self._output_path, 'w') as file: 153 config_file.write(file) 154 155 def is_writable(self): 156 if os.path.exists(self._output_path) and os.access(self._output_path, os.W_OK): 157 return True 158 return os.access(os.path.dirname(self._output_path), os.W_OK | os.X_OK) 159 160 def items(self): 161 return self._groups.items() 162 163 def allitems(self): 164 return ((g, k, items[k]) for (g, items) in self._groups.items() for k in items._items) 165 166 def add_group(self, name): 167 if name in self._groups: 168 return self._groups[name] 169 else: 170 return self._groups.setdefault(name, Config.ConfigGroup(self)) 171 172 @property 173 def key_values(self): 174 return self._key_values 175 176 def _get_key_values(self, item): 177 group = self._groups.get(item[0]) 178 if group: 179 values = group._items.get(item[1]) 180 if values is not None: 181 return tuple(values) 182 return None 183 184 def __iter__(self): 185 return iter(self._groups) 186 187 def __getitem__(self, item): 188 if isinstance(item, tuple): 189 group = self._groups.get(item[0]) 190 return group[item[1]] if group else None 191 return self._groups.get(item) 192 193 def __setitem__(self, item, value): 194 if isinstance(item, tuple): 195 if not item[0] in self._groups: 196 self._groups[item[0]] = Config.ConfigGroup(self) 197 self._groups[item[0]][item[1]] = value 198 199 def __delitem__(self, item): 200 if isinstance(item, tuple): 201 group = self._groups.get(item[0]) 202 if group is not None: 203 del group[item[1]] 204 return 205 206 group = self._groups.get(item) 207 if group is not None: 208 if not group: 209 del self._groups[item] 210 return 211 212 keys_to_remove = [] 213 for key, values in group._items.items(): 214 if values[-1][0] == self._output_path: 215 if len(values) == 1: 216 keys_to_remove.append(key) 217 else: 218 values[-1] = (self._output_path, None) 219 elif values: 220 values.append((self._output_path, None)) 221 222 if len(keys_to_remove) < len(group._items): 223 for key in keys_to_remove: 224 del group._items[key] 225 else: 226 del self._groups[item] 227