Codebase list lightdm-gtk-greeter-settings / c4774c7
Package renamed: lightdm_gtk_greeter_settings Andrew P. 10 years ago
10 changed file(s) with 789 addition(s) and 792 deletion(s). Raw diff Collapse all Expand all
+0
-141
gtk_greeter_settings/GtkGreeterSettingsWindow.py less more
0
1 from collections import namedtuple
2 import configparser
3 from glob import iglob
4 from locale import gettext as _
5 import os
6 import sys
7
8 from gi.repository import Gtk
9
10 from gtk_greeter_settings import OptionEntry
11 from gtk_greeter_settings import helpers
12
13
14 __all__ = ['GtkGreeterSettingsWindow']
15
16
17 BindingValue = namedtuple('BindingValue', ('option', 'default'))
18
19
20 OPTIONS_BINDINGS = \
21 {
22 'greeter':
23 {
24 # key: class, base widgets name, default value
25
26 # Theme
27 'theme-name': (OptionEntry.StringEntry, 'gtk_theme', None),
28 'icon-theme-name': (OptionEntry.StringEntry, 'icons_theme', None),
29 'font-name': (OptionEntry.FontEntry, 'font', 'Sans 10'),
30 'xft-antialias': (OptionEntry.BooleanEntry, 'antialias', False),
31 'xft-dpi': (OptionEntry.StringEntry, 'dpi', None),
32 'background': (OptionEntry.BackgroundEntry, 'background', None),
33 'default-user-image': (OptionEntry.IconEntry, 'user_image', '#avatar-default'),
34 # Panel
35 'show-clock': (OptionEntry.BooleanEntry, 'show_clock', False),
36 'clock-format': (OptionEntry.ClockFormatEntry, 'clock_format', '%a, %H:%M'),
37 'show-indicators': (OptionEntry.IndicatorsEntry, 'indicators', None),
38 # Position
39 'position': (OptionEntry.PositionEntry, 'position', '50%,center'),
40 }
41 }
42
43
44 class BuilderWrapper:
45 def __init__(self, builder, base):
46 self._builder = builder
47 self._base = base
48
49 def __getitem__(self, key):
50 return self._builder.get_object('%s_%s' % (self._base, key))
51
52
53 class GtkGreeterSettingsWindow(Gtk.Window):
54
55 __gtype_name__ = 'GtkGreeterSettingsWindow'
56
57 BUILDER_WIDGETS = ('dialog_buttons', 'apply_button',
58 'gtk_theme_values', 'icons_theme_values')
59
60 def __new__(cls):
61 builder = Gtk.Builder()
62 builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__))
63 window = builder.get_object("settings_window")
64 window._builder = builder
65 window.__dict__.update(('_' + w, builder.get_object(w))
66 for w in cls.BUILDER_WIDGETS)
67 builder.connect_signals(window)
68 window._init_window()
69 return window
70
71 def _init_window(self):
72 self._bindings = {section: {key: BindingValue(cls(BuilderWrapper(self._builder, base_name)), default)
73 for key, (cls, base_name, default) in keys.items()}
74 for section, keys in OPTIONS_BINDINGS.items()}
75
76 self._config_path = helpers.get_config_path()
77 if not self._has_access_to_write(self._config_path):
78 self._apply_button.props.sensitive = False
79
80 helpers.show_message(text=_('No permissions to save configuration'),
81 secondary_text=_(
82 'It seems that you don\'t have permissions to write to file:\n\
83 %s\n\nTry to run this program using "sudo" or "pkexec"') % self._config_path,
84 message_type=Gtk.MessageType.WARNING)
85
86 self._config = configparser.RawConfigParser(strict=False, allow_no_value=True)
87
88 try:
89 if not self._config.read(self._config_path):
90 helpers.show_message(text=_('Failed to read configuration file: %s') % self._config_path,
91 message_type=Gtk.MessageType.ERROR)
92 except (configparser.DuplicateSectionError, configparser.MissingSectionHeaderError):
93 pass
94
95 for theme in iglob(os.path.join(sys.prefix, 'share', 'themes', '*', 'gtk-3.0')):
96 self._gtk_theme_values.append_text(theme.split(os.path.sep)[-2])
97
98 for theme in iglob(os.path.join(sys.prefix, 'share', 'icons', '*', 'index.theme')):
99 self._icons_theme_values.append_text(theme.split(os.path.sep)[-2])
100
101 self._read()
102
103 def _has_access_to_write(self, path):
104 if os.path.exists(path) and os.access(self._config_path, os.W_OK):
105 return True
106 return os.access(os.path.dirname(self._config_path), os.W_OK | os.X_OK)
107
108 def _read(self):
109 for section, keys in self._bindings.items():
110 for key, binding in keys.items():
111 binding.option.value = self._config.get(section, key, fallback=binding.default)
112
113 def _write(self):
114 for section, keys in self._bindings.items():
115 if not self._config.has_section(section):
116 self._config.add_section(section)
117 for key, binding in keys.items():
118 value = binding.option.value
119 if value is None:
120 self._config.remove_option(section, key)
121 else:
122 self._config.set(section, key, value)
123
124 try:
125 with open(self._config_path + '_', 'w') as file:
126 self._config.write(file)
127 except OSError as e:
128 helpers.show_message(e, Gtk.MessageType.ERROR)
129
130 def on_destroy(self, *args):
131 Gtk.main_quit()
132
133 def on_apply_clicked(self, *args):
134 self._write()
135
136 def on_reset_clicked(self, *args):
137 self._read()
138
139 def on_close_clicked(self, *args):
140 self.destroy()
+0
-104
gtk_greeter_settings/IndicatorChooserDialog.py less more
0
1 from glob import iglob
2 import os
3 import sys
4
5 from gi.repository import Gtk
6 from gtk_greeter_settings import helpers
7
8
9 __all__ = ['IndicatorChooserDialog']
10
11
12 class IndicatorChooserDialog(Gtk.Dialog):
13
14 __gtype_name__ = 'IndicatorChooserDialog'
15
16 BUILDER_WIDGETS = ('short_choice', 'short_value', 'short_model',
17 'path_choice', 'path_value',
18 'add_button', 'ok_button', 'infobar', 'message')
19
20 def __new__(cls):
21 builder = Gtk.Builder()
22 builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__))
23 window = builder.get_object('indicator_chooser_dialog')
24 window._builder = builder
25 window.__dict__.update(('_' + w, builder.get_object(w))
26 for w in cls.BUILDER_WIDGETS)
27
28 builder.connect_signals(window)
29 window._init_window()
30 return window
31
32 def _init_window(self):
33 self._add_callback = None
34 self._check_callback = None
35
36 for path in iglob(os.path.join(sys.prefix, 'share', 'unity', 'indicators', '*')):
37 name = os.path.basename(path)
38 parts = name.rsplit('.', maxsplit=1)
39 if len(parts) == 2 and parts[0] == 'com.canonical.indicator':
40 name = parts[1]
41 self._short_model.append((name,))
42
43 for path in iglob(os.path.join(sys.prefix, 'lib', 'indicators3', '7', '*.so')):
44 self._short_model.append((os.path.basename(path),))
45
46 def _get_current_value(self):
47 if self._short_choice.props.active:
48 return self._short_value.props.text
49 else:
50 return self._path_value.get_filename()
51
52 def _update_state(self, force_valid=None):
53 message = None
54 if force_valid is None:
55 valid = False
56 if self._check_callback is not None:
57 check = self._check_callback(self._get_current_value())
58 if isinstance(check, str):
59 message = check
60 else:
61 valid = bool(check)
62 else:
63 valid = True
64 else:
65 valid = force_valid
66
67 self._infobar.props.visible = message is not None
68 if message is not None:
69 self._message.props.label = message
70
71 self._ok_button.props.sensitive = valid
72 self._add_button.props.sensitive = valid
73
74 def on_short_value_changed(self, widget):
75 if not self._short_choice.props.active:
76 self._short_choice.props.active = True
77 else:
78 self._update_state()
79
80 def on_path_value_changed(self, widget):
81 self._path_choice.props.active = True
82 self._update_state()
83
84 def on_short_choice_toggled(self, widget):
85 self._update_state()
86
87 def on_add_clicked(self, *args):
88 value = self._get_current_value()
89 if value:
90 self._add_callback(value)
91 self._update_state(False)
92
93 def get_indicator(self, check_callback=None, add_callback=None):
94 self._check_callback = check_callback
95 self._add_callback = add_callback
96 self._add_button.props.visible = add_callback is not None
97
98 self._update_state()
99 response = self.run()
100 self.hide()
101 if response == Gtk.ResponseType.OK:
102 return self._get_current_value()
103 return None
+0
-496
gtk_greeter_settings/OptionEntry.py less more
0
1 from _functools import partial
2 from builtins import isinstance
3 from collections import namedtuple, OrderedDict
4 from itertools import product
5 from locale import gettext as _
6 import time
7
8 from gi.repository import Gtk, Gdk, GObject
9
10 from gtk_greeter_settings import IndicatorChooserDialog
11
12
13 __all__ = ['BaseEntry', 'BooleanEntry', 'StringEntry', 'ClockFormatEntry',
14 'BackgroundEntry', 'IconEntry', 'IndicatorsEntry', 'PositionEntry']
15
16
17 class GtkSignalBlocker:
18 def __init__(self, widget, handler):
19 if hasattr(handler, '__call__'):
20 self._block = partial(widget.handler_block_by_func, handler)
21 self._unblock = partial(widget.handler_unblock_by_func, handler)
22 elif isinstance(handler, int):
23 self._block = partial(widget.handler_block, handler)
24 self._unblock = partial(widget.handler_unblock, handler)
25 else:
26 self._block = None
27 self._unblock = None
28
29 def __enter__(self):
30 if self._block:
31 self._block()
32 return self
33
34 def __exit__(self, *args):
35 if self._unblock:
36 self._unblock()
37 return False
38
39
40 class BaseEntry:
41
42 @property
43 def value(self):
44 return self._get_value()
45
46 @value.setter
47 def value(self, value):
48 return self._set_value(value)
49
50 def __repr__(self):
51 try:
52 value = self._get_value()
53 except NotImplemented:
54 value = '<Undefined>'
55 return '%s(%s)' % (self.__class__.__name__, value)
56
57 def _get_value(self):
58 raise NotImplementedError(self.__class__)
59
60 def _set_value(self, value):
61 raise NotImplementedError(self.__class__)
62
63
64 class BooleanEntry(BaseEntry):
65
66 def __init__(self, widgets):
67 self._widget = widgets['value']
68
69 def _get_value(self):
70 return 'true' if self._widget.props.active else 'false'
71
72 def _set_value(self, value):
73 self._widget.props.active = value and value.lower() not in ('false', 'no', '0')
74
75
76 class StringEntry(BaseEntry):
77
78 def __init__(self, widgets):
79 self._widget = widgets['value']
80 if isinstance(self._widget, Gtk.ComboBox):
81 self._widget = self._widget.get_child()
82
83 def _get_value(self):
84 return self._widget.props.text
85
86 def _set_value(self, value):
87 self._widget.props.text = value or ''
88
89
90 class ClockFormatEntry(StringEntry):
91
92 def __init__(self, widgets):
93 super().__init__(widgets)
94 self._preview = widgets['preview']
95 self._widget.connect('changed', self._on_changed)
96 GObject.timeout_add_seconds(1, self._on_changed, self._widget)
97
98 def _on_changed(self, entry):
99 self._preview.props.label = time.strftime(self._widget.props.text)
100 return True
101
102
103 class BackgroundEntry(BaseEntry):
104
105 def __init__(self, widgets):
106 self._image_choice = widgets['image_choice']
107 self._color_choice = widgets['color_choice']
108 self._image_value = widgets['image_value']
109 self._color_value = widgets['color_value']
110
111 def _get_value(self):
112 if self._image_choice.props.active:
113 return self._image_value.get_filename()
114 else:
115 return self._color_value.props.color.to_string()
116
117 def _set_value(self, value):
118 if value is None:
119 value = ''
120
121 color = Gdk.color_parse(value)
122
123 self._color_choice.props.active = color is not None
124 self._image_choice.props.active = color is None
125
126 if color is not None:
127 self._color_value.props.color = color
128 self._image_value.unselect_all()
129 else:
130 if value:
131 self._image_value.select_filename(value)
132 else:
133 self._image_value.unselect_all()
134
135
136 class FontEntry(BaseEntry):
137
138 def __init__(self, widgets):
139 self._widget = widgets['value']
140
141 def _get_value(self):
142 return self._widget.get_font_name()
143
144 def _set_value(self, value):
145 self._widget.props.font_name = value or ''
146
147
148 class IconEntry(BaseEntry):
149
150 def __init__(self, widgets):
151 self._image = widgets['image']
152 self._button = widgets['button']
153
154 def _get_value(self):
155 pass
156
157 def _set_value(self, value):
158 pass
159
160
161 class IndicatorsEntry(BaseEntry):
162 NAMES_DELIMITER = ';'
163 # It's the only one place where model columns order defined
164 ModelRow = namedtuple('ModelRow', ('enabled', 'name', 'builtin', 'external'))
165
166 def __init__(self, widgets):
167 # Map ModelRow fields to self._model_[field-name] = [field-index]
168 for i, field in enumerate(IndicatorsEntry.ModelRow._fields):
169 setattr(self, '_model_' + field, i)
170
171 self._use = widgets['use']
172 self._toolbar = widgets['toolbar']
173 self._treeview = widgets['treeview']
174 self._selection = widgets['selection']
175 self._state_renderer = widgets['state_renderer']
176 self._name_column = widgets['name_column']
177 self._name_renderer = widgets['name_renderer']
178 self._add = widgets['add']
179 self._remove = widgets['remove']
180 self._up = widgets['up']
181 self._down = widgets['down']
182 self._model = widgets['model']
183
184 self._initial_items = OrderedDict((item.name, item)
185 for item in map(self.ModelRow._make, self._model))
186 self._indicators_dialog = None
187
188 self._treeview.connect("key-press-event", self._on_key_press)
189 self._selection.connect("changed", self._on_selection_changed)
190 self._state_renderer.connect("toggled", self._on_state_toggled)
191 self._name_renderer.connect("edited", self._on_name_edited)
192 self._add.connect("clicked", self._on_add)
193 self._remove.connect("clicked", self._on_remove)
194 self._up.connect("clicked", self._on_up)
195 self._down.connect("clicked", self._on_down)
196 self._use.connect("notify::active", self._on_use_toggled)
197
198 def _get_value(self):
199 if self._use.props.active:
200 return self.NAMES_DELIMITER.join(item.name for item in map(self.ModelRow._make, self._model)
201 if (item.builtin and item.enabled) or item.external)
202 else:
203 return None
204
205 def _set_value(self, value):
206 with GtkSignalBlocker(self._use, self._on_use_toggled):
207 self._use.set_active(value is not None)
208
209 self._model.clear()
210 last_options = self._initial_items.copy()
211 if value:
212 for name in value.split(self.NAMES_DELIMITER):
213 try:
214 self._model.append(last_options.pop(name)._replace(enabled=True))
215 except KeyError:
216 self._model.append(self.ModelRow(name=name, external=True,
217 builtin=False, enabled=False))
218 for i in last_options.values():
219 self._model.append(i)
220
221 self._toolbar.props.sensitive = value is not None
222 self._treeview.props.sensitive = value is not None
223
224 self._selection.select_path(0)
225
226 def _remove_selection(self):
227 model, rowiter = self._selection.get_selected()
228 if rowiter:
229 previter = model.iter_previous(rowiter)
230 model.remove(rowiter)
231 if previter:
232 self._selection.select_iter(previter)
233
234 def _move_selection(self, move_up):
235 model, rowiter = self._selection.get_selected()
236 if rowiter:
237 if move_up:
238 model.swap(rowiter, model.iter_previous(rowiter))
239 else:
240 model.swap(rowiter, model.iter_next(rowiter))
241 self._on_selection_changed(self._selection)
242
243 def _check_indicator(self, name):
244 ''' Returns True if name is valid, error message or False otherwise '''
245 if not name:
246 return False
247 else:
248 if any(row[self._model_name] == name for row in self._model):
249 return _('Indicator "%s" is already in the list') % name
250 return True
251
252 def _add_indicator(self, name):
253 if name:
254 rowiter = self._model.append(self.ModelRow(name=name, external=True,
255 builtin=False, enabled=False))
256 self._selection.select_iter(rowiter)
257 self._treeview.grab_focus()
258
259 def _on_key_press(self, treeview, event):
260 if Gdk.keyval_name(event.keyval) == 'Delete':
261 self._remove_selection()
262 elif Gdk.keyval_name(event.keyval) == 'F2':
263 model, rowiter = self._selection.get_selected()
264 if rowiter and model[rowiter][self._model_external]:
265 self._treeview.set_cursor(model.get_path(rowiter), self._name_column, True)
266 else:
267 return False
268 return True
269
270 def _on_state_toggled(self, renderer, path):
271 self._model[path][self._model_enabled] = not self._model[path][self._model_enabled]
272
273 def _on_name_edited(self, renderer, path, name):
274 check = self._check_indicator(name)
275 if not isinstance(check, str) and check:
276 self._model[path][self._model_name] = name
277
278 def _on_use_toggled(self, *args):
279 if self._use.props.active:
280 self._set_value([])
281 else:
282 self._set_value(None)
283
284 def _on_selection_changed(self, selection):
285 model, rowiter = selection.get_selected()
286 self._remove.props.sensitive = (rowiter is not None) and self.ModelRow._make(model[rowiter]).external
287 self._down.props.sensitive = (rowiter is not None) and model.iter_next(rowiter) is not None
288 self._up.props.sensitive = (rowiter is not None) and model.iter_previous(rowiter) is not None
289 if rowiter is not None:
290 self._treeview.scroll_to_cell(model.get_path(rowiter))
291
292 def _on_add(self, *args):
293 if not self._indicators_dialog:
294 self._indicators_dialog = IndicatorChooserDialog.IndicatorChooserDialog()
295 name = self._indicators_dialog.get_indicator(check_callback=self._check_indicator,
296 add_callback=self._add_indicator)
297 if name:
298 self._add_indicator(name)
299
300 def _on_remove(self, *args):
301 self._remove_selection()
302
303 def _on_up(self, *args):
304 self._move_selection(move_up=True)
305
306 def _on_down(self, *args):
307 self._move_selection(move_up=False)
308
309
310 class PositionEntry(BaseEntry):
311
312 class Dimension:
313 def __init__(self, name, widgets, anchors, on_changed):
314 self.__dict__.update(('_%s' % w, widgets['%s_%s' % (name, w)])
315 for w in ('value', 'percents', 'mirror', 'adjustment'))
316 self._name = name
317 self._on_changed = on_changed
318 self._anchor = ''
319
320 self._percents.connect('toggled', self._on_percents_toggled)
321 self._mirror.connect('toggled', self._on_mirror_toggled)
322 self._adjustment.connect('value-changed', self._on_value_changed)
323
324 for (x, y), widget in anchors.items():
325 widget.connect('toggled', self._on_anchor_toggled, self,
326 x if self._name == 'x' else y)
327
328 @property
329 def value(self):
330 return '%s%d%s,%s' % ('-' if self._mirror.props.active else '',
331 int(self._value.props.value),
332 '%' if self._percents.props.active else '',
333 'start' if self._name == 'x' else 'end')
334
335 @value.setter
336 def value(self, dim_value):
337 value, _, anchor = dim_value.partition(',')
338
339 percents = value and value[-1] == '%'
340 if percents:
341 value = value[:-1]
342
343 try:
344 p = int(value)
345 except ValueError:
346 p = 0
347
348 negative = (p < 0) or (p == 0 and value and value[0] == '-')
349
350 if not anchor or anchor not in ('start', 'center', 'end'):
351 if negative:
352 anchor = 'end'
353 else:
354 anchor = 'start'
355 self._anchor = anchor
356
357 with GtkSignalBlocker(self._percents, self._on_percents_toggled):
358 self._percents.props.active = percents
359 self._adjustment.props.upper = 100 if self._percents.props.active else 10000
360 with GtkSignalBlocker(self._mirror, self._on_mirror_toggled):
361 self._mirror.props.active = negative
362 with GtkSignalBlocker(self._adjustment, self._on_value_changed):
363 self._adjustment.props.value = -p if negative else p
364
365 @property
366 def anchor(self):
367 return self._anchor
368
369 def get_scaled_position(self, screen, window, scale):
370 screen_size = screen[0] if self._name == 'x' else screen[1]
371 window_size = window[0] if self._name == 'x' else window[1]
372
373 p = int(self._adjustment.props.value)
374 if self._percents.props.active:
375 p = screen_size * p / 100
376 else:
377 p *= scale
378
379 if self._mirror.props.active:
380 p = screen_size - p
381
382 if self._anchor == 'center':
383 p -= window_size / 2
384 elif self._anchor == 'end':
385 p -= window_size
386
387 p = int(p)
388
389 if p + window_size > screen_size:
390 p = screen_size - window_size
391 if p < 0:
392 p = 0
393
394 return p
395
396 def _on_value_changed(self, widget):
397 self._on_changed(self)
398
399 def _on_percents_toggled(self, toggle):
400 self._adjustment.props.upper = 100 if toggle.props.active else 10000
401 self._on_changed(self)
402
403 def _on_mirror_toggled(self, toggle):
404 self._on_changed(self)
405
406 def _on_anchor_toggled(self, toggle, dimension, anchor):
407 if dimension == self and toggle.props.active and anchor != self._anchor:
408 self._anchor = anchor
409 self._on_changed(self)
410
411 REAL_WINDOW_SIZE = 430, 210
412
413 def __init__(self, widgets):
414 self._screen = widgets['screen']
415 self._window = widgets['window']
416 self._screen_pos = (0, 0)
417 self._screen_size = (0, 0)
418
419 self._anchors = {(x, y): widgets['base_%s_%s' % (x, y)]
420 for x, y in product(('start', 'center', 'end'), repeat=2)}
421
422 self._screen.connect('size-allocate', self._on_resize)
423 self._screen.connect('draw', self._on_draw_screen_border)
424
425 self._x = PositionEntry.Dimension('x', widgets, self._anchors, self._on_dimension_changed)
426 self._y = PositionEntry.Dimension('y', widgets, self._anchors, self._on_dimension_changed)
427
428 def _on_draw_screen_border(self, widget, cr):
429 width, height = self._screen_size
430 x, y = self._screen_pos
431 line_width = 2
432 width -= line_width
433 height -= line_width
434
435 x += line_width / 2
436 y += line_width / 2
437 cr.set_source_rgba(0.2, 0.1, 0.2, 0.8)
438 cr.set_line_width(line_width)
439
440 cr.move_to(x, y)
441 cr.line_to(x + width, y)
442 cr.line_to(x + width, y + height)
443 cr.line_to(x, y + height)
444 cr.line_to(x, y - line_width / 2)
445 cr.stroke_preserve()
446
447 return False
448
449 def _get_value(self):
450 return self._x.value + ' ' + self._y.value
451
452 def _set_value(self, value):
453 if value:
454 x, _, y = value.partition(' ')
455 self._x.value = x
456 self._y.value = y or x
457 self._anchors[self._x.anchor, self._y.anchor].props.active = True
458 self._update_layout()
459
460 def _update_layout(self):
461 screen = self._screen.get_toplevel().get_screen()
462 geometry = screen.get_monitor_geometry(screen.get_primary_monitor())
463 window_allocation = self._window.get_allocation()
464 window_size = window_allocation.width, window_allocation.height
465 scale = self._screen_size[0] / geometry.width
466
467 x = self._screen_pos[0] + self._x.get_scaled_position(self._screen_size, window_size, scale)
468 y = self._screen_pos[1] + self._y.get_scaled_position(self._screen_size, window_size, scale)
469
470 self._screen.move(self._window, x, y)
471 self._screen.check_resize()
472
473 def _on_resize(self, widget, allocation):
474 screen = self._screen.get_toplevel().get_screen()
475 geometry = screen.get_monitor_geometry(screen.get_primary_monitor())
476 screen_scale = geometry.height / geometry.width
477
478 width = allocation.width
479 height = int(width * screen_scale)
480
481 if height > allocation.height:
482 height = allocation.height
483 width = min(width, int(height / screen_scale))
484 self._screen_pos = int((allocation.width - width) / 2), 0
485 self._screen_size = (width, height)
486
487 with GtkSignalBlocker(self._screen, self._on_resize):
488 scale = width / geometry.width
489 self._window.set_size_request(PositionEntry.REAL_WINDOW_SIZE[0] * scale,
490 PositionEntry.REAL_WINDOW_SIZE[1] * scale)
491 self._update_layout()
492
493 def _on_dimension_changed(self, dimension):
494 with GtkSignalBlocker(self._screen, self._on_resize):
495 self._update_layout()
+0
-18
gtk_greeter_settings/__init__.py less more
0 #!/usr/bin/env python3
1
2 from gi.repository import Gtk
3
4
5 def main():
6 from gtk_greeter_settings import GtkGreeterSettingsWindow
7 window = GtkGreeterSettingsWindow.GtkGreeterSettingsWindow()
8 window.show()
9 Gtk.main()
10
11
12 if __name__ == "__main__":
13 import sys, os
14 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
15 window = __import__('GtkGreeterSettingsWindow').GtkGreeterSettingsWindow()
16 window.show()
17 Gtk.main()
+0
-33
gtk_greeter_settings/helpers.py less more
0
1 import os
2 from gi.repository import Gtk
3
4
5 __license__ = 'GPL-3'
6 __version__ = '0.1'
7 __data_directory__ = '../data/'
8 __config_path__ = 'lightdm-gtk-greeter.conf'
9
10
11 try:
12 from _override_config import *
13 except ImportError:
14 pass
15
16
17 __all__ = ['get_data_path', 'get_config_path', 'show_message']
18
19
20 def get_data_path(*parts):
21 return os.path.abspath(os.path.join(os.path.dirname(__file__),
22 __data_directory__, *parts))
23
24
25 def get_config_path():
26 return os.path.abspath(__config_path__)
27
28
29 def show_message(**kwargs):
30 dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.CLOSE, **kwargs)
31 dialog.run()
32 dialog.destroy()
0
1 from collections import namedtuple
2 import configparser
3 from glob import iglob
4 from locale import gettext as _
5 import os
6 import sys
7
8 from gi.repository import Gtk
9
10 from lightdm_gtk_greeter_settings import OptionEntry
11 from lightdm_gtk_greeter_settings import helpers
12
13
14 __all__ = ['GtkGreeterSettingsWindow']
15
16
17 BindingValue = namedtuple('BindingValue', ('option', 'default'))
18
19
20 OPTIONS_BINDINGS = \
21 {
22 'greeter':
23 {
24 # key: class, base widgets name, default value
25
26 # Theme
27 'theme-name': (OptionEntry.StringEntry, 'gtk_theme', None),
28 'icon-theme-name': (OptionEntry.StringEntry, 'icons_theme', None),
29 'font-name': (OptionEntry.FontEntry, 'font', 'Sans 10'),
30 'xft-antialias': (OptionEntry.BooleanEntry, 'antialias', False),
31 'xft-dpi': (OptionEntry.StringEntry, 'dpi', None),
32 'background': (OptionEntry.BackgroundEntry, 'background', None),
33 'default-user-image': (OptionEntry.IconEntry, 'user_image', '#avatar-default'),
34 # Panel
35 'show-clock': (OptionEntry.BooleanEntry, 'show_clock', False),
36 'clock-format': (OptionEntry.ClockFormatEntry, 'clock_format', '%a, %H:%M'),
37 'show-indicators': (OptionEntry.IndicatorsEntry, 'indicators', None),
38 # Position
39 'position': (OptionEntry.PositionEntry, 'position', '50%,center'),
40 }
41 }
42
43
44 class BuilderWrapper:
45 def __init__(self, builder, base):
46 self._builder = builder
47 self._base = base
48
49 def __getitem__(self, key):
50 return self._builder.get_object('%s_%s' % (self._base, key))
51
52
53 class GtkGreeterSettingsWindow(Gtk.Window):
54
55 __gtype_name__ = 'GtkGreeterSettingsWindow'
56
57 BUILDER_WIDGETS = ('dialog_buttons', 'apply_button',
58 'gtk_theme_values', 'icons_theme_values')
59
60 def __new__(cls):
61 builder = Gtk.Builder()
62 builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__))
63 window = builder.get_object("settings_window")
64 window._builder = builder
65 window.__dict__.update(('_' + w, builder.get_object(w))
66 for w in cls.BUILDER_WIDGETS)
67 builder.connect_signals(window)
68 window._init_window()
69 return window
70
71 def _init_window(self):
72 self._bindings = {section: {key: BindingValue(cls(BuilderWrapper(self._builder, base_name)), default)
73 for key, (cls, base_name, default) in keys.items()}
74 for section, keys in OPTIONS_BINDINGS.items()}
75
76 self._config_path = helpers.get_config_path()
77 if not self._has_access_to_write(self._config_path):
78 self._apply_button.props.sensitive = False
79
80 helpers.show_message(text=_('No permissions to save configuration'),
81 secondary_text=_(
82 'It seems that you don\'t have permissions to write to file:\n\
83 %s\n\nTry to run this program using "sudo" or "pkexec"') % self._config_path,
84 message_type=Gtk.MessageType.WARNING)
85
86 self._config = configparser.RawConfigParser(strict=False, allow_no_value=True)
87
88 try:
89 if not self._config.read(self._config_path):
90 helpers.show_message(text=_('Failed to read configuration file: %s') % self._config_path,
91 message_type=Gtk.MessageType.ERROR)
92 except (configparser.DuplicateSectionError, configparser.MissingSectionHeaderError):
93 pass
94
95 for theme in iglob(os.path.join(sys.prefix, 'share', 'themes', '*', 'gtk-3.0')):
96 self._gtk_theme_values.append_text(theme.split(os.path.sep)[-2])
97
98 for theme in iglob(os.path.join(sys.prefix, 'share', 'icons', '*', 'index.theme')):
99 self._icons_theme_values.append_text(theme.split(os.path.sep)[-2])
100
101 self._read()
102
103 def _has_access_to_write(self, path):
104 if os.path.exists(path) and os.access(self._config_path, os.W_OK):
105 return True
106 return os.access(os.path.dirname(self._config_path), os.W_OK | os.X_OK)
107
108 def _read(self):
109 for section, keys in self._bindings.items():
110 for key, binding in keys.items():
111 binding.option.value = self._config.get(section, key, fallback=binding.default)
112
113 def _write(self):
114 for section, keys in self._bindings.items():
115 if not self._config.has_section(section):
116 self._config.add_section(section)
117 for key, binding in keys.items():
118 value = binding.option.value
119 if value is None:
120 self._config.remove_option(section, key)
121 else:
122 self._config.set(section, key, value)
123
124 try:
125 with open(self._config_path + '_', 'w') as file:
126 self._config.write(file)
127 except OSError as e:
128 helpers.show_message(e, Gtk.MessageType.ERROR)
129
130 def on_destroy(self, *args):
131 Gtk.main_quit()
132
133 def on_apply_clicked(self, *args):
134 self._write()
135
136 def on_reset_clicked(self, *args):
137 self._read()
138
139 def on_close_clicked(self, *args):
140 self.destroy()
0
1 from glob import iglob
2 import os
3 import sys
4
5 from gi.repository import Gtk
6 from lightdm_gtk_greeter_settings import helpers
7
8
9 __all__ = ['IndicatorChooserDialog']
10
11
12 class IndicatorChooserDialog(Gtk.Dialog):
13
14 __gtype_name__ = 'IndicatorChooserDialog'
15
16 BUILDER_WIDGETS = ('short_choice', 'short_value', 'short_model',
17 'path_choice', 'path_value',
18 'add_button', 'ok_button', 'infobar', 'message')
19
20 def __new__(cls):
21 builder = Gtk.Builder()
22 builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__))
23 window = builder.get_object('indicator_chooser_dialog')
24 window._builder = builder
25 window.__dict__.update(('_' + w, builder.get_object(w))
26 for w in cls.BUILDER_WIDGETS)
27
28 builder.connect_signals(window)
29 window._init_window()
30 return window
31
32 def _init_window(self):
33 self._add_callback = None
34 self._check_callback = None
35
36 for path in iglob(os.path.join(sys.prefix, 'share', 'unity', 'indicators', '*')):
37 name = os.path.basename(path)
38 parts = name.rsplit('.', maxsplit=1)
39 if len(parts) == 2 and parts[0] == 'com.canonical.indicator':
40 name = parts[1]
41 self._short_model.append((name,))
42
43 for path in iglob(os.path.join(sys.prefix, 'lib', 'indicators3', '7', '*.so')):
44 self._short_model.append((os.path.basename(path),))
45
46 def _get_current_value(self):
47 if self._short_choice.props.active:
48 return self._short_value.props.text
49 else:
50 return self._path_value.get_filename()
51
52 def _update_state(self, force_valid=None):
53 message = None
54 if force_valid is None:
55 valid = False
56 if self._check_callback is not None:
57 check = self._check_callback(self._get_current_value())
58 if isinstance(check, str):
59 message = check
60 else:
61 valid = bool(check)
62 else:
63 valid = True
64 else:
65 valid = force_valid
66
67 self._infobar.props.visible = message is not None
68 if message is not None:
69 self._message.props.label = message
70
71 self._ok_button.props.sensitive = valid
72 self._add_button.props.sensitive = valid
73
74 def on_short_value_changed(self, widget):
75 if not self._short_choice.props.active:
76 self._short_choice.props.active = True
77 else:
78 self._update_state()
79
80 def on_path_value_changed(self, widget):
81 self._path_choice.props.active = True
82 self._update_state()
83
84 def on_short_choice_toggled(self, widget):
85 self._update_state()
86
87 def on_add_clicked(self, *args):
88 value = self._get_current_value()
89 if value:
90 self._add_callback(value)
91 self._update_state(False)
92
93 def get_indicator(self, check_callback=None, add_callback=None):
94 self._check_callback = check_callback
95 self._add_callback = add_callback
96 self._add_button.props.visible = add_callback is not None
97
98 self._update_state()
99 response = self.run()
100 self.hide()
101 if response == Gtk.ResponseType.OK:
102 return self._get_current_value()
103 return None
0
1 from _functools import partial
2 from builtins import isinstance
3 from collections import namedtuple, OrderedDict
4 from itertools import product
5 from locale import gettext as _
6 import time
7
8 from gi.repository import Gtk, Gdk, GObject
9
10 from lightdm_gtk_greeter_settings import IndicatorChooserDialog
11
12
13 __all__ = ['BaseEntry', 'BooleanEntry', 'StringEntry', 'ClockFormatEntry',
14 'BackgroundEntry', 'IconEntry', 'IndicatorsEntry', 'PositionEntry']
15
16
17 class GtkSignalBlocker:
18 def __init__(self, widget, handler):
19 if hasattr(handler, '__call__'):
20 self._block = partial(widget.handler_block_by_func, handler)
21 self._unblock = partial(widget.handler_unblock_by_func, handler)
22 elif isinstance(handler, int):
23 self._block = partial(widget.handler_block, handler)
24 self._unblock = partial(widget.handler_unblock, handler)
25 else:
26 self._block = None
27 self._unblock = None
28
29 def __enter__(self):
30 if self._block:
31 self._block()
32 return self
33
34 def __exit__(self, *args):
35 if self._unblock:
36 self._unblock()
37 return False
38
39
40 class BaseEntry:
41
42 @property
43 def value(self):
44 return self._get_value()
45
46 @value.setter
47 def value(self, value):
48 return self._set_value(value)
49
50 def __repr__(self):
51 try:
52 value = self._get_value()
53 except NotImplemented:
54 value = '<Undefined>'
55 return '%s(%s)' % (self.__class__.__name__, value)
56
57 def _get_value(self):
58 raise NotImplementedError(self.__class__)
59
60 def _set_value(self, value):
61 raise NotImplementedError(self.__class__)
62
63
64 class BooleanEntry(BaseEntry):
65
66 def __init__(self, widgets):
67 self._widget = widgets['value']
68
69 def _get_value(self):
70 return 'true' if self._widget.props.active else 'false'
71
72 def _set_value(self, value):
73 self._widget.props.active = value and value.lower() not in ('false', 'no', '0')
74
75
76 class StringEntry(BaseEntry):
77
78 def __init__(self, widgets):
79 self._widget = widgets['value']
80 if isinstance(self._widget, Gtk.ComboBox):
81 self._widget = self._widget.get_child()
82
83 def _get_value(self):
84 return self._widget.props.text
85
86 def _set_value(self, value):
87 self._widget.props.text = value or ''
88
89
90 class ClockFormatEntry(StringEntry):
91
92 def __init__(self, widgets):
93 super().__init__(widgets)
94 self._preview = widgets['preview']
95 self._widget.connect('changed', self._on_changed)
96 GObject.timeout_add_seconds(1, self._on_changed, self._widget)
97
98 def _on_changed(self, entry):
99 self._preview.props.label = time.strftime(self._widget.props.text)
100 return True
101
102
103 class BackgroundEntry(BaseEntry):
104
105 def __init__(self, widgets):
106 self._image_choice = widgets['image_choice']
107 self._color_choice = widgets['color_choice']
108 self._image_value = widgets['image_value']
109 self._color_value = widgets['color_value']
110
111 def _get_value(self):
112 if self._image_choice.props.active:
113 return self._image_value.get_filename()
114 else:
115 return self._color_value.props.color.to_string()
116
117 def _set_value(self, value):
118 if value is None:
119 value = ''
120
121 color = Gdk.color_parse(value)
122
123 self._color_choice.props.active = color is not None
124 self._image_choice.props.active = color is None
125
126 if color is not None:
127 self._color_value.props.color = color
128 self._image_value.unselect_all()
129 else:
130 if value:
131 self._image_value.select_filename(value)
132 else:
133 self._image_value.unselect_all()
134
135
136 class FontEntry(BaseEntry):
137
138 def __init__(self, widgets):
139 self._widget = widgets['value']
140
141 def _get_value(self):
142 return self._widget.get_font_name()
143
144 def _set_value(self, value):
145 self._widget.props.font_name = value or ''
146
147
148 class IconEntry(BaseEntry):
149
150 def __init__(self, widgets):
151 self._image = widgets['image']
152 self._button = widgets['button']
153
154 def _get_value(self):
155 pass
156
157 def _set_value(self, value):
158 pass
159
160
161 class IndicatorsEntry(BaseEntry):
162 NAMES_DELIMITER = ';'
163 # It's the only one place where model columns order defined
164 ModelRow = namedtuple('ModelRow', ('enabled', 'name', 'builtin', 'external'))
165
166 def __init__(self, widgets):
167 # Map ModelRow fields to self._model_[field-name] = [field-index]
168 for i, field in enumerate(IndicatorsEntry.ModelRow._fields):
169 setattr(self, '_model_' + field, i)
170
171 self._use = widgets['use']
172 self._toolbar = widgets['toolbar']
173 self._treeview = widgets['treeview']
174 self._selection = widgets['selection']
175 self._state_renderer = widgets['state_renderer']
176 self._name_column = widgets['name_column']
177 self._name_renderer = widgets['name_renderer']
178 self._add = widgets['add']
179 self._remove = widgets['remove']
180 self._up = widgets['up']
181 self._down = widgets['down']
182 self._model = widgets['model']
183
184 self._initial_items = OrderedDict((item.name, item)
185 for item in map(self.ModelRow._make, self._model))
186 self._indicators_dialog = None
187
188 self._treeview.connect("key-press-event", self._on_key_press)
189 self._selection.connect("changed", self._on_selection_changed)
190 self._state_renderer.connect("toggled", self._on_state_toggled)
191 self._name_renderer.connect("edited", self._on_name_edited)
192 self._add.connect("clicked", self._on_add)
193 self._remove.connect("clicked", self._on_remove)
194 self._up.connect("clicked", self._on_up)
195 self._down.connect("clicked", self._on_down)
196 self._use.connect("notify::active", self._on_use_toggled)
197
198 def _get_value(self):
199 if self._use.props.active:
200 return self.NAMES_DELIMITER.join(item.name for item in map(self.ModelRow._make, self._model)
201 if (item.builtin and item.enabled) or item.external)
202 else:
203 return None
204
205 def _set_value(self, value):
206 with GtkSignalBlocker(self._use, self._on_use_toggled):
207 self._use.set_active(value is not None)
208
209 self._model.clear()
210 last_options = self._initial_items.copy()
211 if value:
212 for name in value.split(self.NAMES_DELIMITER):
213 try:
214 self._model.append(last_options.pop(name)._replace(enabled=True))
215 except KeyError:
216 self._model.append(self.ModelRow(name=name, external=True,
217 builtin=False, enabled=False))
218 for i in last_options.values():
219 self._model.append(i)
220
221 self._toolbar.props.sensitive = value is not None
222 self._treeview.props.sensitive = value is not None
223
224 self._selection.select_path(0)
225
226 def _remove_selection(self):
227 model, rowiter = self._selection.get_selected()
228 if rowiter:
229 previter = model.iter_previous(rowiter)
230 model.remove(rowiter)
231 if previter:
232 self._selection.select_iter(previter)
233
234 def _move_selection(self, move_up):
235 model, rowiter = self._selection.get_selected()
236 if rowiter:
237 if move_up:
238 model.swap(rowiter, model.iter_previous(rowiter))
239 else:
240 model.swap(rowiter, model.iter_next(rowiter))
241 self._on_selection_changed(self._selection)
242
243 def _check_indicator(self, name):
244 ''' Returns True if name is valid, error message or False otherwise '''
245 if not name:
246 return False
247 else:
248 if any(row[self._model_name] == name for row in self._model):
249 return _('Indicator "%s" is already in the list') % name
250 return True
251
252 def _add_indicator(self, name):
253 if name:
254 rowiter = self._model.append(self.ModelRow(name=name, external=True,
255 builtin=False, enabled=False))
256 self._selection.select_iter(rowiter)
257 self._treeview.grab_focus()
258
259 def _on_key_press(self, treeview, event):
260 if Gdk.keyval_name(event.keyval) == 'Delete':
261 self._remove_selection()
262 elif Gdk.keyval_name(event.keyval) == 'F2':
263 model, rowiter = self._selection.get_selected()
264 if rowiter and model[rowiter][self._model_external]:
265 self._treeview.set_cursor(model.get_path(rowiter), self._name_column, True)
266 else:
267 return False
268 return True
269
270 def _on_state_toggled(self, renderer, path):
271 self._model[path][self._model_enabled] = not self._model[path][self._model_enabled]
272
273 def _on_name_edited(self, renderer, path, name):
274 check = self._check_indicator(name)
275 if not isinstance(check, str) and check:
276 self._model[path][self._model_name] = name
277
278 def _on_use_toggled(self, *args):
279 if self._use.props.active:
280 self._set_value([])
281 else:
282 self._set_value(None)
283
284 def _on_selection_changed(self, selection):
285 model, rowiter = selection.get_selected()
286 self._remove.props.sensitive = (rowiter is not None) and self.ModelRow._make(model[rowiter]).external
287 self._down.props.sensitive = (rowiter is not None) and model.iter_next(rowiter) is not None
288 self._up.props.sensitive = (rowiter is not None) and model.iter_previous(rowiter) is not None
289 if rowiter is not None:
290 self._treeview.scroll_to_cell(model.get_path(rowiter))
291
292 def _on_add(self, *args):
293 if not self._indicators_dialog:
294 self._indicators_dialog = IndicatorChooserDialog.IndicatorChooserDialog()
295 name = self._indicators_dialog.get_indicator(check_callback=self._check_indicator,
296 add_callback=self._add_indicator)
297 if name:
298 self._add_indicator(name)
299
300 def _on_remove(self, *args):
301 self._remove_selection()
302
303 def _on_up(self, *args):
304 self._move_selection(move_up=True)
305
306 def _on_down(self, *args):
307 self._move_selection(move_up=False)
308
309
310 class PositionEntry(BaseEntry):
311
312 class Dimension:
313 def __init__(self, name, widgets, anchors, on_changed):
314 self.__dict__.update(('_%s' % w, widgets['%s_%s' % (name, w)])
315 for w in ('value', 'percents', 'mirror', 'adjustment'))
316 self._name = name
317 self._on_changed = on_changed
318 self._anchor = ''
319
320 self._percents.connect('toggled', self._on_percents_toggled)
321 self._mirror.connect('toggled', self._on_mirror_toggled)
322 self._adjustment.connect('value-changed', self._on_value_changed)
323
324 for (x, y), widget in anchors.items():
325 widget.connect('toggled', self._on_anchor_toggled, self,
326 x if self._name == 'x' else y)
327
328 @property
329 def value(self):
330 return '%s%d%s,%s' % ('-' if self._mirror.props.active else '',
331 int(self._value.props.value),
332 '%' if self._percents.props.active else '',
333 'start' if self._name == 'x' else 'end')
334
335 @value.setter
336 def value(self, dim_value):
337 value, _, anchor = dim_value.partition(',')
338
339 percents = value and value[-1] == '%'
340 if percents:
341 value = value[:-1]
342
343 try:
344 p = int(value)
345 except ValueError:
346 p = 0
347
348 negative = (p < 0) or (p == 0 and value and value[0] == '-')
349
350 if not anchor or anchor not in ('start', 'center', 'end'):
351 if negative:
352 anchor = 'end'
353 else:
354 anchor = 'start'
355 self._anchor = anchor
356
357 with GtkSignalBlocker(self._percents, self._on_percents_toggled):
358 self._percents.props.active = percents
359 self._adjustment.props.upper = 100 if self._percents.props.active else 10000
360 with GtkSignalBlocker(self._mirror, self._on_mirror_toggled):
361 self._mirror.props.active = negative
362 with GtkSignalBlocker(self._adjustment, self._on_value_changed):
363 self._adjustment.props.value = -p if negative else p
364
365 @property
366 def anchor(self):
367 return self._anchor
368
369 def get_scaled_position(self, screen, window, scale):
370 screen_size = screen[0] if self._name == 'x' else screen[1]
371 window_size = window[0] if self._name == 'x' else window[1]
372
373 p = int(self._adjustment.props.value)
374 if self._percents.props.active:
375 p = screen_size * p / 100
376 else:
377 p *= scale
378
379 if self._mirror.props.active:
380 p = screen_size - p
381
382 if self._anchor == 'center':
383 p -= window_size / 2
384 elif self._anchor == 'end':
385 p -= window_size
386
387 p = int(p)
388
389 if p + window_size > screen_size:
390 p = screen_size - window_size
391 if p < 0:
392 p = 0
393
394 return p
395
396 def _on_value_changed(self, widget):
397 self._on_changed(self)
398
399 def _on_percents_toggled(self, toggle):
400 self._adjustment.props.upper = 100 if toggle.props.active else 10000
401 self._on_changed(self)
402
403 def _on_mirror_toggled(self, toggle):
404 self._on_changed(self)
405
406 def _on_anchor_toggled(self, toggle, dimension, anchor):
407 if dimension == self and toggle.props.active and anchor != self._anchor:
408 self._anchor = anchor
409 self._on_changed(self)
410
411 REAL_WINDOW_SIZE = 430, 210
412
413 def __init__(self, widgets):
414 self._screen = widgets['screen']
415 self._window = widgets['window']
416 self._screen_pos = (0, 0)
417 self._screen_size = (0, 0)
418
419 self._anchors = {(x, y): widgets['base_%s_%s' % (x, y)]
420 for x, y in product(('start', 'center', 'end'), repeat=2)}
421
422 self._screen.connect('size-allocate', self._on_resize)
423 self._screen.connect('draw', self._on_draw_screen_border)
424
425 self._x = PositionEntry.Dimension('x', widgets, self._anchors, self._on_dimension_changed)
426 self._y = PositionEntry.Dimension('y', widgets, self._anchors, self._on_dimension_changed)
427
428 def _on_draw_screen_border(self, widget, cr):
429 width, height = self._screen_size
430 x, y = self._screen_pos
431 line_width = 2
432 width -= line_width
433 height -= line_width
434
435 x += line_width / 2
436 y += line_width / 2
437 cr.set_source_rgba(0.2, 0.1, 0.2, 0.8)
438 cr.set_line_width(line_width)
439
440 cr.move_to(x, y)
441 cr.line_to(x + width, y)
442 cr.line_to(x + width, y + height)
443 cr.line_to(x, y + height)
444 cr.line_to(x, y - line_width / 2)
445 cr.stroke_preserve()
446
447 return False
448
449 def _get_value(self):
450 return self._x.value + ' ' + self._y.value
451
452 def _set_value(self, value):
453 if value:
454 x, _, y = value.partition(' ')
455 self._x.value = x
456 self._y.value = y or x
457 self._anchors[self._x.anchor, self._y.anchor].props.active = True
458 self._update_layout()
459
460 def _update_layout(self):
461 screen = self._screen.get_toplevel().get_screen()
462 geometry = screen.get_monitor_geometry(screen.get_primary_monitor())
463 window_allocation = self._window.get_allocation()
464 window_size = window_allocation.width, window_allocation.height
465 scale = self._screen_size[0] / geometry.width
466
467 x = self._screen_pos[0] + self._x.get_scaled_position(self._screen_size, window_size, scale)
468 y = self._screen_pos[1] + self._y.get_scaled_position(self._screen_size, window_size, scale)
469
470 self._screen.move(self._window, x, y)
471 self._screen.check_resize()
472
473 def _on_resize(self, widget, allocation):
474 screen = self._screen.get_toplevel().get_screen()
475 geometry = screen.get_monitor_geometry(screen.get_primary_monitor())
476 screen_scale = geometry.height / geometry.width
477
478 width = allocation.width
479 height = int(width * screen_scale)
480
481 if height > allocation.height:
482 height = allocation.height
483 width = min(width, int(height / screen_scale))
484 self._screen_pos = int((allocation.width - width) / 2), 0
485 self._screen_size = (width, height)
486
487 with GtkSignalBlocker(self._screen, self._on_resize):
488 scale = width / geometry.width
489 self._window.set_size_request(PositionEntry.REAL_WINDOW_SIZE[0] * scale,
490 PositionEntry.REAL_WINDOW_SIZE[1] * scale)
491 self._update_layout()
492
493 def _on_dimension_changed(self, dimension):
494 with GtkSignalBlocker(self._screen, self._on_resize):
495 self._update_layout()
0 #!/usr/bin/env python3
1
2
3 def main():
4 from gi.repository import Gtk
5 from lightdm_gtk_greeter_settings import GtkGreeterSettingsWindow
6 window = GtkGreeterSettingsWindow.GtkGreeterSettingsWindow()
7 window.show()
8 Gtk.main()
9
10
11 if __name__ == "__main__":
12 import sys, os
13 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
14 main()
0
1 import os
2 from gi.repository import Gtk
3
4
5 __license__ = 'GPL-3'
6 __version__ = '0.1'
7 __data_directory__ = '../data/'
8 __config_path__ = 'lightdm-gtk-greeter.conf'
9
10
11 try:
12 from _override_config import *
13 except ImportError:
14 pass
15
16
17 __all__ = ['get_data_path', 'get_config_path', 'show_message']
18
19
20 def get_data_path(*parts):
21 return os.path.abspath(os.path.join(os.path.dirname(__file__),
22 __data_directory__, *parts))
23
24
25 def get_config_path():
26 return os.path.abspath(__config_path__)
27
28
29 def show_message(**kwargs):
30 dialog = Gtk.MessageDialog(buttons=Gtk.ButtonsType.CLOSE, **kwargs)
31 dialog.run()
32 dialog.destroy()