Package list lightdm-gtk-greeter-settings / a626171
Multiple configuration files Andrew P. 7 years ago
5 changed file(s) with 244 addition(s) and 51 deletion(s). Raw diff Collapse all Expand all
0 #!/usr/bin/env python3
1 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2 # LightDM GTK Greeter Settings
3 # Copyright (C) 2015 Andrew P. <pan.pav.7c5@gmail.com>
4 #
5 # This program is free software: you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License version 3, as published
7 # by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranties of
11 # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
12 # PURPOSE. See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with this program. If not, see <http://www.gnu.org/licenses/>.
16
17 import configparser
18 import os
19 from collections import OrderedDict
20 from glob import iglob
21
22
23 class Config:
24
25 class ConfigGroup:
26
27 def __init__(self, config):
28 self._config = config
29 self._items = OrderedDict()
30
31 def __iter__(self):
32 return iter(self._items)
33
34 def __contains__(self, item):
35 return item in self._items
36
37 def __getitem__(self, item):
38 values = self._items.get(item)
39 return values[-1][1] if values else None
40
41 def __setitem__(self, item, value):
42 if isinstance(value, tuple):
43 value, default = value
44 else:
45 default = None
46
47 values = self._items.get(item)
48 if values and values[-1][0] == self._config._output_path:
49 if default is not None and value == default and len(values) == 1:
50 values.clear()
51 else:
52 values[-1] = (self._config._output_path, value)
53 elif values is not None:
54 if default is None or value != default or (values and values[-1][1] != default):
55 values.append((self._config._output_path, value))
56 else:
57 if default is None or value != default:
58 self._items[item] = [(self._config._output_path, value)]
59
60 def __delitem__(self, item):
61 values = self._items.get(item)
62 if values is not None:
63 if values and values[-1][0] == self._config._output_path:
64 del values[-1]
65 if not values:
66 del self._items[item]
67
68 def get_key_file(self, key):
69 values = self._items.get(key)
70 return values[-1][0] if values else None
71
72 def __init__(self, input_pathes, output_path):
73 self._input_pathes = tuple(input_pathes)
74 self._output_path = output_path
75 self._groups = OrderedDict()
76
77 def read(self):
78 files = []
79 for path in self._input_pathes:
80 if os.path.isdir(path):
81 files.extend(sorted(iglob(os.path.join(path, '*.conf'))))
82 elif os.path.exists(path):
83 files.append(path)
84 if self._output_path not in files:
85 files.append(self._output_path)
86
87 self._groups.clear()
88 for path in files:
89 config_file = configparser.RawConfigParser(strict=False, allow_no_value=True)
90 config_file.read(path)
91
92 for groupname, values in config_file.items():
93 if groupname == 'DEFAULT':
94 continue
95
96 if groupname not in self._groups:
97 self._groups[groupname] = Config.ConfigGroup(self)
98 group = self._groups[groupname]
99
100 for key, value in values.items():
101 if key in group._items:
102 values = group._items[key]
103 if value is not None or values:
104 values.append((path, value))
105 elif value is not None:
106 group._items[key] = [(path, value)]
107
108 def write(self):
109 config_file = configparser.RawConfigParser(strict=False, allow_no_value=True)
110
111 for groupname, group in self._groups.items():
112 config_file.add_section(groupname)
113 config_section = config_file[groupname]
114
115 for key, values in group._items.items():
116 if not values or values[-1][0] != self._output_path:
117 continue
118 if values[-1][1] is not None or len(values) > 1:
119 config_section[key] = values[-1][1]
120
121 with open(self._output_path, 'w') as file:
122 config_file.write(file)
123
124 def items(self):
125 return self._groups.items()
126
127 def allitems(self):
128 return ((g, k, items[k]) for (g, items) in self._groups.items() for k in items._items)
129
130 def add_group(self, name):
131 if name in self._groups:
132 return self._groups[name]
133 else:
134 return self._groups.setdefault(name, Config.ConfigGroup(self))
135
136 def get_key_file(self, groupname, key):
137 group = self._groups.get(groupname)
138 return group.get_key_file(key) if group is not None else None
139
140 def __iter__(self):
141 return iter(self._groups)
142
143 def __getitem__(self, item):
144 if isinstance(item, tuple):
145 group = self._groups.get(item[0])
146 return group[item[1]] if group else None
147 return self._groups.get(item)
148
149 def __setitem__(self, item, value):
150 if isinstance(item, tuple):
151 if not item[0] in self._groups:
152 self._groups = Config.ConfigGroup(self)
153 self._groups[item[0]][item[1]] = value
154
155 def __delitem__(self, item):
156 if isinstance(item, tuple):
157 group = self._groups.get(item[0])
158 if group is not None:
159 del group[item[1]]
160 return
161
162 group = self._groups.get(item)
163 if group is not None:
164 if not group:
165 del self._groups[item]
166 return
167
168 keys_to_remove = []
169 for key, values in group._items.items():
170 if values[-1][0] == self._output_path:
171 if len(values) == 1:
172 keys_to_remove.append(key)
173 else:
174 values[-1] = (self._output_path, None)
175 elif values:
176 values.append((self._output_path, None))
177
178 if len(keys_to_remove) < len(group._items):
179 for key in keys_to_remove:
180 del group._items[key]
181 else:
182 del self._groups[item]
1616
1717
1818 import collections
19 import configparser
2019 import os
2120 import shlex
2221 import sys
22 from functools import partialmethod
2323 from glob import iglob
24 from functools import partialmethod
2524 from itertools import chain
2625 from locale import gettext as _
2726
2827 from gi.repository import (
2928 Gdk,
29 GLib,
3030 Gtk)
3131 from gi.repository import Pango
3232 from gi.repository.GObject import markup_escape_text as escape_markup
3333
3434 from lightdm_gtk_greeter_settings import (
35 Config,
3536 helpers,
3637 IconEntry,
3738 IndicatorsEntry,
137138 group.entry_added.connect(self.on_entry_added)
138139 group.entry_removed.connect(self.on_entry_removed)
139140
140 self._config_path = helpers.get_config_path()
141 self._allow_edit = self._has_access_to_write(self._config_path)
141 config_pathes = []
142 config_pathes.extend(os.path.join(p, 'lightdm-gtk-greeter.conf.d')
143 for p in GLib.get_system_data_dirs())
144 config_pathes.extend(os.path.join(p, 'lightdm-gtk-greeter.conf.d')
145 for p in GLib.get_system_config_dirs())
146 config_pathes.append(os.path.join(os.path.dirname(helpers.get_config_path()),
147 'lightdm-gtk-greeter.conf.d'))
148
149 self._config = Config.Config(config_pathes, helpers.get_config_path())
150
151 self._allow_edit = self._has_access_to_write(helpers.get_config_path())
142152 self._widgets.apply.props.visible = self._allow_edit
143153
144154 if not self._allow_edit:
150160 secondary_text=_(
151161 'It seems that you don\'t have permissions to write to '
152162 'file:\n{path}\n\nTry to run this program using "sudo" '
153 'or "pkexec"').format(path=self._config_path),
163 'or "pkexec"').format(path=helpers.get_config_path()),
154164 message_type=Gtk.MessageType.WARNING)
155165
156166 if self.mode == WindowMode.Embedded:
175185
176186 self.set_titlebar(header)
177187
178 self._config = configparser.RawConfigParser(strict=False)
179188 self._read()
180189
181190 def _has_access_to_write(self, path):
182 if os.path.exists(path) and os.access(self._config_path, os.W_OK):
191 if os.path.exists(path) and os.access(helpers.get_config_path(), os.W_OK):
183192 return True
184 return os.access(os.path.dirname(self._config_path), os.W_OK | os.X_OK)
193 return os.access(os.path.dirname(helpers.get_config_path()), os.W_OK | os.X_OK)
185194
186195 def _set_message(self, message, type_=Gtk.MessageType.INFO):
187196 if not message:
192201 self._widgets.infobar.show()
193202
194203 def _read(self):
195 self._config.clear()
196 try:
197 if not self._config.read(self._config_path) and \
198 self.mode != WindowMode.Embedded:
199 helpers.show_message(text=_('Failed to read configuration file: {path}')
200 .format(path=self._config_path),
201 message_type=Gtk.MessageType.ERROR)
202 except (configparser.DuplicateSectionError,
203 configparser.MissingSectionHeaderError):
204 pass
205
204 self._config.read()
206205 self._changed_entries = None
207206
208207 for group in self._groups:
225224 self._widgets.apply.props.sensitive = False
226225
227226 try:
228 with open(self._config_path, 'w') as file:
229 self._config.write(file)
227 self._config.write()
230228 except OSError as e:
231229 helpers.show_message(e, Gtk.MessageType.ERROR)
232230
320318 class EntryMenu:
321319 menu = Gtk.Menu()
322320 value = new_item()
321 file = new_item()
323322 error_separator = Gtk.SeparatorMenuItem()
324323 error = new_item()
325324 error_action = new_item(self.on_entry_fix_clicked)
328327 default = new_item(self.on_entry_reset_clicked)
329328
330329 menu.append(value)
330 menu.append(file)
331331 menu.append(error_separator)
332332 menu.append(error)
333333 menu.append(error_action)
354354 group=group.name,
355355 key=key,
356356 value=format_value(value=entry.value, enabled=entry.enabled))
357
358 key_file = None
359 if entry not in self._changed_entries:
360 key_file = self._config.get_key_file(group.name, key)
361 if key_file and key_file == helpers.get_config_path():
362 key_file = None
363 elif key_file:
364 menu.file.props.label = _('Value defined in file: {path}')\
365 .format(path=escape_markup(key_file))
366 menu.file.set_tooltip_text(key_file)
367 menu.file.props.visible = key_file is not None
357368
358369 error = entry.error
359370 error_action = None
366377 menu.error_action.props.label = label or ''
367378 if error_action:
368379 menu.error_action._fix_entry_data = entry, error_action
369 menu.error.set_label(error)
380 menu.error.set_label(escape_markup(error))
370381
371382 menu.error.props.visible = error is not None
372383 menu.error_action.props.visible = error_action is not None
4343
4444 def read(self, config):
4545 self._entries.clear()
46 for name, section in config.items():
46 for name, group in config.items():
4747 if not name.startswith(self.GROUP_PREFIX):
4848 continue
4949 name = name[len(self.GROUP_PREFIX):].strip()
5050 entry = MonitorEntry(self._widgets)
51 entry['background'] = section.get('background', None)
52 entry['user-background'] = bool2string(section.getboolean('user-background', None), 1)
53 entry['laptop'] = bool2string(section.getboolean('laptop', None), True)
51 entry['background'] = group['background']
52 entry['user-background'] = bool2string(group['user-background'], True)
53 entry['laptop'] = bool2string(group['laptop'], True)
5454 self._entries[name] = entry
5555 self.entry_added.emit(entry, name)
5656
5757 def write(self, config):
58 for name in config.sections():
59 if name.startswith(self.GROUP_PREFIX):
60 config.remove_section(name)
58 groups = set(name for name, __ in self._entries.items())
59 groups_to_remove = tuple(name for name in config
60 if (name.startswith(self.GROUP_PREFIX) and
61 name[len(self.GROUP_PREFIX):].strip() not in groups))
6162
6263 for name, entry in self._entries.items():
63 section = '{prefix} {name}'.format(prefix=self.GROUP_PREFIX, name=name.strip())
64 config.add_section(section)
64 groupname = '{prefix} {name}'.format(prefix=self.GROUP_PREFIX, name=name.strip())
65 group = config.add_group(groupname)
6566 for key, value in entry:
66 if value is not None:
67 config.set(section, key, value)
67 group[key] = value
68
69 for name in groups_to_remove:
70 del config[name]
6871
6972 def _on_label_link_activate(self, label, uri):
7073 if not self._dialog:
4343 self.__defaults_wrapper = None
4444
4545 def read(self, config):
46 '''Read group content from specified GreeterConfig object'''
4647 raise NotImplementedError(self.__class__)
4748
4849 def write(self, config):
50 '''Writes content of this group to specified GreeterConfig object'''
4951 raise NotImplementedError(self.__class__)
5052
5153 @property
5254 def entries(self):
55 '''entries["key"] - key => Entry mapping. Read only.'''
5356 if not self.__entries_wrapper:
5457 self.__entries_wrapper = BaseGroup.__DictWrapper(self._get_entry)
5558 return self.__entries_wrapper
5659
5760 @property
5861 def defaults(self):
62 '''defaults["key"] - default value for "key" entry. Read only.'''
5963 if not self.__defaults_wrapper:
6064 self.__defaults_wrapper = BaseGroup.__DictWrapper(self._get_default)
6165 return self.__defaults_wrapper
6266
63 def _get_default(self, key):
67 def _get_entry(self, key):
6468 raise NotImplementedError(self.__class__)
6569
66 def _get_entry(self, key):
70 def _get_default(self, key):
6771 raise NotImplementedError(self.__class__)
6872
6973 @GObject.Signal
9296 return self._name
9397
9498 def read(self, config):
95
9699 if not self._entries:
97100 for key, (klass, default) in self._options.items():
98101 entry = klass(WidgetsWrapper(self._widgets, key))
101104 self.entry_added.emit(entry, key)
102105
103106 for key, entry in self._entries.items():
104 if config.has_option(self._name, key):
105 entry.value = config.get(self._name, key)
106 entry.enabled = True
107 else:
108 entry.value = self._defaults[key]
109 entry.enabled = False
107 value = config[self._name, key]
108 entry.value = value if value is not None else self._defaults[key]
109 entry.enabled = value is not None
110110
111111 def write(self, config):
112
113 if not config.has_section(self._name):
114 config.add_section(self._name)
115
116112 for key, entry in self._entries.items():
117 value = entry.value
118 if entry.enabled and value != self._get_default(key):
119 config.set(self._name, key, entry.value)
120 else:
121 config.remove_option(self._name, key)
113 del config[self._name, key]
114 if entry.enabled:
115 config[self._name, key] = entry.value, self._get_default(key)
122116
123117 def _get_entry(self, key):
124118 return self._entries.get(key)
117117
118118
119119 def bool2string(value, skip_none=False):
120 if isinstance(value, str):
121 value = string2bool(value)
120122 return 'true' if value else 'false' if not skip_none or value is not None else None
121123
122124