Codebase list lightdm-gtk-greeter-settings / a91faa0 lightdm_gtk_greeter_settings / GtkGreeterSettingsWindow.py
a91faa0

Tree @a91faa0 (Download .tar.gz)

GtkGreeterSettingsWindow.py @a91faa0

731163b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c5c583
ce0bfe9
88c0636
1c5c583
ce0bfe9
a91faa0
ce0bfe9
1c5c583
768fab3
1c5c583
ce0bfe9
88c0636
 
 
 
 
 
 
 
 
 
 
 
 
ce0bfe9
88c0636
1c5c583
 
ab6f665
d95dec9
1c5c583
88c0636
1c5c583
 
 
 
 
 
88c0636
 
 
1c5c583
 
 
 
768fab3
88c0636
1c5c583
88c0636
1c5c583
 
88c0636
ab6f665
88c0636
 
 
 
 
 
a91faa0
 
 
88c0636
 
 
ab6f665
 
1bfd3ae
ab6f665
 
88c0636
 
ab6f665
a91faa0
 
ab6f665
7be348c
ab6f665
 
 
 
7be348c
564f908
ab6f665
 
 
88c0636
ab6f665
 
6f846b1
ab6f665
 
82dfb26
59fb8a6
a91faa0
88c0636
 
ab6f665
 
 
 
 
1c5c583
1bfd3ae
88c0636
 
1bfd3ae
d95dec9
 
 
 
 
 
 
1c5c583
a13a852
44154bd
1c5c583
 
 
 
 
 
 
f5842f8
1bfd3ae
 
d95dec9
 
 
1bfd3ae
d95dec9
 
1bfd3ae
 
ab6f665
 
 
 
 
 
88c0636
ab6f665
88c0636
1c5c583
 
ab6f665
 
 
 
 
 
 
88c0636
ab6f665
1c5c583
8da10eb
1c5c583
 
 
 
ab6f665
88c0636
 
 
 
 
 
 
 
 
ab6f665
 
 
 
 
 
 
 
 
 
88c0636
ab6f665
 
 
 
 
88c0636
 
 
ab6f665
44154bd
ab6f665
5a404cf
88c0636
5a404cf
88c0636
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44154bd
ab6f665
a91faa0
 
 
 
 
768fab3
 
 
 
ab6f665
a91faa0
 
 
 
 
768fab3
 
 
 
88c0636
 
 
 
 
 
 
 
 
 
 
 
5a404cf
88c0636
 
5a404cf
 
 
 
88c0636
 
 
 
 
 
5a404cf
88c0636
 
5a404cf
 
 
 
88c0636
 
 
 
 
 
 
 
 
 
5a404cf
5ed9846
 
 
 
88c0636
5ed9846
88c0636
5ed9846
 
88c0636
5ed9846
c71bb13
88c0636
44154bd
a91faa0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
768fab3
1c5c583
 
768fab3
1c5c583
 
768fab3
1c5c583
 
768fab3
1c5c583
#!/usr/bin/env python3
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#   LightDM GTK Greeter Settings
#   Copyright (C) 2014 Andrew P. <pan.pav.7c5@gmail.com>
#
#   This program is free software: you can redistribute it and/or modify it
#   under the terms of the GNU General Public License version 3, as published
#   by the Free Software Foundation.
#
#   This program is distributed in the hope that it will be useful, but
#   WITHOUT ANY WARRANTY; without even the implied warranties of
#   MERCHANTABILITY, SATISFACTORY QUALITY, 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, see <http://www.gnu.org/licenses/>.


import collections
import configparser
import os
import shlex
import sys
from glob import iglob
from itertools import chain
from locale import gettext as _

from gi.repository import (
    Gdk,
    Gtk)

from lightdm_gtk_greeter_settings import (
    helpers,
    IndicatorsEntry,
    OptionEntry,
    PositionEntry)
from lightdm_gtk_greeter_settings.helpers import (
    C_,
    string2bool,
    WidgetsWrapper, WidgetsEnum)
from lightdm_gtk_greeter_settings.MonitorsGroup import MonitorsGroup
from lightdm_gtk_greeter_settings.OptionGroup import SimpleGroup


__all__ = ['GtkGreeterSettingsWindow']


InitialValue = collections.namedtuple('InitialValue', ('value', 'enabled'))


