File: icon-theme.c

package info (click to toggle)
lxappearance 0.6.3-1
  • links: PTS, VCS
  • area: main
  • in suites: bullseye, buster, sid, stretch
  • size: 1,624 kB
  • ctags: 199
  • sloc: ansic: 1,745; sh: 1,232; makefile: 112; xml: 44; python: 28
file content (303 lines) | stat: -rw-r--r-- 12,010 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
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
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
/*
 *      icon-theme.c
 *
 *      Copyright 2010 PCMan <pcman.tw@gmail.com>
 *
 *      This program 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 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program 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 this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 */

#include "icon-theme.h"
#include "lxappearance.h"
#include <string.h>
#include <unistd.h>
#include "utils.h"

static char** icon_theme_dirs = NULL;

gint icon_theme_cmp_name(IconTheme* t, const char* name)
{
    return strcmp(t->name, name);
}

gint icon_theme_cmp_disp_name(IconTheme* t1, IconTheme* t2)
{
    return g_utf8_collate(t1->disp_name, t2->disp_name);
}

static void icon_theme_free(IconTheme* theme)
{
    g_free(theme->comment);
    g_free(theme->name);
    if(theme->disp_name != theme->name)
        g_free(theme->disp_name);
    g_slice_free(IconTheme, theme);
}

void load_icon_themes_from_dir(const char* base_dir, const char* theme_dir, GKeyFile* kf)
{
    /* NOTE:
     * 1. theoratically, base_dir is identical to theme_dir
     * the only case that they are different is when we try to install
     * a new theme. icon themes in a temporary theme dir may later be
     * installed to base_dir, and we load it from temp. theme dir
     * before installation. So theme_dir is the temporary dir containing
     * the themes to install. base_dir is the destination directory to be
     * installed to.
     * 2. base_dir is actually a component of global variable icon_theme_dirs.
     * so it's safe to use this string without strdup() since its life
     * span is the same as the whole program and won't be freed. */

    GDir* dir = g_dir_open(theme_dir, 0, NULL);
    if(dir)
    {
        const char* name;
        while ((name = g_dir_read_name(dir)))
        {
            /* skip "default" */
            if(G_UNLIKELY(strcmp(name, "default") == 0))
                continue;
            /* test if we already have this in list */
            if(!g_slist_find_custom(app.icon_themes, name, (GCompareFunc)icon_theme_cmp_name))
            {
                IconTheme* theme = g_slice_new0(IconTheme);
                char* index_theme;
                char* cursor_subdir;

                theme->name = g_strdup(name);
                index_theme = g_build_filename(theme_dir, name, "index.theme", NULL);
                theme->base_dir = base_dir;
                theme->is_removable = (0 == access(base_dir, W_OK));

                if(g_key_file_load_from_file(kf, index_theme, 0, NULL))
                {
                    /* skip hidden ones */
                    if(!g_key_file_get_boolean(kf, "Icon Theme", "Hidden", NULL))
                    {
                        theme->disp_name = g_key_file_get_locale_string(kf, "Icon Theme", "Name", NULL, NULL);
                        /* test if this is a icon theme or it's a cursor theme */
                        theme->comment = g_key_file_get_locale_string(kf, "Icon Theme", "Comment", NULL, NULL);

                        /* icon theme must have this key, so it has icons if it has this key */
                        theme->has_icon = g_key_file_has_key(kf, "Icon Theme", "Directories", NULL);
                    }
                }
                else
                    theme->disp_name = theme->name;
                g_free(index_theme);

                cursor_subdir = g_build_filename(theme_dir, name, "cursors", NULL);
                /* it contains a cursor theme */
                if(g_file_test(cursor_subdir, G_FILE_TEST_IS_DIR))
                    theme->has_cursor = TRUE;
                g_free(cursor_subdir);

                if(theme->has_icon || theme->has_cursor)
                    app.icon_themes = g_slist_insert_sorted(app.icon_themes, theme,
                                            (GCompareFunc)icon_theme_cmp_disp_name);
                else /* this dir contains no icon or cursor theme, drop it. */
                    icon_theme_free(theme);
            }
        }
        g_dir_close(dir);
    }
}

