1 /**************************************************************************
3 * Copyright (c) 2004-17 Simon Peter
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 **************************************************************************/
27 #ident "AppImage by Simon Peter, http://appimage.org/"
30 * Optional daempon to watch directories for AppImages
31 * and register/unregister them with the system
33 * TODO (feel free to send pull requests):
34 * - Switch to https://developer.gnome.org/gio/stable/GFileMonitor.html (but with subdirectories)
35 * which would drop the dependency on libinotifytools.so.0
36 * - Add and remove subdirectories on the fly at runtime -
37 * see https://github.com/paragone/configure-via-inotify/blob/master/inotify/src/inotifywatch.c
45 #include <sys/types.h>
48 #include <inotifytools/inotifytools.h>
49 #include <inotifytools/inotify.h>
52 #include <glib/gprintf.h>
58 extern int notify(char *title
, char *body
, int timeout
);
60 static gboolean verbose
= FALSE
;
61 static gboolean version
= FALSE
;
62 static gboolean install
= FALSE
;
63 static gboolean uninstall
= FALSE
;
64 gchar
**remaining_args
= NULL
;
66 static GOptionEntry entries
[] =
68 { "verbose", 'v', 0, G_OPTION_ARG_NONE
, &verbose
, "Be verbose", NULL
},
69 { "install", 'i', 0, G_OPTION_ARG_NONE
, &install
, "Install this appimaged instance to $HOME", NULL
},
70 { "uninstall", 'u', 0, G_OPTION_ARG_NONE
, &uninstall
, "Uninstall an appimaged instance from $HOME", NULL
},
71 { "version", 0, 0, G_OPTION_ARG_NONE
, &version
, "Show version number", NULL
},
72 { G_OPTION_REMAINING
, 0, 0, G_OPTION_ARG_FILENAME_ARRAY
, &remaining_args
, NULL
},
76 #define EXCLUDE_CHUNK 1024
77 #define WR_EVENTS (IN_CLOSE_WRITE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF)
79 /* Run the actual work in treads;
80 * pthread allows to pass only one argument to the thread function,
81 * hence we use a struct as the argument in which the real arguments are */
87 void *thread_appimage_register_in_system(void *arguments
)
89 struct arg_struct
*args
= arguments
;
90 appimage_register_in_system(args
->path
, args
->verbose
);
94 void *thread_appimage_unregister_in_system(void *arguments
)
96 struct arg_struct
*args
= arguments
;
97 appimage_unregister_in_system(args
->path
, args
->verbose
);
101 /* Recursively process the files in this directory and its subdirectories,
102 * http://stackoverflow.com/questions/8436841/how-to-recursively-list-directories-in-c-on-linux
104 void initially_register(const char *name
, int level
)
107 struct dirent
*entry
;
109 if (!(dir
= opendir(name
))) {
111 if (errno
== EACCES
) {
112 g_print("_________________________\n");
113 g_print("Permission denied on dir '%s'\n", name
);
116 g_print("_________________________\n");
117 g_print("Failed to open dir '%s'\n", name
);
124 if (!(entry
= readdir(dir
))) {
126 g_print("_________________________\n");
127 g_print("Invalid directory stream descriptor '%s'\n", name
);
134 if (entry
->d_type
== DT_DIR
) {
136 int len
= snprintf(path
, sizeof(path
)-1, "%s/%s", name
, entry
->d_name
);
138 if (strcmp(entry
->d_name
, ".") == 0 || strcmp(entry
->d_name
, "..") == 0)
140 initially_register(path
, level
+ 1);
144 gchar
*absolute_path
= g_build_path(G_DIR_SEPARATOR_S
, name
, entry
->d_name
, NULL
);
145 if(g_file_test(absolute_path
, G_FILE_TEST_IS_REGULAR
)){
146 pthread_t some_thread
;
147 struct arg_struct args
;
148 args
.path
= absolute_path
;
149 args
.verbose
= verbose
;
150 ret
= pthread_create(&some_thread
, NULL
, thread_appimage_register_in_system
, &args
);
152 pthread_join(some_thread
, NULL
);
156 } while ((entry
= readdir(dir
)) != NULL
);
160 void add_dir_to_watch(char *directory
)
162 if (g_file_test (directory
, G_FILE_TEST_IS_DIR
)){
163 if(!inotifytools_watch_recursively(directory
, WR_EVENTS
) ) {
164 fprintf(stderr
, "%s\n", strerror(inotifytools_error()));
168 initially_register(directory
, 0);
172 void handle_event(struct inotify_event
*event
)
175 gchar
*absolute_path
= g_build_path(G_DIR_SEPARATOR_S
, inotifytools_filename_from_wd(event
->wd
), event
->name
, NULL
);
177 if((event
->mask
& IN_CLOSE_WRITE
) | (event
->mask
& IN_MOVED_TO
)){
178 if(g_file_test(absolute_path
, G_FILE_TEST_IS_REGULAR
)){
179 pthread_t some_thread
;
180 struct arg_struct args
;
181 args
.path
= absolute_path
;
182 args
.verbose
= verbose
;
183 g_print("_________________________\n");
184 ret
= pthread_create(&some_thread
, NULL
, thread_appimage_register_in_system
, &args
);
186 pthread_join(some_thread
, NULL
);
191 if((event
->mask
& IN_MOVED_FROM
) | (event
->mask
& IN_DELETE
)){
192 pthread_t some_thread
;
193 struct arg_struct args
;
194 args
.path
= absolute_path
;
195 args
.verbose
= verbose
;
196 g_print("_________________________\n");
197 ret
= pthread_create(&some_thread
, NULL
, thread_appimage_unregister_in_system
, &args
);
199 pthread_join(some_thread
, NULL
);
203 /* Too many FS events were received, some event notifications were potentially lost */
204 if (event
->mask
& IN_Q_OVERFLOW
){
205 printf ("Warning: AN OVERFLOW EVENT OCCURRED\n");
208 if(event
->mask
& IN_IGNORED
){
209 printf ("Warning: AN IN_IGNORED EVENT OCCURRED\n");
214 int main(int argc
, char ** argv
) {
216 GError
*error
= NULL
;
217 GOptionContext
*context
;
219 context
= g_option_context_new ("");
220 g_option_context_add_main_entries (context
, entries
, NULL
);
221 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
222 if (!g_option_context_parse (context
, &argc
, &argv
, &error
))
224 g_print("option parsing failed: %s\n", error
->message
);
229 fprintf(stderr
,"Version: %s\n", VERSION_NUMBER
);
233 if ( !inotifytools_initialize()){
234 fprintf(stderr
, "inotifytools_initialize error\n");
238 gchar
*user_bin_dir
= g_build_filename(g_get_home_dir(), "/.local/bin", NULL
);
239 gchar
*installed_appimaged_location
= g_build_filename(user_bin_dir
, "appimaged", NULL
);
240 const gchar
*appimage_location
= g_getenv("APPIMAGE");
241 gchar
*own_desktop_file_location
= g_build_filename(g_getenv("APPDIR"), "/appimaged.desktop", NULL
);
242 gchar
*global_autostart_file
= "/etc/xdg/autostart/appimaged.desktop";
243 gchar
*global_systemd_file
= "/usr/lib/systemd/user/appimaged.service";
244 gchar
*partial_path
= g_strdup_printf("autostart/appimagekit-appimaged.desktop");
245 gchar
*destination
= g_build_filename(g_get_user_config_dir(), partial_path
, NULL
);
248 if(g_file_test (installed_appimaged_location
, G_FILE_TEST_EXISTS
))
249 fprintf(stderr
, "* Please delete %s\n", installed_appimaged_location
);
250 if(g_file_test (destination
, G_FILE_TEST_EXISTS
))
251 fprintf(stderr
, "* Please delete %s\n", destination
);
252 fprintf(stderr
, "* To remove all AppImage desktop integration, run\n");
253 fprintf(stderr
, " find ~/.local/share -name 'appimagekit_*' -exec rm {} \\;\n\n");
258 if(((appimage_location
!= NULL
)) && ((own_desktop_file_location
!= NULL
))){
259 printf("Running from within %s\n", appimage_location
);
260 if ( (! g_file_test ("/usr/bin/appimaged", G_FILE_TEST_EXISTS
)) && (! g_file_test (global_autostart_file
, G_FILE_TEST_EXISTS
)) && (! g_file_test (global_systemd_file
, G_FILE_TEST_EXISTS
))){
261 printf ("%s is not installed, moving it to %s\n", argv
[0], installed_appimaged_location
);
262 g_mkdir_with_parents(user_bin_dir
, 0755);
263 gchar
*command
= g_strdup_printf("mv \"%s\" \"%s\"", appimage_location
, installed_appimaged_location
);
265 /* When appimaged installs itself, then to the $XDG_CONFIG_HOME/autostart/ directory, falling back to ~/.config/autostart/ */
266 fprintf(stderr
, "Installing to autostart: %s\n", own_desktop_file_location
);
267 g_mkdir_with_parents(g_path_get_dirname(destination
), 0755);
268 gchar
*command2
= g_strdup_printf("cp \"%s\" \"%s\"", own_desktop_file_location
, destination
);
270 if(g_file_test (installed_appimaged_location
, G_FILE_TEST_EXISTS
))
271 fprintf(stderr
, "* Installed %s\n", installed_appimaged_location
);
272 if(g_file_test (destination
, G_FILE_TEST_EXISTS
)){
273 gchar
*command3
= g_strdup_printf("sed -i -e 's|^Exec=.*|Exec=%s|g' '%s'", installed_appimaged_location
, destination
);
275 fprintf(stderr
, "%s\n", command3
);
277 fprintf(stderr
, "* Installed %s\n", destination
);
279 if(g_file_test (installed_appimaged_location
, G_FILE_TEST_EXISTS
))
280 fprintf(stderr
, "\nTo uninstall, run %s --uninstall and follow the instructions\n\n", installed_appimaged_location
);
283 title
= g_strdup_printf("Please log out");
284 body
= g_strdup_printf("and log in again to complete the installation");
285 notify(title
, body
, 15);
289 printf("Not running from within an AppImage. This binary cannot be installed in this way.\n");
294 /* When we run from inside an AppImage, then we check if we are installed
295 * in a per-user location and if not, we install ourselves there */
296 if(((appimage_location
!= NULL
)) && ((own_desktop_file_location
!= NULL
))){
297 if ( (! g_file_test ("/usr/bin/appimaged", G_FILE_TEST_EXISTS
)) && ((! g_file_test (global_autostart_file
, G_FILE_TEST_EXISTS
)) || (! g_file_test (destination
, G_FILE_TEST_EXISTS
))) && (! g_file_test (global_systemd_file
, G_FILE_TEST_EXISTS
)) && (! g_file_test (installed_appimaged_location
, G_FILE_TEST_EXISTS
)) && (g_file_test (own_desktop_file_location
, G_FILE_TEST_IS_REGULAR
))){
300 title
= g_strdup_printf("Not installed\n");
301 body
= g_strdup_printf("Please run %s --install", argv
[0]);
302 notify(title
, body
, 15);
307 add_dir_to_watch(user_bin_dir
);
308 add_dir_to_watch(g_build_filename(g_get_home_dir(), "/Downloads", NULL
));
309 add_dir_to_watch(g_build_filename(g_get_home_dir(), "/bin", NULL
));
310 add_dir_to_watch(g_build_filename("/Applications", NULL
));
311 add_dir_to_watch(g_build_filename("/isodevice/Applications", NULL
)); // Ubuntu Live media
312 add_dir_to_watch(g_build_filename("/isofrom/Applications", NULL
)); // openSUSE Live media
313 add_dir_to_watch(g_build_filename("/run/archiso/img_dev/Applications", NULL
)); // Antergos Live media
314 add_dir_to_watch(g_build_filename("/opt", NULL
));
315 add_dir_to_watch(g_build_filename("/usr/local/bin", NULL
));
317 struct inotify_event
* event
= inotifytools_next_event(-1);
320 inotifytools_printf(event
, "%w%f %e\n");
325 event
= inotifytools_next_event(-1);