class GtkGreeterSettingsWindow(Gtk.Window):

    __gtype_name__ = 'GtkGreeterSettingsWindow'

    class Widgets(WidgetsEnum):
        apply = 'apply_button'
        infobar = 'infobar'

    def __new__(cls):
        builder = Gtk.Builder()
        builder.add_from_file(helpers.get_data_path('%s.ui' % cls.__name__))
        window = builder.get_object('settings_window')
        window.builder = builder
        builder.connect_signals(window)
        window.init_window()
        return window

    builder = None

    entries_setup = {
        ('greeter', 'allow-debugging'): ('changed',),
        ('greeter', 'background'): ('changed',),
        ('greeter', 'default-user-image'): ('changed',),
        ('greeter', 'screensaver-timeout'): ('setup', 'get', 'set'),
        ('greeter', 'theme-name'): ('setup', 'changed'),
        ('greeter', 'icon-theme-name'): ('setup', 'changed'),
        ('greeter', 'keyboard'): ('changed',),
        ('greeter', 'reader'): ('changed',)}

    def init_window(self):
        self._widgets = self.Widgets(builder=self.builder)

        self._multihead_dialog = None
        self._initial_values = {}
        self._changed_entries = None
        self._entries = None
        self._groups = (
            SimpleGroup('greeter', self.builder, {
                # Appearance
                'theme-name': (OptionEntry.StringEntry, ''),
                'icon-theme-name': (OptionEntry.StringEntry, ''),
                'font-name': (OptionEntry.FontEntry, 'Sans 10'),
                'xft-antialias': (OptionEntry.BooleanEntry, 'false'),
                'xft-dpi': (OptionEntry.StringEntry, None),
                'xft-rgba': (OptionEntry.ChoiceEntry, None),
                'xft-hintstyle': (OptionEntry.ChoiceEntry, None),
                'background': (OptionEntry.BackgroundEntry, '#000000'),
                'user-background': (OptionEntry.BooleanEntry, 'true'),
                'hide-user-image': (OptionEntry.InvertedBooleanEntry, 'false'),
                'default-user-image': (OptionEntry.IconEntry, '#avatar-default'),
                # Panel
                'clock-format': (OptionEntry.ClockFormatEntry, '%a, %H:%M'),
                'indicators': (IndicatorsEntry.IndicatorsEntry,
                               '~host;~spacer;~clock;~spacer;~language;~session;~a11y;~power'),
                # Position
                'position': (PositionEntry.PositionEntry, '50%,center'),
                # Misc
                'screensaver-timeout': (OptionEntry.AdjustmentEntry, 60),
                'keyboard': (OptionEntry.StringPathEntry, None),
                'reader': (OptionEntry.StringPathEntry, None),
                'a11y-states': (OptionEntry.AccessibilityStatesEntry, ''),
                'allow-debugging': (OptionEntry.BooleanEntry, 'false'), }),
            MonitorsGroup(WidgetsWrapper(self.builder)))

        for group in self._groups:
            group.entry_added.connect(self.on_entry_added)
            group.entry_removed.connect(self.on_entry_removed)

        self._config_path = helpers.get_config_path()
        self._allow_edit = self._has_access_to_write(self._config_path)
        self._widgets.infobar.props.visible = not self._allow_edit
        self._widgets.apply.props.visible = self._allow_edit
        if not self._allow_edit:
            helpers.show_message(
                text=_('No permissions to save configuration'),
                secondary_text=_(
                    'It seems that you don\'t have permissions to write to '
                    'file:\n{path}\n\nTry to run this program using "sudo" '
                    'or "pkexec"').format(path=self._config_path),
                message_type=Gtk.MessageType.WARNING)

        self._config = configparser.RawConfigParser(strict=False)
        self._read()

    def _has_access_to_write(self, path):
        if os.path.exists(path) and os.access(self._config_path, os.W_OK):
            return True
        return os.access(os.path.dirname(self._config_path), os.W_OK | os.X_OK)

    def _read(self):
        self._config.clear()
        try:
            if not self._config.read(self._config_path):
                helpers.show_message(text=_('Failed to read configuration '
                                            'file: {path}')
                                     .format(path=self._config_path),
                                     message_type=Gtk.MessageType.ERROR)
        except (configparser.DuplicateSectionError,
                configparser.MissingSectionHeaderError):
            pass

        self._changed_entries = None

        for group in self._groups:
            group.read(self._config)

        self._initial_values = {entry: InitialValue(entry.value, entry.enabled)
                                for entry in self._initial_values.keys()}
        self._changed_entries = set()
        self._widgets.apply.props.sensitive = False

    def _write(self):
        for group in self._groups:
            group.write(self._config)

        for entry in self._changed_entries:
            self._initial_values[entry] = InitialValue(entry.value, entry.enabled)

        self._changed_entries.clear()
        self._widgets.apply.props.sensitive = False

        try:
            with open(self._config_path, 'w') as file:
                self._config.write(file)
        except OSError as e:
            helpers.show_message(e, Gtk.MessageType.ERROR)

    def on_entry_added(self, group, entry, key):
        if isinstance(group, SimpleGroup) and (group.name, key) in self.entries_setup:
            for action in self.entries_setup[(group.name, key)]:
                fname = 'on_entry_%s_%s_%s' % (action, group.name, key)
                f = getattr(self, fname.replace('-', '_'))
                if action == 'setup':
                    f(entry)
                else:
                    entry.connect(action, f)

        entry.changed.connect(self.on_entry_changed)
        self._initial_values[entry] = InitialValue(entry.value, entry.enabled)
        self.on_entry_changed(entry, force=True)

    def on_entry_removed(self, group, entry, key):
        self._initial_values.pop(entry)
        if self._changed_entries is None:
            return

        self._changed_entries.discard(entry)
        self._widgets.apply.props.sensitive = self._allow_edit and self._changed_entries

    def on_entry_changed(self, entry, force=False):
        if self._changed_entries is None:
            return

        initial = self._initial_values[entry]
        if force or entry.enabled != initial.enabled or \
           (entry.enabled and entry.value != initial.value):
            self._changed_entries.add(entry)
        else:
            self._changed_entries.discard(entry)

        self._widgets.apply.props.sensitive = self._allow_edit and self._changed_entries

    # [greeter] screensaver-timeout
    def on_entry_setup_greeter_screensaver_timeout(self, entry):
        view = entry.widgets['view']
        adjustment = entry.widgets['adjustment']
        end_label = entry.widgets['end-label']
        for mark in chain((range(10, 61, 10)),
                          (range(69, int(adjustment.props.upper), 10))):
            view.add_mark(mark, Gtk.PositionType.BOTTOM, None)
        total = int(adjustment.props.upper - 60) + 1
        end_label.props.label = C_('option|greeter|screensaver-timeout',
                                   '{count} min').format(count=total)

        view.connect('format-value',
                     self.on_entry_format_scale_greeter_screensaver_timeout,
                     adjustment)

    def on_entry_get_greeter_screensaver_timeout(self, entry=None, value=None):
        try:
            value = int(float(value))
        except ValueError:
            value = 60

        if value > 60:
            return (value - 59) * 60
        return value

    def on_entry_set_greeter_screensaver_timeout(self, entry=None, value=None):
        try:
            value = int(float(value))
        except ValueError:
            value = 60

        if value > 60:
            return value // 60 + 59
        return value

    def on_entry_format_scale_greeter_screensaver_timeout(self, scale, value, adjustment):
        if value != adjustment.props.lower and value != adjustment.props.upper:
            value = self.on_entry_get_greeter_screensaver_timeout(value=value)
            return '%02d:%02d' % (value // 60, value % 60)
        return ''

    # [greeter] theme-name
    def on_entry_setup_greeter_theme_name(self, entry):
        values = entry.widgets['values']
        for theme in sorted(iglob(os.path.join(sys.prefix, 'share', 'themes', '*', 'gtk-3.0'))):
            values.append_text(theme.split(os.path.sep)[-2])

    def on_entry_changed_greeter_theme_name(self, entry):
        if not entry.value or \
                entry.value in (row[0] for row in entry.widgets['values'].props.model):
            entry.error = None
        else:
            entry.error = C_('option|greeter|theme-name', 'Selected theme is not available')

    # [greeter] icon-theme-name
    def on_entry_setup_greeter_icon_theme_name(self, entry):
        values = entry.widgets['values']
        for theme in sorted(iglob(os.path.join(sys.prefix, 'share', 'icons', '*', 'index.theme'))):
            values.append_text(theme.split(os.path.sep)[-2])

    def on_entry_changed_greeter_icon_theme_name(self, entry):
        if not entry.value or \
                entry.value in (row[0] for row in entry.widgets['values'].props.model):
            entry.error = None
        else:
            entry.error = C_('option|greeter|icon-theme-name', 'Selected theme is not available')

    # [greeter] allow-debugging
    def on_entry_changed_greeter_allow_debugging(self, entry):
        if (Gtk.MAJOR_VERSION, Gtk.MINOR_VERSION, Gtk.MICRO_VERSION) < (3, 14, 0) and \
                string2bool(entry.value):
            entry.error = C_('option|greeter|allow-debugging',
                             'GtkInspector is not available on your system')
        else:
            entry.error = None

    # [greeter] default-user-image
    def on_entry_changed_greeter_default_user_image(self, entry):
        value = entry.value
        if value.startswith('#'):
            entry.error = None
        else:
            entry.error = helpers.check_path_accessibility(value)

    # [greeter] background
    def on_entry_changed_greeter_background(self, entry):
        value = entry.value
        if not value or Gdk.RGBA().parse(value):
            entry.error = None
        else:
            entry.error = helpers.check_path_accessibility(value)

    # [greeter] keyboard
    def on_entry_changed_greeter_keyboard(self, entry):
        error = None
        if entry.enabled:
            value = entry.value
            if os.path.isabs(value):
                argv = shlex.split(value)
                error = helpers.check_path_accessibility(argv[0], executable=True)
            elif not value:
                error = _('Do not leave this field empty')
        entry.error = error

    # [greeter] reader
    on_entry_changed_greeter_reader = on_entry_changed_greeter_keyboard

    def on_destroy(self, *unused):
        Gtk.main_quit()

    def on_apply_clicked(self, *unused):
        self._write()

    def on_reset_clicked(self, *unused):
        self._read()

    def on_close_clicked(self, *unused):
        self.destroy()