static void load_icon_themes()
{
    int n, i;
    gtk_icon_theme_get_search_path(gtk_icon_theme_get_default(), &icon_theme_dirs, &n);
    GKeyFile* kf = g_key_file_new();
    for(i = 0; i < n; ++i)
    {
        load_icon_themes_from_dir(icon_theme_dirs[i], icon_theme_dirs[i], kf);
        /* g_debug("icon_theme_dirs[%d] = %s", i, icon_theme_dirs[i]); */
    }
    g_key_file_free(kf);
}


static void on_icon_theme_sel_changed(GtkTreeSelection* tree_sel, gpointer user_data)
{
    GtkTreeModel* model;
    GtkTreeIter it;
    if(gtk_tree_selection_get_selected(tree_sel, &model, &it))
    {
        IconTheme* theme;
        gtk_tree_model_get(model, &it, 1, &theme, -1);
        if(g_strcmp0(theme->name, app.icon_theme))
        {
            g_free(app.icon_theme);
            app.icon_theme = g_strdup(theme->name);
            g_object_set(gtk_settings_get_default(), "gtk-icon-theme-name", app.icon_theme, NULL);

            lxappearance_changed();
        }

        gtk_widget_set_sensitive(app.icon_theme_remove_btn, theme->is_removable);
    }
}

static void on_install_theme_clicked(GtkButton* btn, gpointer user_data)
{
    install_icon_theme(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(btn))));
}

static void on_remove_theme_clicked(GtkButton* btn, gpointer user_data)
{
    GtkTreeSelection* sel;
    GtkTreeModel* model;
    GtkTreeIter it;

    if(btn == (GtkButton*)app.icon_theme_remove_btn) /* remove icon theme */
        sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(app.icon_theme_view));
    else if(btn == (GtkButton*)app.cursor_theme_remove_btn) /* remove cursor theme */
        sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(app.cursor_theme_view));
    else
        return;

    if(gtk_tree_selection_get_selected(sel, &model, &it))
    {
        IconTheme* theme;
        gboolean both = theme->has_icon && theme->has_cursor;

        if(gtk_tree_model_iter_n_children(model, NULL) < 2)
        {
            /* FIXME: the user shouldn't remove the last available theme.
             * another list needs to be checked, too. */
            return;
        }

        gtk_tree_model_get(model, &it, 1, &theme, -1);
        if(remove_icon_theme(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(btn))), theme))
        {
            gtk_list_store_remove(GTK_LIST_STORE(model), &it);

            /* select the first theme */
            gtk_tree_model_get_iter_first(model, &it);
            gtk_tree_selection_select_iter(sel, &it);

            /* FIXME: in rare case, a theme can contain both icons and cursors */
            if(both) /* we need to remove item in another list store, too */
            {
                model = GTK_TREE_MODEL(model == (GtkTreeModel*)app.icon_theme_store ? app.cursor_theme_store : app.icon_theme_store);
                /* find the item in another store */
                if(gtk_tree_model_get_iter_first(model, &it))
                {
                    IconTheme* theme2;
                    do
                    {
                        gtk_tree_model_get(model, &it, 1, &theme2, -1);
                        if(theme2 == theme)
                        {
                            gtk_list_store_remove(GTK_LIST_STORE(model), &it);
                            /* select the first theme */
                            gtk_tree_model_get_iter_first(model, &it);
                            gtk_tree_selection_select_iter(sel, &it);
                            break;
                        }
                    }while(gtk_tree_model_iter_next(model, &it));
                }
            }
        }
    }
}

