Skip to content

Commit bf26da3

Browse files
committed
Multiple configuration files
1 parent 30d133d commit bf26da3

File tree

5 files changed

+244
-51
lines changed

5 files changed

+244
-51
lines changed
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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. <[email protected]>
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+
18+
import configparser
19+
import os
20+
from collections import OrderedDict
21+
from glob import iglob
22+
23+
24+
class Config:
25+
26+
class ConfigGroup:
27+
28+
def __init__(self, config):
29+
self._config = config
30+
self._items = OrderedDict()
31+
32+
def __iter__(self):
33+
return iter(self._items)
34+
35+
def __contains__(self, item):
36+
return item in self._items
37+
38+
def __getitem__(self, item):
39+
values = self._items.get(item)
40+
return values[-1][1] if values else None
41+
42+
def __setitem__(self, item, value):
43+
if isinstance(value, tuple):
44+
value, default = value
45+
else:
46+
default = None
47+
48+
values = self._items.get(item)
49+
if values and values[-1][0] == self._config._output_path:
50+
if default is not None and value == default and len(values) == 1:
51+
values.clear()
52+
else:
53+
values[-1] = (self._config._output_path, value)
54+
elif values is not None:
55+
if default is None or value != default or (values and values[-1][1] != default):
56+
values.append((self._config._output_path, value))
57+
else:
58+
if default is None or value != default:
59+
self._items[item] = [(self._config._output_path, value)]
60+
61+
def __delitem__(self, item):
62+
values = self._items.get(item)
63+
if values is not None:
64+
if values and values[-1][0] == self._config._output_path:
65+
del values[-1]
66+
if not values:
67+
del self._items[item]
68+
69+
def get_key_file(self, key):
70+
values = self._items.get(key)
71+
return values[-1][0] if values else None
72+
73+
def __init__(self, input_pathes, output_path):
74+
self._input_pathes = tuple(input_pathes)
75+
self._output_path = output_path
76+
self._groups = OrderedDict()
77+
78+
def read(self):
79+
files = []
80+
for path in self._input_pathes:
81+
if os.path.isdir(path):
82+
files.extend(sorted(iglob(os.path.join(path, '*.conf'))))
83+
elif os.path.exists(path):
84+
files.append(path)
85+
if self._output_path not in files:
86+
files.append(self._output_path)
87+
88+
self._groups.clear()
89+
for path in files:
90+
config_file = configparser.RawConfigParser(strict=False, allow_no_value=True)
91+
config_file.read(path)
92+
93+
for groupname, values in config_file.items():
94+
if groupname == 'DEFAULT':
95+
continue
96+
97+
if groupname not in self._groups:
98+
self._groups[groupname] = Config.ConfigGroup(self)
99+
group = self._groups[groupname]
100+
101+
for key, value in values.items():
102+
if key in group._items:
103+
values = group._items[key]
104+
if value is not None or values:
105+
values.append((path, value))
106+
elif value is not None:
107+
group._items[key] = [(path, value)]
108+
109+
def write(self):
110+
config_file = configparser.RawConfigParser(strict=False, allow_no_value=True)
111+
112+
for groupname, group in self._groups.items():
113+
config_file.add_section(groupname)
114+
config_section = config_file[groupname]
115+
116+
for key, values in group._items.items():
117+
if not values or values[-1][0] != self._output_path:
118+
continue
119+
if values[-1][1] is not None or len(values) > 1:
120+
config_section[key] = values[-1][1]
121+
122+
with open(self._output_path, 'w') as file:
123+
config_file.write(file)
124+
125+
def items(self):
126+
return self._groups.items()
127+
128+
def allitems(self):
129+
return ((g, k, items[k]) for (g, items) in self._groups.items() for k in items._items)
130+
131+
def add_group(self, name):
132+
if name in self._groups:
133+
return self._groups[name]
134+
else:
135+
return self._groups.setdefault(name, Config.ConfigGroup(self))
136+
137+
def get_key_file(self, groupname, key):
138+
group = self._groups.get(groupname)
139+
return group.get_key_file(key) if group is not None else None
140+
141+
def __iter__(self):
142+
return iter(self._groups)
143+
144+
def __getitem__(self, item):
145+
if isinstance(item, tuple):
146+
group = self._groups.get(item[0])
147+
return group[item[1]] if group else None
148+
return self._groups.get(item)
149+
150+
def __setitem__(self, item, value):
151+
if isinstance(item, tuple):
152+
if not item[0] in self._groups:
153+
self._groups = Config.ConfigGroup(self)
154+
self._groups[item[0]][item[1]] = value
155+
156+
def __delitem__(self, item):
157+
if isinstance(item, tuple):
158+
group = self._groups.get(item[0])
159+
if group is not None:
160+
del group[item[1]]
161+
return
162+
163+
group = self._groups.get(item)
164+
if group is not None:
165+
if not group:
166+
del self._groups[item]
167+
return
168+
169+
keys_to_remove = []
170+
for key, values in group._items.items():
171+
if values[-1][0] == self._output_path:
172+
if len(values) == 1:
173+
keys_to_remove.append(key)
174+
else:
175+
values[-1] = (self._output_path, None)
176+
elif values:
177+
values.append((self._output_path, None))
178+
179+
if len(keys_to_remove) < len(group._items):
180+
for key in keys_to_remove:
181+
del group._items[key]
182+
else:
183+
del self._groups[item]

