2
* Copyright 2013 Canonical Ltd.
4
* This program is free software: you can redistribute it and/or modify it
5
* under the terms of the GNU General Public License version 3, as published
6
* by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranties of
10
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11
* PURPOSE. See the GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License along
14
* with this program. If not, see <http://www.gnu.org/licenses/>.
17
* Ted Gould <ted.gould@canonical.com>
24
This is a hook for Click packages. You can find information on Click package hooks in
25
the click documentation:
27
https://click-package.readthedocs.org/en/latest/
29
Probably the biggest thing to understand for how this code works is that you need to
30
understand that this hook is run after one, or many packages are installed. A set of
31
symbolic links are made to the desktop files per-application (not per-package) in the
32
directory specified in upstart-app-launcher-desktop.click-hook.in. Those desktop files
33
give us the App ID of the packages that are installed and have applications needing
34
desktop files in them. We then operate on each of them ensuring that they are synchronized
35
with the desktop files in ~/.local/share/applications/.
37
The desktop files that we're creating there ARE NOT used for execution by the
38
upstart-app-launch Upstart jobs. They are there so that Unity can know which applications
39
are installed for this user and they provide an Exec line to allow compatibility with
40
desktop environments that are not using upstart-app-launch for launching applications.
41
You should not modify them and expect any executing under Unity to change.
46
#include <glib/gstdio.h>
51
typedef struct _app_state_t app_state_t;
56
guint64 click_modified;
57
guint64 desktop_modified;
60
/* Find an entry in the app array */
62
find_app_entry (const gchar * name, GArray * app_array)
65
for (i = 0; i < app_array->len; i++) {
66
app_state_t * state = &g_array_index(app_array, app_state_t, i);
68
if (g_strcmp0(state->app_id, name) == 0) {
74
newstate.has_click = FALSE;
75
newstate.has_desktop = FALSE;
76
newstate.click_modified = 0;
77
newstate.desktop_modified = 0;
78
newstate.app_id = g_strdup(name);
80
g_array_append_val(app_array, newstate);
82
/* Note: The pointer needs to be the entry in the array, not the
83
one that we have on the stack. Criticaly important. */
84
app_state_t * statepntr = &g_array_index(app_array, app_state_t, app_array->len - 1);
88
/* Looks up the file creation time, which seems harder with GLib
91
modified_time (const gchar * dir, const gchar * filename)
93
gchar * path = g_build_filename(dir, filename, NULL);
94
GFile * file = g_file_new_for_path(path);
95
GFileInfo * info = g_file_query_info(file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
97
guint64 time = g_file_info_get_attribute_uint64(info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
100
g_object_unref(file);
106
/* Look at an click package entry */
108
add_click_package (const gchar * dir, const gchar * name, GArray * app_array)
110
if (!g_str_has_suffix(name, ".desktop")) {
114
gchar * appid = g_strdup(name);
115
g_strstr_len(appid, -1, ".desktop")[0] = '\0';
117
app_state_t * state = find_app_entry(appid, app_array);
118
state->has_click = TRUE;
119
state->click_modified = modified_time(dir, name);
126
/* Look at an desktop file entry */
128
add_desktop_file (const gchar * dir, const gchar * name, GArray * app_array)
130
if (!g_str_has_suffix(name, ".desktop")) {
134
gchar * appid = g_strdup(name);
135
g_strstr_len(appid, -1, ".desktop")[0] = '\0';
137
/* We only want valid APP IDs as desktop files */
138
if (!app_id_to_triplet(appid, NULL, NULL, NULL)) {
143
app_state_t * state = find_app_entry(appid, app_array);
144
state->has_desktop = TRUE;
145
state->desktop_modified = modified_time(dir, name);
151
/* Open a directory and look at all the entries */
153
dir_for_each (const gchar * dirname, void(*func)(const gchar * dir, const gchar * name, GArray * app_array), GArray * app_array)
155
GError * error = NULL;
156
GDir * directory = g_dir_open(dirname, 0, &error);
159
g_warning("Unable to read directory '%s': %s", dirname, error->message);
164
const gchar * filename = NULL;
165
while ((filename = g_dir_read_name(directory)) != NULL) {
166
func(dirname, filename, app_array);
169
g_dir_close(directory);
173
/* Function to take the source Desktop file and build a new
174
one with similar, but not the same data in it */
176
copy_desktop_file (const gchar * from, const gchar * to, const gchar * appdir, const gchar * app_id)
178
GError * error = NULL;
179
GKeyFile * keyfile = g_key_file_new();
180
g_key_file_load_from_file(keyfile,
182
G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS,
186
g_warning("Unable to read the desktop file '%s' in the application directory: %s", from, error->message);
188
g_key_file_unref(keyfile);
192
gchar * oldexec = desktop_to_exec(keyfile, from);
193
if (oldexec == NULL) {
194
g_key_file_unref(keyfile);
198
if (g_key_file_has_key(keyfile, "Desktop Entry", "Path", NULL)) {
199
gchar * oldpath = g_key_file_get_string(keyfile, "Desktop Entry", "Path", NULL);
200
g_debug("Desktop file '%s' has a Path set to '%s'. Setting as X-Ubuntu-Old-Path.", from, oldpath);
202
g_key_file_set_string(keyfile, "Desktop Entry", "X-Ubuntu-Old-Path", oldpath);
207
gchar * path = g_build_filename(appdir, NULL);
208
g_key_file_set_string(keyfile, "Desktop Entry", "Path", path);
211
gchar * newexec = g_strdup_printf("aa-exec -p %s -- %s", app_id, oldexec);
212
g_key_file_set_string(keyfile, "Desktop Entry", "Exec", newexec);
216
g_key_file_set_string(keyfile, "Desktop Entry", "X-Ubuntu-Application-ID", app_id);
219
gchar * data = g_key_file_to_data(keyfile, &datalen, &error);
220
g_key_file_unref(keyfile);
223
g_warning("Unable serialize keyfile built from '%s': %s", from, error->message);
228
g_file_set_contents(to, data, datalen, &error);
232
g_warning("Unable to write out desktop file to '%s': %s", to, error->message);
240
/* Build a desktop file in the user's home directory */
242
build_desktop_file (app_state_t * state, const gchar * symlinkdir, const gchar * desktopdir)
244
GError * error = NULL;
245
gchar * package = NULL;
246
/* 'Parse' the App ID */
247
if (!app_id_to_triplet(state->app_id, &package, NULL, NULL)) {
251
/* Check click to find out where the files are */
252
gchar * cmdline = g_strdup_printf("click pkgdir \"%s\"", package);
255
gchar * output = NULL;
256
g_spawn_command_line_sync(cmdline, &output, NULL, NULL, &error);
259
/* If we have an extra newline, we can hide it. */
260
if (output != NULL) {
261
gchar * newline = NULL;
263
newline = g_strstr_len(output, -1, "\n");
265
if (newline != NULL) {
271
g_warning("Unable to get the package directory from click: %s", error->message);
273
g_free(output); /* Probably not set, but just in case */
277
if (!g_file_test(output, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
278
g_warning("Dirctory returned by click '%s' couldn't be found", output);
283
gchar * indesktop = manifest_to_desktop(output, state->app_id);
284
if (indesktop == NULL) {
289
/* Determine the desktop file name */
290
gchar * desktopfile = g_strdup_printf("%s.desktop", state->app_id);
291
gchar * desktoppath = g_build_filename(desktopdir, desktopfile, NULL);
294
copy_desktop_file(indesktop, desktoppath, output, state->app_id);
303
/* Remove the desktop file from the user's home directory */
305
remove_desktop_file (app_state_t * state, const gchar * desktopdir)
307
gchar * desktopfile = g_strdup_printf("%s.desktop", state->app_id);
308
gchar * desktoppath = g_build_filename(desktopdir, desktopfile, NULL);
311
GKeyFile * keyfile = g_key_file_new();
312
g_key_file_load_from_file(keyfile,
317
if (!g_key_file_has_key(keyfile, "Desktop Entry", "X-Ubuntu-Application-ID", NULL)) {
318
g_debug("Desktop file '%s' is not one created by us.", desktoppath);
319
g_key_file_unref(keyfile);
323
g_key_file_unref(keyfile);
325
if (g_unlink(desktoppath) != 0) {
326
g_warning("Unable to delete desktop file: %s", desktoppath);
334
/* The main function */
336
main (int argc, char * argv[])
339
g_error("Shouldn't have arguments");
343
GArray * apparray = g_array_new(FALSE, FALSE, sizeof(app_state_t));
345
/* Find all the symlinks of desktop files */
346
gchar * symlinkdir = g_build_filename(g_get_user_cache_dir(), "upstart-app-launch", "desktop", NULL);
347
if (!g_file_test(symlinkdir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
348
g_warning("No installed click packages");
350
dir_for_each(symlinkdir, add_click_package, apparray);
353
/* Find all the click desktop files */
354
gchar * desktopdir = g_build_filename(g_get_user_data_dir(), "applications", NULL);
355
gboolean desktopdirexists = FALSE;
356
if (!g_file_test(symlinkdir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
357
g_warning("No applications defined");
359
dir_for_each(desktopdir, add_desktop_file, apparray);
360
desktopdirexists = TRUE;
363
/* Process the merge */
365
for (i = 0; i < apparray->len; i++) {
366
app_state_t * state = &g_array_index(apparray, app_state_t, i);
367
g_debug("Processing App ID: %s", state->app_id);
369
if (state->has_click && state->has_desktop) {
370
if (state->click_modified > state->desktop_modified) {
371
g_debug("\tClick updated more recently");
372
g_debug("\tRemoving desktop file");
373
if (remove_desktop_file(state, desktopdir)) {
374
g_debug("\tBuilding desktop file");
375
build_desktop_file(state, symlinkdir, desktopdir);
378
g_debug("\tAlready synchronized");
380
} else if (state->has_click) {
381
if (!desktopdirexists) {
382
if (g_mkdir_with_parents(desktopdir, 0755) == 0) {
383
g_debug("\tCreated applications directory");
384
desktopdirexists = TRUE;
386
g_warning("\tUnable to create applications directory");
389
if (desktopdirexists) {
390
g_debug("\tBuilding desktop file");
391
build_desktop_file(state, symlinkdir, desktopdir);
393
} else if (state->has_desktop) {
394
g_debug("\tRemoving desktop file");
395
remove_desktop_file(state, desktopdir);
398
g_free(state->app_id);
401
g_array_free(apparray, TRUE);