void icon_theme_init(GtkBuilder* b)
{
    GSList* l;
    GtkTreeIter it;
    GtkTreeIter icon_theme_sel_it = {0};
    GtkTreeIter cursor_theme_sel_it = {0};
    GtkTreeSelection* sel;
    GtkWidget* btn;

    app.icon_theme_store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
    app.cursor_theme_store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_POINTER);
    app.icon_theme_view = GTK_WIDGET(gtk_builder_get_object(b, "icon_theme_view"));
    app.cursor_theme_view = GTK_WIDGET(gtk_builder_get_object(b, "cursor_theme_view"));

    /* install icon and cursor theme */
    btn = GTK_WIDGET(gtk_builder_get_object(b, "install_icon_theme"));
    g_signal_connect(btn, "clicked", G_CALLBACK(on_install_theme_clicked), NULL);

    btn = GTK_WIDGET(gtk_builder_get_object(b, "install_cursor_theme"));
    g_signal_connect(btn, "clicked", G_CALLBACK(on_install_theme_clicked), NULL);

    /* remove icon theme */
    app.icon_theme_remove_btn = GTK_WIDGET(gtk_builder_get_object(b, "remove_icon_theme"));
    g_signal_connect(app.icon_theme_remove_btn, "clicked", G_CALLBACK(on_remove_theme_clicked), NULL);

    app.cursor_theme_remove_btn = GTK_WIDGET(gtk_builder_get_object(b, "remove_cursor_theme"));
    g_signal_connect(app.cursor_theme_remove_btn, "clicked", G_CALLBACK(on_remove_theme_clicked), NULL);

    /* load icon and cursor themes */
    load_icon_themes();

    for(l = app.icon_themes; l; l=l->next)
    {
        IconTheme* theme = (IconTheme*)l->data;

        if(theme->has_icon)
        {
            gtk_list_store_insert_with_values(app.icon_theme_store, &it, -1, 0, theme->disp_name, 1, theme, -1);
            if(!icon_theme_sel_it.user_data)
            {
                if(strcmp(theme->name, app.icon_theme) == 0)
                    icon_theme_sel_it = it;
            }
        }

        if(theme->has_cursor)
        {
            gtk_list_store_insert_with_values(app.cursor_theme_store, &it, -1, 0, theme->disp_name, 1, theme, -1);
            if(!cursor_theme_sel_it.user_data)
            {
                if(g_strcmp0(theme->name, app.cursor_theme) == 0)
                    cursor_theme_sel_it = it;
            }
        }
    }

    /* select the currently used theme from the list */
    gtk_tree_view_set_model(GTK_TREE_VIEW(app.icon_theme_view), GTK_TREE_MODEL(app.icon_theme_store));
    sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(app.icon_theme_view));
    if(icon_theme_sel_it.user_data)
    {
        IconTheme* theme;
        GtkTreePath* tp = gtk_tree_model_get_path(GTK_TREE_MODEL(app.icon_theme_store), &icon_theme_sel_it);
        gtk_tree_selection_select_iter(sel, &icon_theme_sel_it);
        gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(app.icon_theme_view), tp, NULL, FALSE, 0, 0);
        gtk_tree_path_free(tp);

        gtk_tree_model_get(GTK_TREE_MODEL(app.icon_theme_store), &icon_theme_sel_it, 1, &theme, -1);
        gtk_widget_set_sensitive(app.icon_theme_remove_btn, theme->is_removable);
    }
    g_signal_connect(sel, "changed", G_CALLBACK(on_icon_theme_sel_changed), NULL);

    gtk_tree_view_set_model(GTK_TREE_VIEW(app.cursor_theme_view), GTK_TREE_MODEL(app.cursor_theme_store));
    sel = gtk_tree_view_get_selection(GTK_TREE_VIEW(app.cursor_theme_view));
    if(cursor_theme_sel_it.user_data)
    {
        IconTheme* theme;
        GtkTreePath* tp = gtk_tree_model_get_path(GTK_TREE_MODEL(app.cursor_theme_store), &cursor_theme_sel_it);
        gtk_tree_selection_select_iter(sel, &cursor_theme_sel_it);
        gtk_tree_view_scroll_to_cell(GTK_TREE_VIEW(app.cursor_theme_view), tp, NULL, FALSE, 0, 0);
        gtk_tree_path_free(tp);

        gtk_tree_model_get(GTK_TREE_MODEL(app.cursor_theme_store), &cursor_theme_sel_it, 1, &theme, -1);
        gtk_widget_set_sensitive(app.cursor_theme_remove_btn, theme->is_removable);
    }
}