tweak_group_interface.py 11.6 KB
Newer Older
John Stowers's avatar
John Stowers committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# This file is part of gnome-tweak-tool.
#
# Copyright (c) 2011 John Stowers
#
# gnome-tweak-tool is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gnome-tweak-tool is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY 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 gnome-tweak-tool.  If not, see <http://www.gnu.org/licenses/>.

John Stowers's avatar
John Stowers committed
18
import os.path
19
20
21
22
23
import logging
import zipfile
import tempfile
import json
import pprint
John Stowers's avatar
John Stowers committed
24

25
from gi.repository import Gtk
26
from gi.repository import GLib
27

John Stowers's avatar
John Stowers committed
28
import gtweak
29
from gtweak.utils import walk_directories, make_combo_list_with_default, extract_zip_file
John Stowers's avatar
John Stowers committed
30
from gtweak.tweakmodel import Tweak, TWEAK_GROUP_APPEARANCE
31
32
33
34
35
36
from gtweak.gshellwrapper import GnomeShellFactory
from gtweak.gsettings import GSettingsSetting
from gtweak.widgets import ListBoxTweakGroup, GSettingsSwitchTweak, GSettingsComboTweak, DarkThemeSwitcher, Title, build_combo_box_text,build_label_beside_widget, FileChooserButton

_shell = GnomeShellFactory().get_shell()
_shell_loaded = _shell is not None
John Stowers's avatar
John Stowers committed
37

John Stowers's avatar
John Stowers committed
38
class GtkThemeSwitcher(GSettingsComboTweak):
John Stowers's avatar
John Stowers committed
39
    def __init__(self, **options):
40
        GSettingsComboTweak.__init__(self,
41
			"GTK+",
42
43
            "org.gnome.desktop.interface",
            "gtk-theme",
44
            make_combo_list_with_default(self._get_valid_themes(), "Adwaita"),
45
            **options)
46

47
48
    def _get_valid_themes(self):
        """ Only shows themes that have variations for gtk+-3 and gtk+-2 """
49
50
51
52
        gtk_ver = Gtk.MINOR_VERSION
        if gtk_ver % 2: # Want even number
            gtk_ver += 1

53
        dirs = ( os.path.join(gtweak.DATA_DIR, "themes"),
54
                 os.path.join(GLib.get_user_data_dir(), "themes"),
55
                 os.path.join(os.path.expanduser("~"), ".themes"))
56
57
        valid = walk_directories(dirs, lambda d:
                    os.path.exists(os.path.join(d, "gtk-2.0")) and \
58
59
                        (os.path.exists(os.path.join(d, "gtk-3.0")) or \
                         os.path.exists(os.path.join(d, "gtk-3.{}".format(gtk_ver)))))
60
        return valid
Andrea Fagiani's avatar
Andrea Fagiani committed
61

62
class IconThemeSwitcher(GSettingsComboTweak):
63
64
    def __init__(self, **options):
        GSettingsComboTweak.__init__(self,
65
			_("Icons"),            
66
			"org.gnome.desktop.interface",
67
            "icon-theme",
68
            make_combo_list_with_default(self._get_valid_icon_themes(), "Adwaita"),
69
            **options)
John Stowers's avatar
John Stowers committed
70

71
72
    def _get_valid_icon_themes(self):
        dirs = ( os.path.join(gtweak.DATA_DIR, "icons"),
73
                 os.path.join(GLib.get_user_data_dir(), "icons"),
74
                 os.path.join(os.path.expanduser("~"), ".icons"))
75
76
        valid = walk_directories(dirs, lambda d:
                    os.path.isdir(d) and \
Matthias Clasen's avatar
Matthias Clasen committed
77
			os.path.exists(os.path.join(d, "index.theme")))
78
        return valid
Andrea Fagiani's avatar
Andrea Fagiani committed
79

80
class CursorThemeSwitcher(GSettingsComboTweak):
Andrea Fagiani's avatar
Andrea Fagiani committed
81
    def __init__(self, **options):
