1 /*
2  * This file is part of brisk-menu.
3  *
4  * Copyright © 2017-2020 Brisk Menu Developers
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  */
11 
12 #define _GNU_SOURCE
13 
14 #include "util.h"
15 
16 BRISK_BEGIN_PEDANTIC
17 #include "favourites-backend.h"
18 #include <glib/gi18n.h>
19 #include <glib/gstdio.h>
20 BRISK_END_PEDANTIC
21 
22 #include <errno.h>
23 #include <string.h>
24 
25 DEF_AUTOFREE(GFile, g_object_unref)
26 DEF_AUTOFREE(gchar, g_free)
27 DEF_AUTOFREE(GError, g_error_free)
28 
29 typedef enum {
30         PIN_STATUS_UNPINNABLE = 0,
31         PIN_STATUS_PINNED = 1,
32         PIN_STATUS_UNPINNED = 2,
33 } DesktopPinStatus;
34 
35 /**
36  * get_desktop_item_source:
37  *
38  * Get a GFile for the BriskItem's source
39  */
get_desktop_item_source(BriskItem * item)40 static GFile *get_desktop_item_source(BriskItem *item)
41 {
42         autofree(gchar) *uri = NULL;
43 
44         if (!item) {
45                 return NULL;
46         }
47 
48         uri = brisk_item_get_uri(item);
49         if (!uri) {
50                 return NULL;
51         }
52         return g_file_new_for_uri(uri);
53 }
54 
55 /**
56  * get_item_path will return the path that would be pinned on the desktop
57  */
get_desktop_item_target_path(GFile * file)58 static gchar *get_desktop_item_target_path(GFile *file)
59 {
60         if (!g_file_query_exists(file, NULL)) {
61                 return NULL;
62         }
63         autofree(gchar) *basename = g_file_get_basename(file);
64 
65         return g_build_path(G_DIR_SEPARATOR_S,
66                             g_get_user_special_dir(G_USER_DIRECTORY_DESKTOP),
67                             basename,
68                             NULL);
69 }
70 
71 /**
72  * get_desktop_item_target:
73  *
74  * Get the target GFile on the desktop
75  */
get_desktop_item_target(GFile * file)76 static GFile *get_desktop_item_target(GFile *file)
77 {
78         autofree(gchar) *path = NULL;
79 
80         path = get_desktop_item_target_path(file);
81         return g_file_new_for_path(path);
82 }
83 
84 /**
85  * brisk_favourites_backend_action_desktop_pin will pin the item to the desktop
86  * by copying the source file to the target
87  */
brisk_favourites_backend_action_desktop_pin(__brisk_unused__ GSimpleAction * action,__brisk_unused__ GVariant * parameter,BriskFavouritesBackend * self)88 static void brisk_favourites_backend_action_desktop_pin(__brisk_unused__ GSimpleAction *action,
89                                                         __brisk_unused__ GVariant *parameter,
90                                                         BriskFavouritesBackend *self)
91 {
92         autofree(GFile) *source = NULL;
93         autofree(GFile) *dest = NULL;
94         autofree(GError) *error = NULL;
95         autofree(gchar) *path = NULL;
96 
97         source = get_desktop_item_source(self->active_item);
98         if (!source) {
99                 return;
100         }
101         dest = get_desktop_item_target(source);
102         if (!dest) {
103                 return;
104         }
105 
106         path = g_file_get_path(dest);
107         if (!path) {
108                 return;
109         }
110 
111         /* Consider making async. */
112         if (!g_file_copy(source,
113                          dest,
114                          G_FILE_COPY_ALL_METADATA | G_FILE_COPY_OVERWRITE,
115                          NULL,
116                          NULL,
117                          NULL,
118                          &error)) {
119                 /* Consider using libnotify */
120                 g_message("Failed to pin desktop item: %s", error->message);
121         }
122 
123         /* MATE will sanitize .desktop files that are chmod +x */
124         int r = g_chmod(path, 00755);
125         if (r != 0) {
126                 g_message("Failed to chmod desktop item: %s", strerror(errno));
127         }
128 }
129 
130 /**
131  * brisk_favourites_backend_action_desktop_unpin will attempt to unpin the item
132  * from the desktop by removing the .desktop file
133  */
brisk_favourites_backend_action_desktop_unpin(__brisk_unused__ GSimpleAction * action,__brisk_unused__ GVariant * parameter,BriskFavouritesBackend * self)134 static void brisk_favourites_backend_action_desktop_unpin(__brisk_unused__ GSimpleAction *action,
135                                                           __brisk_unused__ GVariant *parameter,
136                                                           BriskFavouritesBackend *self)
137 {
138         autofree(GFile) *source = NULL;
139         autofree(GFile) *dest = NULL;
140         autofree(GError) *error = NULL;
141 
142         source = get_desktop_item_source(self->active_item);
143         if (!source) {
144                 return;
145         }
146 
147         dest = get_desktop_item_target(source);
148         if (!dest || !g_file_query_exists(dest, NULL)) {
149                 return;
150         }
151 
152         if (!g_file_delete(dest, NULL, &error)) {
153                 /* Consider using libnotify */
154                 g_message("Unable to unpin desktop item: %s", error->message);
155         }
156 }
157 
158 /**
159  * brisk_favourites_backend_is_desktop_pinned:
160  *
161  * Determine if the source file is actually pinned to the desktop or not
162  */
brisk_favourites_backend_get_desktop_pin_status(BriskItem * item)163 static DesktopPinStatus brisk_favourites_backend_get_desktop_pin_status(BriskItem *item)
164 {
165         autofree(GFile) *source = NULL;
166         autofree(GFile) *dest = NULL;
167         autofree(gchar) *base = NULL;
168 
169         source = get_desktop_item_source(item);
170         if (!source) {
171                 return PIN_STATUS_UNPINNABLE;
172         }
173 
174         /* .desktop .. */
175         base = g_file_get_basename(source);
176         if (!g_str_has_suffix(base, ".desktop")) {
177                 return PIN_STATUS_UNPINNABLE;
178         }
179 
180         dest = get_desktop_item_target(source);
181         if (!dest || !g_file_query_exists(dest, NULL)) {
182                 return PIN_STATUS_UNPINNED;
183         }
184 
185         return PIN_STATUS_PINNED;
186 }
187 
188 /**
189  * brisk_favourites_backend_init_desktop:
190  *
191  * Initialise actions for the favourite backend's .desktop functionality
192  */
brisk_favourites_backend_init_desktop(BriskFavouritesBackend * self)193 void brisk_favourites_backend_init_desktop(BriskFavouritesBackend *self)
194 {
195         self->action_add_desktop = g_simple_action_new("favourites.pin-desktop", NULL);
196         g_signal_connect(self->action_add_desktop,
197                          "activate",
198                          G_CALLBACK(brisk_favourites_backend_action_desktop_pin),
199                          self);
200         self->action_remove_desktop = g_simple_action_new("favourites.unpin-desktop", NULL);
201         g_signal_connect(self->action_remove_desktop,
202                          "activate",
203                          G_CALLBACK(brisk_favourites_backend_action_desktop_unpin),
204                          self);
205 }
206 
207 /**
208  * brisk_favourites_backend_menu_desktop:
209  *
210  * Add relevant entries to the context menu pertaining to .desktop handling
211  */
brisk_favourites_backend_menu_desktop(BriskFavouritesBackend * self,GMenu * menu,GActionGroup * group)212 void brisk_favourites_backend_menu_desktop(BriskFavouritesBackend *self, GMenu *menu,
213                                            GActionGroup *group)
214 {
215         g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(self->action_add_desktop));
216         g_action_map_add_action(G_ACTION_MAP(group), G_ACTION(self->action_remove_desktop));
217         DesktopPinStatus t = brisk_favourites_backend_get_desktop_pin_status(self->active_item);
218 
219         switch (t) {
220         case PIN_STATUS_PINNED:
221                 g_menu_append(menu,
222                               _("Unpin from desktop"),
223                               "brisk-context-items.favourites.unpin-desktop");
224                 break;
225         case PIN_STATUS_UNPINNED:
226                 g_menu_append(menu,
227                               _("Pin to desktop"),
228                               "brisk-context-items.favourites.pin-desktop");
229                 break;
230         case PIN_STATUS_UNPINNABLE:
231         default:
232                 return;
233         }
234 }
235 
236 /*
237  * Editor modelines  -  https://www.wireshark.org/tools/modelines.html
238  *
239  * Local variables:
240  * c-basic-offset: 8
241  * tab-width: 8
242  * indent-tabs-mode: nil
243  * End:
244  *
245  * vi: set shiftwidth=8 tabstop=8 expandtab:
246  * :indentSize=8:tabSize=8:noTabs=true:
247  */
248