1 /**************************************************************************
3 * Copyright (c) 2004-18 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>
59 #define RELEASE_NAME "continuous build"
62 extern int notify(char *title
, char *body
, int timeout
);
64 static gboolean verbose
= FALSE
;
65 static gboolean showVersionOnly
= FALSE
;
66 static gboolean install
= FALSE
;
67 static gboolean uninstall
= FALSE
;
68 static gboolean no_install
= FALSE
;
69 gchar
**remaining_args
= NULL
;
71 static GOptionEntry entries
[] =
73 { "verbose", 'v', 0, G_OPTION_ARG_NONE
, &verbose
, "Be verbose", NULL
},
74 { "install", 'i', 0, G_OPTION_ARG_NONE
, &install
, "Install this appimaged instance to $HOME", NULL
},
75 { "uninstall", 'u', 0, G_OPTION_ARG_NONE
, &uninstall
, "Uninstall an appimaged instance from $HOME", NULL
},
76 { "no-install", 'n', 0, G_OPTION_ARG_NONE
, &no_install
, "Force run without installation", NULL
},
77 { "version", 0, 0, G_OPTION_ARG_NONE
, &showVersionOnly
, "Show version number", NULL
},
78 { G_OPTION_REMAINING
, 0, 0, G_OPTION_ARG_FILENAME_ARRAY
, &remaining_args
, NULL
},
82 #define EXCLUDE_CHUNK 1024
83 #define WR_EVENTS (IN_CLOSE_WRITE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF)
85 /* Run the actual work in treads;
86 * pthread allows to pass only one argument to the thread function,
87 * hence we use a struct as the argument in which the real arguments are */
93 void *thread_appimage_register_in_system(void *arguments
)
95 struct arg_struct
*args
= arguments
;
96 appimage_register_in_system(args
->path
, args
->verbose
);
100 void *thread_appimage_unregister_in_system(void *arguments
)
102 struct arg_struct
*args
= arguments
;
103 appimage_unregister_in_system(args
->path
, args
->verbose
);
107 /* Recursively process the files in this directory and its subdirectories,
108 * http://stackoverflow.com/questions/8436841/how-to-recursively-list-directories-in-c-on-linux
110 void initially_register(const char *name
, int level
)
113 struct dirent
*entry
;
115 if (!(dir
= opendir(name
))) {
117 if (errno
== EACCES
) {
118 g_print("_________________________\n");
119 g_print("Permission denied on dir '%s'\n", name
);
122 g_print("_________________________\n");
123 g_print("Failed to open dir '%s'\n", name
);
130 if (!(entry
= readdir(dir
))) {
132 g_print("_________________________\n");
133 g_print("Invalid directory stream descriptor '%s'\n", name
);
140 if (entry
->d_type
== DT_DIR
) {
142 int len
= snprintf(path
, sizeof(path
)-1, "%s/%s", name
, entry
->d_name
);
144 if (strcmp(entry
->d_name
, ".") == 0 || strcmp(entry
->d_name
, "..") == 0)
146 initially_register(path
, level
+ 1);
150 gchar
*absolute_path
= g_build_path(G_DIR_SEPARATOR_S
, name
, entry
->d_name
, NULL
);
151 if(g_file_test(absolute_path
, G_FILE_TEST_IS_REGULAR
)){
152 pthread_t some_thread
;
153 struct arg_struct args
;
154 args
.path
= absolute_path
;
155 args
.verbose
= verbose
;
156 ret
= pthread_create(&some_thread
, NULL
, thread_appimage_register_in_system
, &args
);
158 pthread_join(some_thread
, NULL
);
161 g_free(absolute_path
);
163 } while ((entry
= readdir(dir
)) != NULL
);
167 void add_dir_to_watch(char *directory
)
169 if (g_file_test (directory
, G_FILE_TEST_IS_DIR
)){
170 if(!inotifytools_watch_recursively(directory
, WR_EVENTS
) ) {
171 fprintf(stderr
, "%s\n", strerror(inotifytools_error()));
175 initially_register(directory
, 0);
179 void handle_event(struct inotify_event
*event
)
182 gchar
*absolute_path
= g_build_path(G_DIR_SEPARATOR_S
, inotifytools_filename_from_wd(event
->wd
), event
->name
, NULL
);
184 if((event
->mask
& IN_CLOSE_WRITE
) | (event
->mask
& IN_MOVED_TO
)){
185 if(g_file_test(absolute_path
, G_FILE_TEST_IS_REGULAR
)){
186 pthread_t some_thread
;
187 struct arg_struct args
;
188 args
.path
= absolute_path
;
189 args
.verbose
= verbose
;
190 g_print("_________________________\n");
191 ret
= pthread_create(&some_thread
, NULL
, thread_appimage_register_in_system
, &args
);
193 pthread_join(some_thread
, NULL
);
198 if((event
->mask
& IN_MOVED_FROM
) | (event
->mask
& IN_DELETE
)){
199 pthread_t some_thread
;
200 struct arg_struct args
;
201 args
.path
= absolute_path
;
202 args
.verbose
= verbose
;
203 g_print("_________________________\n");
204 ret
= pthread_create(&some_thread
, NULL
, thread_appimage_unregister_in_system
, &args
);
206 pthread_join(some_thread
, NULL
);
210 g_free(absolute_path
);
212 /* Too many FS events were received, some event notifications were potentially lost */
213 if (event
->mask
& IN_Q_OVERFLOW
){
214 printf ("Warning: AN OVERFLOW EVENT OCCURRED\n");
217 if(event
->mask
& IN_IGNORED
){
218 printf ("Warning: AN IN_IGNORED EVENT OCCURRED\n");
223 int main(int argc
, char ** argv
) {
225 GError
*error
= NULL
;
226 GOptionContext
*context
;
228 context
= g_option_context_new ("");
229 g_option_context_add_main_entries (context
, entries
, NULL
);
230 // g_option_context_add_group (context, gtk_get_option_group (TRUE));
231 if (!g_option_context_parse (context
, &argc
, &argv
, &error
))
233 g_print("option parsing failed: %s\n", error
->message
);
237 // always show version, but exit immediately if only the version number was requested
240 "appimaged, %s (commit %s), build %s built on %s\n",
241 RELEASE_NAME
, GIT_COMMIT
, BUILD_NUMBER
, BUILD_DATE
247 if (!inotifytools_initialize()) {
248 fprintf(stderr
, "inotifytools_initialize error\n");
252 gchar
*user_bin_dir
= g_build_filename(g_get_home_dir(), "/.local/bin", NULL
);
253 gchar
*installed_appimaged_location
= g_build_filename(user_bin_dir
, "appimaged", NULL
);
254 const gchar
*appimage_location
= g_getenv("APPIMAGE");
255 gchar
*own_desktop_file_location
= g_build_filename(g_getenv("APPDIR"), "/appimaged.desktop", NULL
);
256 gchar
*global_autostart_file
= "/etc/xdg/autostart/appimaged.desktop";
257 gchar
*global_systemd_file
= "/usr/lib/systemd/user/appimaged.service";
258 gchar
*partial_path
= g_strdup_printf("autostart/appimagekit-appimaged.desktop");
259 gchar
*destination
= g_build_filename(g_get_user_config_dir(), partial_path
, NULL
);
262 if(g_file_test (installed_appimaged_location
, G_FILE_TEST_EXISTS
))
263 fprintf(stderr
, "* Please delete %s\n", installed_appimaged_location
);
264 if(g_file_test (destination
, G_FILE_TEST_EXISTS
))
265 fprintf(stderr
, "* Please delete %s\n", destination
);
266 fprintf(stderr
, "* To remove all AppImage desktop integration, run\n");
267 fprintf(stderr
, " find ~/.local/share -name 'appimagekit_*' -exec rm {} \\;\n\n");
272 if(((appimage_location
!= NULL
)) && ((own_desktop_file_location
!= NULL
))){
273 printf("Running from within %s\n", appimage_location
);
274 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
))){
275 printf ("%s is not installed, moving it to %s\n", argv
[0], installed_appimaged_location
);
276 g_mkdir_with_parents(user_bin_dir
, 0755);
277 gchar
*command
= g_strdup_printf("mv \"%s\" \"%s\"", appimage_location
, installed_appimaged_location
);
279 /* When appimaged installs itself, then to the $XDG_CONFIG_HOME/autostart/ directory, falling back to ~/.config/autostart/ */
280 fprintf(stderr
, "Installing to autostart: %s\n", own_desktop_file_location
);
281 g_mkdir_with_parents(g_path_get_dirname(destination
), 0755);
282 gchar
*command2
= g_strdup_printf("cp \"%s\" \"%s\"", own_desktop_file_location
, destination
);
284 if(g_file_test (installed_appimaged_location
, G_FILE_TEST_EXISTS
))
285 fprintf(stderr
, "* Installed %s\n", installed_appimaged_location
);
286 if(g_file_test (destination
, G_FILE_TEST_EXISTS
)){
287 gchar
*command3
= g_strdup_printf("sed -i -e 's|^Exec=.*|Exec=%s|g' '%s'", installed_appimaged_location
, destination
);
289 fprintf(stderr
, "%s\n", command3
);
291 fprintf(stderr
, "* Installed %s\n", destination
);
293 if(g_file_test (installed_appimaged_location
, G_FILE_TEST_EXISTS
))
294 fprintf(stderr
, "\nTo uninstall, run %s --uninstall and follow the instructions\n\n", installed_appimaged_location
);
297 title
= g_strdup_printf("Please log out");
298 body
= g_strdup_printf("and log in again to complete the installation");
299 notify(title
, body
, 15);
303 printf("Not running from within an AppImage. This binary cannot be installed in this way.\n");
308 /* When we run from inside an AppImage, then we check if we are installed
309 * in a per-user location and if not, we install ourselves there */
310 if(!no_install
&& (appimage_location
!= NULL
&& own_desktop_file_location
!= NULL
)) {
311 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
))){
314 title
= g_strdup_printf("Not installed\n");
315 body
= g_strdup_printf("Please run %s --install", argv
[0]);
316 notify(title
, body
, 15);
321 add_dir_to_watch(user_bin_dir
);
322 add_dir_to_watch(g_build_filename(g_get_home_dir(), "/Downloads", NULL
));
323 add_dir_to_watch(g_build_filename(g_get_home_dir(), "/bin", NULL
));
324 add_dir_to_watch(g_build_filename(g_get_home_dir(), "/.bin", NULL
));
325 add_dir_to_watch(g_build_filename("/Applications", NULL
));
326 // Perhaps we should determine the following dynamically using something like
327 // mount | grep -i iso | head -n 1 | cut -d ' ' -f 3
328 add_dir_to_watch(g_build_filename("/isodevice/Applications", NULL
)); // Ubuntu Live media
329 add_dir_to_watch(g_build_filename("/isofrom/Applications", NULL
)); // openSUSE Live media
330 add_dir_to_watch(g_build_filename("/run/archiso/img_dev/Applications", NULL
)); // Antergos Live media
331 add_dir_to_watch(g_build_filename("/lib/live/mount/findiso/Applications", NULL
)); // Manjaro Live media
332 add_dir_to_watch(g_build_filename("/opt", NULL
));
333 add_dir_to_watch(g_build_filename("/usr/local/bin", NULL
));
335 struct inotify_event
* event
= inotifytools_next_event(-1);
338 inotifytools_printf(event
, "%w%f %e\n");
343 event
= inotifytools_next_event(-1);