Andrea Fagiani's avatar
Andrea Fagiani committed
82
        GSettingsComboTweak.__init__(self,
83
			_("Cursor"),
Andrea Fagiani's avatar
Andrea Fagiani committed
84
85
            "org.gnome.desktop.interface",
            "cursor-theme",
86
            make_combo_list_with_default(self._get_valid_cursor_themes(), "Adwaita"),
Andrea Fagiani's avatar
Andrea Fagiani committed
87
88
            **options)

89
90
    def _get_valid_cursor_themes(self):
        dirs = ( os.path.join(gtweak.DATA_DIR, "icons"),
91
                 os.path.join(GLib.get_user_data_dir(), "icons"),
92
                 os.path.join(os.path.expanduser("~"), ".icons"))
93
94
95
        valid = walk_directories(dirs, lambda d:
                    os.path.isdir(d) and \
                        os.path.exists(os.path.join(d, "cursors")))
96
97
        return valid

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
class ShellThemeTweak(Gtk.Box, Tweak):

    THEME_EXT_NAME = "user-theme@gnome-shell-extensions.gcampax.github.com"
    THEME_GSETTINGS_SCHEMA = "org.gnome.shell.extensions.user-theme"
    THEME_GSETTINGS_NAME = "name"
    THEME_GSETTINGS_DIR = os.path.join(GLib.get_user_data_dir(), "gnome-shell", "extensions",
                                       THEME_EXT_NAME, "schemas")
    LEGACY_THEME_DIR = os.path.join(GLib.get_home_dir(), ".themes")
    THEME_DIR = os.path.join(GLib.get_user_data_dir(), "themes")

    def __init__(self, **options):
        Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)
        Tweak.__init__(self, _("Shell theme"), _("Install custom or user themes for gnome-shell"), **options)

        #check the shell is running and the usertheme extension is present
        error = _("Unknown error")
        self._shell = _shell

        if self._shell is None:
            logging.warning("Shell not running", exc_info=True)
            error = _("Shell not running")
        else:
            try:
                extensions = self._shell.list_extensions()
                if ShellThemeTweak.THEME_EXT_NAME in extensions and extensions[ShellThemeTweak.THEME_EXT_NAME]["state"] == 1:
                    #check the correct gsettings key is present
                    try:
                        if os.path.exists(ShellThemeTweak.THEME_GSETTINGS_DIR):
                            self._settings = GSettingsSetting(ShellThemeTweak.THEME_GSETTINGS_SCHEMA,
                                                              schema_dir=ShellThemeTweak.THEME_GSETTINGS_DIR)
                        else:
                            self._settings = GSettingsSetting(ShellThemeTweak.THEME_GSETTINGS_SCHEMA)
                        name = self._settings.get_string(ShellThemeTweak.THEME_GSETTINGS_NAME)

                        ext = extensions[ShellThemeTweak.THEME_EXT_NAME]
                        logging.debug("Shell user-theme extension\n%s" % pprint.pformat(ext))

                        error = None
                    except:
                        logging.warning(
                            "Could not find user-theme extension in %s" % ','.join(extensions.keys()),
                            exc_info=True)
                        error = _("Shell user-theme extension incorrectly installed")

                else:
                    error = _("Shell user-theme extension not enabled")
            except Exception, e:
                logging.warning("Could not list shell extensions", exc_info=True)
                error = _("Could not list shell extensions")

        if error:
            cb = build_combo_box_text(None)
            build_label_beside_widget(self.name, cb,
                        warning=error,
                        hbox=self)
            self.widget_for_size_group = cb
        else:
            #include both system, and user themes
            #note: the default theme lives in /system/data/dir/gnome-shell/theme
            #      and not themes/, so add it manually later
            dirs = [os.path.join(d, "themes") for d in GLib.get_system_data_dirs()]
            dirs += [ShellThemeTweak.THEME_DIR]
            dirs += [ShellThemeTweak.LEGACY_THEME_DIR]

            valid = walk_directories(dirs, lambda d:
                        os.path.exists(os.path.join(d, "gnome-shell")) and \
                        os.path.exists(os.path.join(d, "gnome-shell", "gnome-shell.css")))
            #the default value to reset the shell is an empty string
            valid.extend( ("",) )

            #build a combo box with all the valid theme options
            #manually add Adwaita to represent the default
            cb = build_combo_box_text(
                    self._settings.get_string(ShellThemeTweak.THEME_GSETTINGS_NAME),
                    *make_combo_list_with_default(
                        valid,
                        "",
                        default_text=_("<i>Default</i>")))
            cb.connect('changed', self._on_combo_changed)
            self._combo = cb

            #a filechooser to install new themes
            chooser = FileChooserButton(
                        _("Select a theme"),
                        True,
                        ["application/zip"])
            chooser.connect("file-set", self._on_file_set)

            build_label_beside_widget(self.name, chooser, cb, hbox=self)
            self.widget_for_size_group = cb
    
    def _on_file_set(self, chooser):
        f = chooser.get_filename()

        with zipfile.ZipFile(f, 'r') as z:
            try:
                fragment = ()
                theme_name = None
                for n in z.namelist():
                    if n.endswith("gnome-shell.css"):
                        fragment = n.split("/")[0:-1]
                    if n.endswith("gnome-shell/theme.json"):
                        logging.info("New style theme detected (theme.json)")
                        #new style theme - extract the name from the json file
                        tmp = tempfile.mkdtemp()
                        z.extract(n, tmp)
                        with open(os.path.join(tmp,n)) as f:
                            try:
                                theme_name = json.load(f)["shell-theme"]["name"]
                            except:
                                logging.warning("Invalid theme format", exc_info=True)

                if not fragment:
                    raise Exception("Could not find gnome-shell.css")

                if not theme_name:
                    logging.info("Old style theme detected (missing theme.json)")
                    #old style themes name was taken from the zip name
                    if fragment[0] == "theme" and len(fragment) == 1:
                        theme_name = os.path.basename(f)
                    else:
                        theme_name = fragment[0]

                theme_members_path = "/".join(fragment)

                ok, updated = extract_zip_file(
                                z,
                                theme_members_path,
                                os.path.join(ShellThemeTweak.THEME_DIR, theme_name, "gnome-shell"))

                if ok:
                    if updated:
                        self.notify_information(_("%s theme updated successfully") % theme_name)
                    else:
                        self.notify_information(_("%s theme installed successfully") % theme_name)

                    #I suppose I could rely on updated as indicating whether to add the theme
                    #name to the combo, but just check to see if it is already there
                    model = self._combo.get_model()
                    if theme_name not in [r[0] for r in model]:
                        model.append( (theme_name, theme_name) )
                else:
                    self.notify_information(_("Error installing theme"))


            except:
                #does not look like a valid theme
                self.notify_information(_("Invalid theme"))
                logging.warning("Error parsing theme zip", exc_info=True)

        #set button back to default state
        chooser.unselect_all()

    def _on_combo_changed(self, combo):
        val = combo.get_model().get_value(combo.get_active_iter(), 0)
        self._settings.set_string(ShellThemeTweak.THEME_GSETTINGS_NAME, val)
254
255
256
257


TWEAK_GROUPS = [
    ListBoxTweakGroup(TWEAK_GROUP_APPEARANCE,
Alex Muñoz's avatar
Alex Muñoz committed
258
        DarkThemeSwitcher(),
259
260
        #GSettingsSwitchTweak("Buttons Icons","org.gnome.desktop.interface", "buttons-have-icons"),
        #GSettingsSwitchTweak("Menu Icons","org.gnome.desktop.interface", "menus-have-icons"),
261
        Title(_("Theme"), "", uid="title-theme"),
262
        GtkThemeSwitcher(),
263
        IconThemeSwitcher(),
264
        CursorThemeSwitcher(),
265
        ShellThemeTweak(loaded=_shell_loaded),
266
        GSettingsSwitchTweak(_("Enable animations"), "org.gnome.desktop.interface", "enable-animations"),
267
268
    ),
]