lightdm_gtk_greeter_settings/GtkGreeterSettingsWindow.py

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,23 @@
1717

1818

1919
import collections
20-
import configparser
2120
import os
2221
import shlex
2322
import sys
24-
from glob import iglob
2523
from functools import partialmethod
24+
from glob import iglob
2625
from itertools import chain
2726
from locale import gettext as _
2827

2928
from gi.repository import (
3029
Gdk,
30+
GLib,
3131
Gtk)
3232
from gi.repository import Pango
3333
from gi.repository.GObject import markup_escape_text as escape_markup
3434

3535
from lightdm_gtk_greeter_settings import (
36+
Config,
3637
helpers,
3738
IconEntry,
3839
IndicatorsEntry,
@@ -138,8 +139,17 @@ def init_window(self):
138139
group.entry_added.connect(self.on_entry_added)
139140
group.entry_removed.connect(self.on_entry_removed)
140141

141-
self._config_path = helpers.get_config_path()
142-
self._allow_edit = self._has_access_to_write(self._config_path)
142+
config_pathes = []
143+
config_pathes.extend(os.path.join(p, 'lightdm-gtk-greeter.conf.d')
144+
for p in GLib.get_system_data_dirs())
145+
config_pathes.extend(os.path.join(p, 'lightdm-gtk-greeter.conf.d')
146+
for p in GLib.get_system_config_dirs())
147+
config_pathes.append(os.path.join(os.path.dirname(helpers.get_config_path()),
148+
'lightdm-gtk-greeter.conf.d'))
149+
150+
self._config = Config.Config(config_pathes, helpers.get_config_path())
151+
152+
self._allow_edit = self._has_access_to_write(helpers.get_config_path())
143153
self._widgets.apply.props.visible = self._allow_edit
144154

145155
if not self._allow_edit:
@@ -151,7 +161,7 @@ def init_window(self):
151161
secondary_text=_(
152162
'It seems that you don\'t have permissions to write to '
153163
'file:\n{path}\n\nTry to run this program using "sudo" '
154-
'or "pkexec"').format(path=self._config_path),
164+
'or "pkexec"').format(path=helpers.get_config_path()),
155165
message_type=Gtk.MessageType.WARNING)
156166

157167
if self.mode == WindowMode.Embedded:
@@ -176,13 +186,12 @@ def init_window(self):
176186

177187
self.set_titlebar(header)
178188

179-
self._config = configparser.RawConfigParser(strict=False)
180189
self._read()
181190

182191
def _has_access_to_write(self, path):
183-
if os.path.exists(path) and os.access(self._config_path, os.W_OK):
192+
if os.path.exists(path) and os.access(helpers.get_config_path(), os.W_OK):
184193
return True
185-
return os.access(os.path.dirname(self._config_path), os.W_OK | os.X_OK)
194+
return os.access(os.path.dirname(helpers.get_config_path()), os.W_OK | os.X_OK)
186195

187196
def _set_message(self, message, type_=Gtk.MessageType.INFO):
188197
if not message:
@@ -193,17 +202,7 @@ def _set_message(self, message, type_=Gtk.MessageType.INFO):
193202
self._widgets.infobar.show()
194203

195204
def _read(self):
196-
self._config.clear()
197-
try:
198-
if not self._config.read(self._config_path) and \
199-
self.mode != WindowMode.Embedded:
200-
helpers.show_message(text=_('Failed to read configuration file: {path}')
201-
.format(path=self._config_path),
202-
message_type=Gtk.MessageType.ERROR)
203-
except (configparser.DuplicateSectionError,
204-
configparser.MissingSectionHeaderError):
205-
pass
206-
205+
self._config.read()
207206
self._changed_entries = None
208207

209208
for group in self._groups:
@@ -226,8 +225,7 @@ def _write(self):
226225
self._widgets.apply.props.sensitive = False
227226

228227
try:
229-
with open(self._config_path, 'w') as file:
230-
self._config.write(file)
228+
self._config.write()
231229
except OSError as e:
232230
helpers.show_message(e, Gtk.MessageType.ERROR)
233231

@@ -321,6 +319,7 @@ def new_item(activate=None, width=90):
321319
class EntryMenu:
322320
menu = Gtk.Menu()
323321
value = new_item()
322+
file = new_item()
324323
error_separator = Gtk.SeparatorMenuItem()
325324
error = new_item()
326325
error_action = new_item(self.on_entry_fix_clicked)
@@ -329,6 +328,7 @@ class EntryMenu:
329328
default = new_item(self.on_entry_reset_clicked)
330329

331330
menu.append(value)
331+
menu.append(file)
332332
menu.append(error_separator)
333333
menu.append(error)
334334
menu.append(error_action)
@@ -356,6 +356,17 @@ def format_value(value=None, enabled=True):
356356
key=key,
357357
value=format_value(value=entry.value, enabled=entry.enabled))
358358

359+
key_file = None
360+
if entry not in self._changed_entries:
361+
key_file = self._config.get_key_file(group.name, key)
362+
if key_file and key_file == helpers.get_config_path():
363+
key_file = None
364+
elif key_file:
365+
menu.file.props.label = _('Value defined in file: {path}')\
366+
.format(path=escape_markup(key_file))
367+
menu.file.set_tooltip_text(key_file)
368+
menu.file.props.visible = key_file is not None
369+
359370
error = entry.error
360371
error_action = None
361372
if error:
@@ -367,7 +378,7 @@ def format_value(value=None, enabled=True):
367378
menu.error_action.props.label = label or ''
368379
if error_action:
369380
menu.error_action._fix_entry_data = entry, error_action
370-
menu.error.set_label(error)
381+
menu.error.set_label(escape_markup(error))
371382

372383
menu.error.props.visible = error is not None
373384
menu.error_action.props.visible = error_action is not None

lightdm_gtk_greeter_settings/MonitorsGroup.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,31 @@ def __init__(self, widgets, defaults_callback=None):
4444

4545
def read(self, config):
4646
self._entries.clear()
47-
for name, section in config.items():
47+
for name, group in config.items():
4848
if not name.startswith(self.GROUP_PREFIX):
4949
continue
5050
name = name[len(self.GROUP_PREFIX):].strip()
5151
entry = MonitorEntry(self._widgets)
52-
entry['background'] = section.get('background', None)
53-
entry['user-background'] = bool2string(section.getboolean('user-background', None), 1)
54-
entry['laptop'] = bool2string(section.getboolean('laptop', None), True)
52+
entry['background'] = group['background']
53+
entry['user-background'] = bool2string(group['user-background'], True)
54+
entry['laptop'] = bool2string(group['laptop'], True)
5555
self._entries[name] = entry
5656
self.entry_added.emit(entry, name)
5757

5858
def write(self, config):
59-
for name in config.sections():
60-
if name.startswith(self.GROUP_PREFIX):
61-
config.remove_section(name)
59+
groups = set(name for name, __ in self._entries.items())
60+
groups_to_remove = tuple(name for name in config
61+
if (name.startswith(self.GROUP_PREFIX) and
62+
name[len(self.GROUP_PREFIX):].strip() not in groups))
6263

6364
for name, entry in self._entries.items():
64-
section = '{prefix} {name}'.format(prefix=self.GROUP_PREFIX, name=name.strip())
65-
config.add_section(section)
65+
groupname = '{prefix} {name}'.format(prefix=self.GROUP_PREFIX, name=name.strip())
66+
group = config.add_group(groupname)
6667
for key, value in entry:
67-
if value is not None:
68-
config.set(section, key, value)
68+
group[key] = value
69+
70+
for name in groups_to_remove:
71+
del config[name]
6972

7073
def _on_label_link_activate(self, label, uri):
7174
if not self._dialog:

0 commit comments

Comments
 (0)