├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── FindINotify.cmake ├── cpack_debs.cmake ├── cpack_rpms.cmake └── toolchains │ └── i386-linux-gnu.cmake ├── lib └── CMakeLists.txt ├── resources ├── CMakeLists.txt ├── appimaged.desktop ├── appimaged.png ├── appimaged.service ├── appimaged.svg └── usr │ └── share │ └── metainfo │ └── appimaged.appdata.xml ├── src ├── CMakeLists.txt ├── main.c ├── notify.c └── notify.h └── travis ├── check_latest_libappimage_tag.sh ├── test-appimage.sh └── travis-build.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *build*/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | sudo: required 3 | 4 | matrix: 5 | include: 6 | - env: ARCH=x86_64 7 | addons: 8 | apt: 9 | update: true 10 | packages: 11 | - librsvg2-bin 12 | - libfuse-dev 13 | - desktop-file-utils 14 | - ccache 15 | - libcurl3 16 | - libbsd-dev 17 | - libcairo-dev 18 | - rpm 19 | - librsvg2-dev 20 | - env: ARCH=i386 21 | addons: 22 | apt: 23 | packages: 24 | - gcc-multilib 25 | - g++-multilib 26 | - libfuse2:i386 27 | - libfuse-dev:i386 28 | - desktop-file-utils 29 | - ccache 30 | - libbsd-dev:i386 31 | - libglib2.0-dev:i386 32 | - liblzma-dev:i386 33 | - libgtest-dev 34 | - libcairo2-dev:i386 35 | - librsvg2-bin:i386 36 | - librsvg2-dev:i386 37 | - libfreetype6-dev:i386 38 | - libfontconfig1-dev:i386 39 | - rpm 40 | - gir1.2-rsvg-2.0:i386 41 | - libgdk-pixbuf2.0-dev:i386 42 | - gir1.2-freedesktop:i386 43 | - gir1.2-gdkpixbuf-2.0:i386 44 | - gir1.2-glib-2.0:i386 45 | - libgirepository-1.0-1:i386 46 | 47 | script: 48 | - ( mkdir -p ./lib ; cd ./lib/ ; git clone --recursive https://github.com/AppImage/libappimage ) 49 | - bash -xe travis/travis-build.sh 50 | 51 | after_success: 52 | - wget -c https://github.com/probonopd/uploadtool/raw/master/upload.sh 53 | - if [ "$TRAVIS_BRANCH" != "master" ]; then export TRAVIS_EVENT_TYPE=pull_request; fi 54 | - bash upload.sh appimaged*.AppImage* appimaged*.deb* appimaged*.rpm* 55 | 56 | branches: 57 | except: 58 | - # Do not build tags that we create when we upload to GitHub Releases 59 | - /^(?i:continuous)$/ 60 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(appimaged) 4 | 5 | # include own FindXXXX scripts 6 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 7 | 8 | # determine Git commit ID 9 | execute_process( 10 | COMMAND git rev-parse --short HEAD 11 | WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} 12 | OUTPUT_VARIABLE APPIMAGED_GIT_COMMIT 13 | OUTPUT_STRIP_TRAILING_WHITESPACE 14 | ) 15 | mark_as_advanced(APPIMAGED_GIT_COMMIT) 16 | 17 | # set version and build number 18 | set(APPIMAGED_VERSION 1-alpha) 19 | mark_as_advanced(APPIMAGED_VERSION) 20 | 21 | if("$ENV{TRAVIS_BUILD_NUMBER}" STREQUAL "") 22 | set(APPIMAGED_BUILD_NUMBER "") 23 | else() 24 | set(APPIMAGED_BUILD_NUMBER "$ENV{TRAVIS_BUILD_NUMBER}") 25 | endif() 26 | mark_as_advanced(APPIMAGED_BUILD_NUMBER) 27 | 28 | # get current date 29 | execute_process( 30 | COMMAND env LC_ALL=C date -u "+%Y-%m-%d %H:%M:%S %Z" 31 | OUTPUT_VARIABLE APPIMAGED_BUILD_DATE 32 | OUTPUT_STRIP_TRAILING_WHITESPACE 33 | ) 34 | mark_as_advanced(APPIMAGED_BUILD_DATE) 35 | 36 | add_definitions( 37 | -DAPPIMAGED_GIT_COMMIT="${APPIMAGED_GIT_COMMIT}" 38 | -DAPPIMAGED_BUILD_NUMBER="${APPIMAGED_BUILD_NUMBER}" 39 | -DAPPIMAGED_BUILD_DATE="${APPIMAGED_BUILD_DATE}" 40 | ) 41 | 42 | # C and C++ versions 43 | set(CMAKE_C_STANDARD 99) 44 | set(CMAKE_CXX_STANDARD 98) 45 | set(CMAKE_C_STANDARD_REQUIRED ON) 46 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 47 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 48 | 49 | # include external dependencies 50 | add_subdirectory(lib) 51 | 52 | # include resources 53 | add_subdirectory(resources) 54 | 55 | # include source dir 56 | add_subdirectory(src) 57 | 58 | # include packaging configuration 59 | include(cmake/cpack_debs.cmake) 60 | include(cmake/cpack_rpms.cmake) 61 | # CPack must be included once only, after setting all the variables 62 | include(CPack) 63 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | If not stated otherwise within the individual file or subdirectory, the 4 | original source code in this repository is licensed as below. This does not 5 | necessarily apply for all dependencies. For the sake of clarity, this license 6 | does NOT apply to the contents of AppImages that anyone may create. 7 | Software contained inside an AppImage may be licensed under any license at the 8 | discretion of the respecive rights holder(s). 9 | 10 | Copyright (c) 2004-18 Simon Peter 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining a copy 13 | of this software and associated documentation files (the "Software"), to deal 14 | in the Software without restriction, including without limitation the rights 15 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 16 | copies of the Software, and to permit persons to whom the Software is 17 | furnished to do so, subject to the following conditions: 18 | 19 | The above copyright notice and this permission notice shall be included in all 20 | copies or substantial portions of the Software. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 26 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 27 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 28 | SOFTWARE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # appimaged 2 | 3 | ## :warning: This project has been deprecated in favor of the [new codebase](https://github.com/probonopd/go-appimage). 4 | 5 | `appimaged` is an optional daemon that watches locations like `~/bin` and `~/Downloads` for AppImages and if it detects some, registers them with the system, so that they show up in the menu, have their icons show up, MIME types associated, etc. It also unregisters AppImages again from the system if they are deleted. Optionally you can use a sandbox if you like: If the [firejail](https://github.com/netblue30/firejail) sandbox is installed, it runs the AppImages with it. 6 | -------------------------------------------------------------------------------- /cmake/FindINotify.cmake: -------------------------------------------------------------------------------- 1 | find_path(INOTIFYTOOLS_INCLUDE_DIR sys/inotify.h PATH_SUFFIXES inotify) 2 | mark_as_advanced(INOTIFYTOOLS_INCLUDE_DIR) 3 | 4 | find_library(INOTIFYTOOLS_LIBRARY inotifytools PATH_SUFFIXES lib/inotify) 5 | 6 | get_filename_component(INOTIFYTOOLS_LIBRARY_DIR ${INOTIFYTOOLS_LIBRARY} PATH) 7 | mark_as_advanced(INOTIFYTOOLS_LIBRARY_DIR) 8 | 9 | include(FindPackageHandleStandardArgs) 10 | find_package_handle_standard_args(INOTIFYTOOLS DEFAULT_MSG INOTIFYTOOLS_INCLUDE_DIR INOTIFYTOOLS_LIBRARY_DIR) 11 | 12 | IF(INOTIFYTOOLS_FOUND) 13 | SET(INOTIFYTOOLS_INCLUDE_DIRS ${INOTIFYTOOLS_INCLUDE_DIR}) 14 | SET(INOTIFYTOOLS_LIBRARY_DIRS ${INOTIFYTOOLS_LIBRARY_DIR}) 15 | ENDIF(INOTIFYTOOLS_FOUND) 16 | -------------------------------------------------------------------------------- /cmake/cpack_debs.cmake: -------------------------------------------------------------------------------- 1 | # required for DEB-DEFAULT to work as intended 2 | cmake_minimum_required(VERSION 3.6) 3 | 4 | set(CPACK_GENERATOR "DEB") 5 | 6 | # versioning 7 | # it appears setting CPACK_DEBIAN_PACKAGE_VERSION doesn't work, hence setting CPACK_PACKAGE_VERSION 8 | set(CPACK_PACKAGE_VERSION ${APPIMAGED_VERSION}) 9 | 10 | # use git hash as package release 11 | set(CPACK_DEBIAN_PACKAGE_RELEASE "git${APPIMAGED_GIT_COMMIT}") 12 | 13 | # append build ID, similar to AppImage naming 14 | if(DEFINED ENV{TRAVIS_BUILD_NUMBER}) 15 | set(CPACK_DEBIAN_PACKAGE_RELEASE "${CPACK_DEBIAN_PACKAGE_RELEASE}~travis$ENV{TRAVIS_BUILD_NUMBER}") 16 | else() 17 | set(CPACK_DEBIAN_PACKAGE_RELEASE "${CPACK_DEBIAN_PACKAGE_RELEASE}~local") 18 | endif() 19 | 20 | if(DEFINED ENV{ARCH}) 21 | set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE $ENV{ARCH}) 22 | if(CPACK_DEBIAN_PACKAGE_ARCHITECTURE MATCHES "i686") 23 | set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "i386") 24 | endif() 25 | if(CPACK_DEBIAN_PACKAGE_ARCHITECTURE MATCHES "x86_64") 26 | set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64") 27 | endif() 28 | endif() 29 | 30 | set(CPACK_DEBIAN_PACKAGE_MAINTAINER "AppImage Team") 31 | set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://appimage.org/") 32 | set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) 33 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/README.md") 34 | set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") 35 | 36 | set(CPACK_DEBIAN_APPIMAGED_PACKAGE_NAME "appimaged") 37 | set(CPACK_COMPONENT_APPIMAGED_DESCRIPTION 38 | "Optional AppImage daemon for desktop integration.\n Integrates AppImages into the desktop, e.g., installs icons and menu entries.") 39 | 40 | set(CPACK_DEBIAN_APPIMAGED_PACKAGE_DEPENDS "libarchive13, libc6 (>= 2.4), libglib2.0-0, zlib1g, fuse") 41 | 42 | set(CPACK_COMPONENTS_ALL appimaged) 43 | set(CPACK_DEB_COMPONENT_INSTALL ON) 44 | -------------------------------------------------------------------------------- /cmake/cpack_rpms.cmake: -------------------------------------------------------------------------------- 1 | # required for DEB-DEFAULT to work as intended 2 | cmake_minimum_required(VERSION 3.6) 3 | 4 | # allow building RPM packages on non-RPM systems 5 | if(DEFINED ENV{ARCH}) 6 | set(CPACK_RPM_PACKAGE_ARCHITECTURE $ENV{ARCH}) 7 | if(CPACK_RPM_PACKAGE_ARCHITECTURE MATCHES "i686") 8 | set(CPACK_RPM_PACKAGE_ARCHITECTURE "i386") 9 | elseif(CPACK_RPM_PACKAGE_ARCHITECTURE MATCHES "amd64") 10 | set(CPACK_RPM_PACKAGE_ARCHITECTURE "x86_64") 11 | endif() 12 | endif() 13 | 14 | # override default package naming 15 | set(CPACK_RPM_FILE_NAME RPM-DEFAULT) 16 | 17 | # versioning 18 | set(CPACK_PACKAGE_VERSION ${APPIMAGED_VERSION}) 19 | 20 | # use git hash as package release 21 | set(CPACK_RPM_PACKAGE_RELEASE "git${APPIMAGED_GIT_COMMIT}") 22 | 23 | # append build ID, similar to AppImage naming 24 | if(DEFINED ENV{TRAVIS_BUILD_NUMBER}) 25 | set(CPACK_RPM_PACKAGE_RELEASE "${CPACK_RPM_PACKAGE_RELEASE}~travis$ENV{TRAVIS_BUILD_NUMBER}") 26 | else() 27 | set(CPACK_RPM_PACKAGE_RELEASE "${CPACK_RPM_PACKAGE_RELEASE}~local") 28 | endif() 29 | 30 | # Exclude well known paths or file crash will be reported at the moment of installing 31 | set( 32 | CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION 33 | /usr/share/icons 34 | /usr/share/icons/hicolor 35 | /usr/share/icons/hicolor/scalable 36 | /usr/share/icons/hicolor/scalable/apps 37 | /usr/share/icons/hicolor/128x128 38 | /usr/share/icons/hicolor/128x128/apps 39 | /usr/share/applications 40 | /usr/share/metainfo 41 | /usr/lib/systemd 42 | /usr/lib/systemd/user 43 | ) 44 | 45 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${PROJECT_SOURCE_DIR}/README.md") 46 | set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") 47 | 48 | set(CPACK_RPM_PACKAGE_NAME "appimaged") 49 | set(CPACK_RPM_PACKAGE_SUMMARY 50 | "Optional AppImage daemon for desktop integration. Integrates AppImages into the desktop, e.g., installs icons and menu entries.") 51 | 52 | set(CPACK_RPM_PACKAGE_REQUIRES "libarchive, glib2, fuse") 53 | 54 | set(CPACK_COMPONENTS_ALL appimaged) 55 | set(CPACK_RPM_COMPONENT_INSTALL ON) 56 | -------------------------------------------------------------------------------- /cmake/toolchains/i386-linux-gnu.cmake: -------------------------------------------------------------------------------- 1 | # this toolchain file works for cross compiling on Ubuntu when the following prerequisites are given: 2 | # - all dependencies that would be needed for a normal build must be installed as i386 versions 3 | # - building XZ/liblzma doesn't work yet, so one has to install liblzma-dev:i386 and set -DUSE_SYSTEM_XZ=ON 4 | # - building GTest doesn't work yet, so one has to install libgtest-dev:i386 and set -DUSE_SYSTEM_GTEST=ON 5 | # - building libarchive doesn't work yet, so one has to install liblzma-dev:i386 and set -DUSE_SYSTEM_LIBARCHIVE=ON (TODO: link system libarchive statically like liblzma) 6 | # some of the packets interfere with their x86_64 version (e.g., libfuse-dev:i386, libglib2-dev:i386), so building on a 7 | # normal system will most likely not work, but on systems like Travis it should work fine 8 | 9 | set(CMAKE_SYSTEM_NAME Linux CACHE STRING "" FORCE) 10 | set(CMAKE_SYSTEM_PROCESSOR i386 CACHE STRING "" FORCE) 11 | 12 | set(CMAKE_C_FLAGS "-m32" CACHE STRING "" FORCE) 13 | set(CMAKE_CXX_FLAGS "-m32" CACHE STRING "" FORCE) 14 | 15 | # CMAKE_SHARED_LINKER_FLAGS, CMAKE_STATIC_LINKER_FLAGS etc. must not be set, but CMAKE_EXE_LINKER_FLAGS is necessary 16 | set(CMAKE_EXE_LINKER_FLAGS "-m32" CACHE STRING "" FORCE) 17 | 18 | set(DEPENDENCIES_CFLAGS "-m32" CACHE STRING "" FORCE) 19 | set(DEPENDENCIES_CPPFLAGS "-m32" CACHE STRING "" FORCE) 20 | set(DEPENDENCIES_LDFLAGS "-m32" CACHE STRING "" FORCE) 21 | 22 | # host = target system 23 | # build = build system 24 | # both must be specified 25 | set(EXTRA_CONFIGURE_FLAGS "--host=i686-pc-linux-gnu" "--build=x86_64-pc-linux-gnu" CACHE STRING "" FORCE) 26 | 27 | # may help with some rare issues 28 | set(CMAKE_PREFIX_PATH /usr/lib/i386-linux-gnu CACHE STRING "" FORCE) 29 | 30 | # makes sure that at least on Ubuntu pkg-config will search for the :i386 packages 31 | set(ENV{PKG_CONFIG_PATH} /usr/lib/i386-linux-gnu/pkgconfig/ CACHE STRING "" FORCE) 32 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # include libappimage 2 | add_subdirectory(libappimage EXCLUDE_FROM_ALL) 3 | 4 | 5 | # uses libappimage's scripts 6 | include(ExternalProject) 7 | set(USE_SYSTEM_INOTIFY_TOOLS OFF CACHE BOOL "Use system libinotifytools instead of building our own") 8 | 9 | if(NOT USE_SYSTEM_INOTIFY_TOOLS) 10 | message(STATUS "Downloading and building inotify-tools") 11 | 12 | # TODO: build out of source 13 | ExternalProject_Add(inotify-tools-EXTERNAL 14 | URL https://github.com/downloads/rvoicilas/inotify-tools/inotify-tools-3.14.tar.gz 15 | URL_HASH SHA512=6074d510e89bba5da0d7c4d86f2562c662868666ba0a7ea5d73e53c010a0050dd1fc01959b22cffdb9b8a35bd1b0b43c04d02d6f19927520f05889e8a9297dfb 16 | PATCH_COMMAND ${WGET} -N --content-disposition "https://raw.githubusercontent.com/AppImage/external-resources/master/patches/config.guess" 17 | COMMAND ${WGET} -N --content-disposition "https://raw.githubusercontent.com/AppImage/external-resources/master/patches/config.sub" 18 | UPDATE_COMMAND "" # ${MAKE} sure CMake won't try to fetch updates unnecessarily and hence rebuild the dependency every time 19 | CONFIGURE_COMMAND CC=${CC} CXX=${DEPENDENCIES_CXX} CFLAGS=${DEPENDENCIES_CFLAGS} LDFLAGS=${LDFLAGS} /configure --enable-shared --enable-static --enable-doxygen=no --prefix= --libdir=/lib ${EXTRA_CONFIGURE_FLAGS} 20 | BUILD_COMMAND ${MAKE} 21 | BUILD_IN_SOURCE ON 22 | INSTALL_COMMAND ${MAKE} install 23 | ) 24 | 25 | import_external_project( 26 | TARGET_NAME inotify-tools 27 | EXT_PROJECT_NAME inotify-tools-EXTERNAL 28 | LIBRARIES "/lib/libinotifytools.a" 29 | INCLUDE_DIRS "/include/" 30 | ) 31 | else() 32 | message(STATUS "Using system inotify-tools") 33 | 34 | import_find_pkg_target(inotify-tools INotify INOTIFYTOOLS) 35 | endif() 36 | -------------------------------------------------------------------------------- /resources/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # handles installation of resources 2 | 3 | install( 4 | FILES ${CMAKE_CURRENT_SOURCE_DIR}/appimaged.service 5 | DESTINATION lib/systemd/user/ 6 | COMPONENT appimaged 7 | ) 8 | 9 | install( 10 | FILES ${CMAKE_CURRENT_SOURCE_DIR}/appimaged.desktop 11 | DESTINATION share/applications 12 | COMPONENT appimaged 13 | ) 14 | 15 | install( 16 | FILES ${CMAKE_CURRENT_SOURCE_DIR}/appimaged.png 17 | DESTINATION share/icons/hicolor/128x128/apps 18 | COMPONENT appimaged 19 | ) 20 | 21 | install( 22 | FILES ${CMAKE_CURRENT_SOURCE_DIR}/usr/share/metainfo/appimaged.appdata.xml 23 | DESTINATION share/metainfo 24 | COMPONENT appimaged 25 | ) 26 | -------------------------------------------------------------------------------- /resources/appimaged.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=appimaged 4 | Exec=appimaged 5 | Comment=Optional daemon to integrate AppImages with the desktop 6 | Icon=appimaged 7 | Categories=Development; 8 | Terminal=false 9 | NoDisplay=true 10 | -------------------------------------------------------------------------------- /resources/appimaged.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppImageCommunity/appimaged/11b249848d7d0d9b3b7154ae5fca0328afa167d4/resources/appimaged.png -------------------------------------------------------------------------------- /resources/appimaged.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=AppImage daemon 3 | After=basic.target 4 | [Service] 5 | ExecStart=/usr/bin/appimaged 6 | Restart=always 7 | RestartSec=5s 8 | StartLimitInterval=0 9 | [Install] 10 | WantedBy=graphical.target 11 | -------------------------------------------------------------------------------- /resources/appimaged.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 30 | 32 | 36 | 40 | 41 | 51 | 53 | 57 | 61 | 62 | 72 | 74 | 78 | 82 | 83 | 94 | 96 | 100 | 104 | 105 | 116 | 118 | 122 | 126 | 127 | 137 | 139 | 143 | 147 | 148 | 158 | 160 | 164 | 168 | 169 | 179 | 181 | 185 | 189 | 190 | 198 | 200 | 204 | 208 | 212 | 213 | 214 | 233 | 235 | 236 | 238 | image/svg+xml 239 | 241 | 242 | 243 | 244 | 245 | 249 | 253 | 257 | 264 | 272 | 279 | 280 | 281 | 290 | 294 | 298 | 302 | 311 | 315 | 319 | 320 | 321 | -------------------------------------------------------------------------------- /resources/usr/share/metainfo/appimaged.appdata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | appimaged.desktop 4 | MIT 5 | MIT 6 | appimaged 7 | Register AppImage files with the system 8 | 9 |

10 | appimaged is a daemon that observes direcories and registers 11 | AppImage files with your desktop so that you can upen thesem 12 | from the menu, see their icons, have their MIME types and file associatons 13 | set, can easily update them, and so on. 14 |

15 |

16 | It is an entirely optional component. 17 | It also unregisters AppImages when they are moved away or deleted. 18 |

19 |
20 | 21 | 22 | Update from Ubuntu with AppImageUpdate 23 | https://cloud.githubusercontent.com/assets/2480569/19410850/0390fe9c-92f6-11e6-9882-3ca6d360a190.jpg 24 | 25 | 26 | https://github.com/probonopd/appimagetool 27 | AppImage 28 | 29 | appimaged 30 | 31 |
32 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # required for pkg_check_module's IMPORTED_TARGET 2 | cmake_minimum_required(VERSION 3.6) 3 | 4 | 5 | find_package(PkgConfig) 6 | pkg_check_modules(GLIB glib-2.0 IMPORTED_TARGET) 7 | 8 | add_executable(appimaged main.c notify.c notify.h) 9 | target_link_libraries(appimaged PRIVATE inotify-tools libappimage_static xdg-basedir dl PkgConfig::GLIB) 10 | 11 | install( 12 | TARGETS appimaged 13 | RUNTIME DESTINATION bin COMPONENT appimaged 14 | ) 15 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * 3 | * Copyright (c) 2004-18 Simon Peter 4 | * 5 | * All Rights Reserved. 6 | * 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: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 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 23 | * THE SOFTWARE. 24 | * 25 | **************************************************************************/ 26 | 27 | #ident "AppImage by Simon Peter, http://appimage.org/" 28 | 29 | /* 30 | * Optional daempon to watch directories for AppImages 31 | * and register/unregister them with the system 32 | * 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 38 | */ 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | 49 | #include 50 | #include 51 | 52 | #include 53 | #include 54 | 55 | #include 56 | 57 | #include 58 | #include 59 | 60 | #include "notify.h" 61 | 62 | #ifndef RELEASE_NAME 63 | #define RELEASE_NAME "continuous build" 64 | #endif 65 | 66 | static gboolean verbose = FALSE; 67 | static gboolean showVersionOnly = FALSE; 68 | static gboolean install = FALSE; 69 | static gboolean uninstall = FALSE; 70 | static gboolean no_install = FALSE; 71 | static GMutex print_mutex; 72 | static GMutex time_mutex; 73 | static const gint64 time_update_interval = 3 * 1000000; // 3 seconds (in microseconds) 74 | static gint64 time_last_change = 0; // in microseconds 75 | static gint64 time_last_update = 0; // in microseconds 76 | static const gchar* program_update_desktop = "update-desktop-database"; 77 | static const gchar* program_update_mime = "update-mime-database"; 78 | static const gchar* program_gtk_update_icon_cache = "gtk-update-icon-cache"; 79 | static const gchar* program_kbuildsycoca5 = "kbuildsycoca5"; 80 | static const gchar* cmd_update_desktop = "update-desktop-database ~/.local/share/applications/"; 81 | static const gchar* cmd_update_mime = "update-mime-database ~/.local/share/mime/"; 82 | static const gchar* cmd_gtk_update_icon_cache = "gtk-update-icon-cache ~/.local/share/icons/hicolor/ -t"; 83 | static const gchar* cmd_kbuildsycoca5 = "kbuildsycoca5"; 84 | static gboolean is_update_desktop_available = FALSE; 85 | static gboolean is_update_mime_available = FALSE; 86 | static gboolean is_gtk_update_icon_cache_available = FALSE; 87 | static gboolean is_kbuildsycoca5_available = FALSE; 88 | gchar** remaining_args = NULL; 89 | 90 | static GOptionEntry entries[] = 91 | { 92 | {"verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Be verbose", NULL}, 93 | {"install", 'i', 0, G_OPTION_ARG_NONE, &install, "Install this appimaged instance to $HOME", NULL}, 94 | {"uninstall", 'u', 0, G_OPTION_ARG_NONE, &uninstall, "Uninstall an appimaged instance from $HOME", NULL}, 95 | {"no-install", 'n', 0, G_OPTION_ARG_NONE, &no_install, "Force run without installation", NULL}, 96 | {"version", 0, 0, G_OPTION_ARG_NONE, &showVersionOnly, "Show version number", NULL}, 97 | {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &remaining_args, NULL}, 98 | {NULL} 99 | }; 100 | 101 | #define EXCLUDE_CHUNK 1024 102 | #define WR_EVENTS (IN_CLOSE_WRITE | IN_MOVED_FROM | IN_MOVED_TO | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF) 103 | 104 | // to ensure we don't garble stdout, we have to use this in the threads 105 | #define THREADSAFE_G_PRINT(str, ...) \ 106 | g_mutex_lock(&print_mutex);\ 107 | g_print(str, ##__VA_ARGS__); \ 108 | g_mutex_unlock(&print_mutex) 109 | 110 | /* Run the actual work in treads; 111 | * pthread allows to pass only one argument to the thread function, 112 | * hence we use a struct as the argument in which the real arguments are */ 113 | struct arg_struct { 114 | char* path; 115 | gboolean verbose; 116 | }; 117 | 118 | void update_desktop() { 119 | const gchar* error_msgfmt = "Warning: %s retuned non-zero exit code:\n"; 120 | if (is_update_desktop_available && system(cmd_update_desktop) != 0) { 121 | THREADSAFE_G_PRINT(error_msgfmt, program_update_desktop); 122 | } 123 | if (is_update_mime_available && system(cmd_update_mime) != 0) { 124 | THREADSAFE_G_PRINT(error_msgfmt, program_update_mime); 125 | } 126 | if (is_gtk_update_icon_cache_available && system(cmd_gtk_update_icon_cache) != 0) { 127 | THREADSAFE_G_PRINT(error_msgfmt, program_gtk_update_icon_cache); 128 | } 129 | if (is_kbuildsycoca5_available && system(cmd_kbuildsycoca5) != 0) { 130 | THREADSAFE_G_PRINT(error_msgfmt, program_kbuildsycoca5); 131 | } 132 | } 133 | 134 | void update_desktop_set_dirty() { 135 | gint64 time = g_get_real_time(); 136 | g_mutex_lock(&time_mutex); 137 | time_last_change = time; 138 | // this is very unlikely to happen, but theoretically possible due to 139 | // timer precision. in such an unlikely case we want to just run the update again. 140 | if (time_last_change == time_last_update) { 141 | time_last_change++; 142 | } 143 | g_mutex_unlock(&time_mutex); 144 | } 145 | 146 | bool is_appimage(char* path, gboolean verbose) { 147 | return appimage_get_type(path, verbose) != -1; 148 | } 149 | 150 | 151 | GKeyFile* load_desktop_entry(const char* desktop_file_path) { 152 | GKeyFile* key_file_structure = g_key_file_new(); 153 | gboolean success = g_key_file_load_from_file(key_file_structure, desktop_file_path, 154 | G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL); 155 | 156 | if (!success) { 157 | // Don't remove the brackets or the macro will segfault 158 | THREADSAFE_G_PRINT("Failed to load the deployed desktop entry, '%s'\n", "a"); 159 | } 160 | 161 | return key_file_structure; 162 | } 163 | 164 | void setup_firejail_on_desktop_entry(GKeyFile* key_file_structure) { 165 | char* oldExecValue = g_key_file_get_value(key_file_structure, 166 | G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, NULL); 167 | 168 | char* firejail_exec = g_strdup_printf("firejail --env=DESKTOPINTEGRATION=appimaged --noprofile --appimage %s", 169 | oldExecValue); 170 | g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, G_KEY_FILE_DESKTOP_KEY_EXEC, firejail_exec); 171 | 172 | gchar* firejail_profile_group = "Desktop Action FirejailProfile"; 173 | gchar* firejail_profile_exec = g_strdup_printf( 174 | "firejail --env=DESKTOPINTEGRATION=appimaged --private --appimage %s", oldExecValue); 175 | gchar* firejail_tryexec = "firejail"; 176 | g_key_file_set_value(key_file_structure, firejail_profile_group, G_KEY_FILE_DESKTOP_KEY_NAME, 177 | "Run without sandbox profile"); 178 | g_key_file_set_value(key_file_structure, firejail_profile_group, G_KEY_FILE_DESKTOP_KEY_EXEC, 179 | firejail_profile_exec); 180 | g_key_file_set_value(key_file_structure, firejail_profile_group, G_KEY_FILE_DESKTOP_KEY_TRY_EXEC, 181 | firejail_tryexec); 182 | g_key_file_set_value(key_file_structure, G_KEY_FILE_DESKTOP_GROUP, "Actions", "FirejailProfile;"); 183 | 184 | g_free(firejail_profile_exec); 185 | g_free(oldExecValue); 186 | g_free(firejail_exec); 187 | } 188 | 189 | void save_desktop_entry(GKeyFile* key_file_structure, const char* desktop_file_path) { 190 | gboolean success = g_key_file_save_to_file(key_file_structure, desktop_file_path, NULL); 191 | 192 | if (!success) { 193 | // Don't remove the brackets or the macro will segfault 194 | THREADSAFE_G_PRINT("Failed to save the deployed desktop entry\n"); 195 | } 196 | } 197 | 198 | 199 | void enable_firejail_if_available(const char* appimage_path) { 200 | /* If firejail is on the $PATH, then use it to run AppImages */ 201 | char* firejail_paht = g_find_program_in_path("firejail"); 202 | if (firejail_paht) { 203 | const char* desktop_file_path = appimage_registered_desktop_file_path(appimage_path, NULL, false); 204 | 205 | if (desktop_file_path != NULL && g_file_test(desktop_file_path, G_FILE_TEST_EXISTS)) { 206 | GKeyFile* key_file_structure = load_desktop_entry(desktop_file_path); 207 | 208 | setup_firejail_on_desktop_entry(key_file_structure); 209 | save_desktop_entry(key_file_structure, desktop_file_path); 210 | 211 | g_key_file_unref(key_file_structure); 212 | g_free(firejail_paht); 213 | } 214 | 215 | g_free(desktop_file_path); 216 | } 217 | } 218 | 219 | void* thread_appimage_register_in_system(void* arguments) { 220 | struct arg_struct* args = arguments; 221 | if (args->verbose) { 222 | THREADSAFE_G_PRINT("%s (%s)\n", __FUNCTION__, args->path); 223 | } 224 | 225 | bool is_appimage_result = is_appimage(args->path, args->verbose); 226 | bool appimage_is_registered_in_system_result = is_appimage_result && appimage_is_registered_in_system(args->path); 227 | if (is_appimage_result && !appimage_is_registered_in_system_result) { 228 | int failed = appimage_register_in_system(args->path, args->verbose); 229 | 230 | if (!failed) { 231 | enable_firejail_if_available(args->path); 232 | update_desktop_set_dirty(); 233 | } 234 | 235 | if (args->verbose) { 236 | THREADSAFE_G_PRINT("appimage_register_in_system result: %d\n", failed); 237 | } 238 | 239 | } else if (args->verbose) { 240 | THREADSAFE_G_PRINT("appimage_register_in_system call skipped. " 241 | "is_appimage_result: %d appimage_is_registered_in_system_result: %d\n", 242 | is_appimage_result, appimage_is_registered_in_system_result); 243 | } 244 | 245 | pthread_exit(NULL); 246 | } 247 | 248 | void* thread_appimage_unregister_in_system(void* arguments) { 249 | struct arg_struct* args = arguments; 250 | if (args->verbose) { 251 | THREADSAFE_G_PRINT("%s (%s)\n", __FUNCTION__, args->path); 252 | } 253 | 254 | bool result = appimage_unregister_in_system(args->path, args->verbose); 255 | if (args->verbose) { 256 | THREADSAFE_G_PRINT("appimage_unregister_in_system (%s): $d\n", __FUNCTION__, args->path, result); 257 | } 258 | update_desktop_set_dirty(); 259 | pthread_exit(NULL); 260 | } 261 | 262 | // thread which checks if an update of the desktop is necessary and updates it accordingly. 263 | void* thread_update_desktop() { 264 | while (TRUE) { 265 | gboolean do_update = FALSE; 266 | 267 | // update only after a specific interval (time_update_interval) has passed since the last change. 268 | // the lock is here to ensure that the desktop is never in an inconsistent state. 269 | g_mutex_lock(&time_mutex); 270 | if (time_last_change != time_last_update && g_get_real_time() > time_last_change + time_update_interval) { 271 | time_last_update = g_get_real_time(); 272 | time_last_change = time_last_update; 273 | do_update = TRUE; 274 | } 275 | g_mutex_unlock(&time_mutex); 276 | 277 | if (do_update) { 278 | THREADSAFE_G_PRINT("Updating desktop...\n"); 279 | gint64 update_start = g_get_real_time(); 280 | update_desktop(); 281 | gint64 update_end = g_get_real_time(); 282 | THREADSAFE_G_PRINT("Finished updating desktop in %ld milliseconds.\n", (update_end - update_start) / 1000); 283 | } 284 | 285 | // sleep one second 286 | g_usleep(1000000); 287 | } 288 | } 289 | 290 | // check the availability of a single program in the $PATH. 291 | gboolean check_for_program(const gchar* program_name) { 292 | gboolean result = FALSE; 293 | 294 | gchar* tmp = g_find_program_in_path(program_name); 295 | if (tmp != NULL) { 296 | g_free(tmp); 297 | result = TRUE; 298 | } 299 | 300 | return result; 301 | } 302 | 303 | // check if update programs are available 304 | void check_update_programs() { 305 | is_update_desktop_available = check_for_program(program_update_desktop); 306 | is_update_mime_available = check_for_program(program_update_mime); 307 | is_gtk_update_icon_cache_available = check_for_program(program_gtk_update_icon_cache); 308 | is_kbuildsycoca5_available = check_for_program(program_kbuildsycoca5); 309 | } 310 | 311 | /* Recursively process the files in this directory and its subdirectories, 312 | * http://stackoverflow.com/questions/8436841/how-to-recursively-list-directories-in-c-on-linux 313 | */ 314 | void initially_register(const char* name, int level) { 315 | DIR* dir; 316 | struct dirent* entry; 317 | 318 | if (!(dir = opendir(name))) { 319 | if (verbose) { 320 | if (errno == EACCES) { 321 | THREADSAFE_G_PRINT("_________________________\nPermission denied on dir '%s'\n", name); 322 | } else { 323 | THREADSAFE_G_PRINT("_________________________\nFailed to open dir '%s'\n", name); 324 | } 325 | } 326 | closedir(dir); 327 | return; 328 | } 329 | 330 | if (!(entry = readdir(dir))) { 331 | if (verbose) { 332 | THREADSAFE_G_PRINT("_________________________\nInvalid directory stream descriptor '%s'\n", name); 333 | } 334 | closedir(dir); 335 | return; 336 | } 337 | 338 | do { 339 | if (entry->d_type == DT_DIR) { 340 | char path[1024]; 341 | int len = snprintf(path, sizeof(path) - 1, "%s/%s", name, entry->d_name); 342 | path[len] = 0; 343 | if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) 344 | continue; 345 | initially_register(path, level + 1); 346 | } else { 347 | int ret; 348 | gchar* absolute_path = g_build_path(G_DIR_SEPARATOR_S, name, entry->d_name, NULL); 349 | if (g_file_test(absolute_path, G_FILE_TEST_IS_REGULAR)) { 350 | pthread_t some_thread; 351 | struct arg_struct args; 352 | args.path = absolute_path; 353 | args.verbose = verbose; 354 | ret = pthread_create(&some_thread, NULL, thread_appimage_register_in_system, &args); 355 | if (!ret) { 356 | pthread_join(some_thread, NULL); 357 | } 358 | } 359 | g_free(absolute_path); 360 | } 361 | } while ((entry = readdir(dir)) != NULL); 362 | closedir(dir); 363 | } 364 | 365 | void add_dir_to_watch(const char* directory) { 366 | GError* err = NULL; 367 | 368 | // XXX: Fails silently if file doesn’t exist. Maybe log? 369 | if (NULL == directory || !g_file_test(directory, G_FILE_TEST_EXISTS)) { 370 | return; 371 | } 372 | 373 | // Follow symlinks. 374 | const char* realdir = g_file_test(directory, G_FILE_TEST_IS_SYMLINK) 375 | ? g_file_read_link(directory, &err) 376 | : directory; 377 | 378 | if (NULL != err) { 379 | THREADSAFE_G_PRINT("Error #%d following symlink %s: %s\n", 380 | err->code, directory, err->message); 381 | return; 382 | } 383 | 384 | if (g_file_test(realdir, G_FILE_TEST_IS_DIR)) { 385 | if (!inotifytools_watch_file(realdir, WR_EVENTS)) { 386 | fprintf(stderr, "%s: %s\n", realdir, strerror(inotifytools_error())); 387 | exit(1); 388 | } 389 | initially_register(realdir, 0); 390 | THREADSAFE_G_PRINT("Watching %s\n", realdir); 391 | } 392 | } 393 | 394 | void handle_event(struct inotify_event* event) { 395 | int ret; 396 | gchar* absolute_path = g_build_path(G_DIR_SEPARATOR_S, inotifytools_filename_from_wd(event->wd), event->name, NULL); 397 | 398 | if ((event->mask & IN_CLOSE_WRITE) | (event->mask & IN_MOVED_TO)) { 399 | if (g_file_test(absolute_path, G_FILE_TEST_IS_REGULAR)) { 400 | pthread_t some_thread; 401 | struct arg_struct args; 402 | args.path = absolute_path; 403 | args.verbose = verbose; 404 | g_print("_________________________\n"); 405 | ret = pthread_create(&some_thread, NULL, thread_appimage_register_in_system, &args); 406 | if (!ret) { 407 | pthread_join(some_thread, NULL); 408 | } 409 | } 410 | } 411 | 412 | if ((event->mask & IN_MOVED_FROM) | (event->mask & IN_DELETE)) { 413 | pthread_t some_thread; 414 | struct arg_struct args; 415 | args.path = absolute_path; 416 | args.verbose = verbose; 417 | g_print("_________________________\n"); 418 | ret = pthread_create(&some_thread, NULL, thread_appimage_unregister_in_system, &args); 419 | if (!ret) { 420 | pthread_join(some_thread, NULL); 421 | } 422 | } 423 | 424 | g_free(absolute_path); 425 | 426 | /* Too many FS events were received, some event notifications were potentially lost */ 427 | if (event->mask & IN_Q_OVERFLOW) { 428 | printf("Warning: AN OVERFLOW EVENT OCCURRED\n"); 429 | } 430 | 431 | if (event->mask & IN_IGNORED) { 432 | printf("Warning: AN IN_IGNORED EVENT OCCURRED\n"); 433 | } 434 | 435 | } 436 | 437 | int main(int argc, char** argv) { 438 | GError* error = NULL; 439 | GOptionContext* context; 440 | 441 | context = g_option_context_new(""); 442 | g_option_context_add_main_entries(context, entries, NULL); 443 | // g_option_context_add_group (context, gtk_get_option_group (TRUE)); 444 | if (!g_option_context_parse(context, &argc, &argv, &error)) { 445 | g_print("option parsing failed: %s\n", error->message); 446 | exit(1); 447 | } 448 | 449 | // always show version, but exit immediately if only the version number was requested 450 | fprintf( 451 | stderr, 452 | "appimaged, %s (commit %s), build %s built on %s\n", 453 | RELEASE_NAME, APPIMAGED_GIT_COMMIT, APPIMAGED_BUILD_NUMBER, APPIMAGED_BUILD_DATE 454 | ); 455 | 456 | if (showVersionOnly) 457 | exit(0); 458 | 459 | if (!inotifytools_initialize()) { 460 | fprintf(stderr, "inotifytools_initialize error\n"); 461 | exit(1); 462 | } 463 | 464 | gchar* user_bin_dir = g_build_filename(g_get_home_dir(), "/.local/bin", NULL); 465 | gchar* installed_appimaged_location = g_build_filename(user_bin_dir, "appimaged", NULL); 466 | const gchar* appimage_location = g_getenv("APPIMAGE"); 467 | gchar* own_desktop_file_location = g_build_filename(g_getenv("APPDIR"), "/appimaged.desktop", NULL); 468 | gchar* global_autostart_file = "/etc/xdg/autostart/appimaged.desktop"; 469 | gchar* global_systemd_file = "/usr/lib/systemd/user/appimaged.service"; 470 | gchar* partial_path = g_strdup_printf("autostart/appimagekit-appimaged.desktop"); 471 | char* config_home = xdg_config_home(); 472 | gchar* destination = g_build_filename(config_home, partial_path, NULL); 473 | free(config_home); 474 | 475 | if (uninstall) { 476 | if (g_file_test(installed_appimaged_location, G_FILE_TEST_EXISTS)) 477 | fprintf(stderr, "* Please delete %s\n", installed_appimaged_location); 478 | if (g_file_test(destination, G_FILE_TEST_EXISTS)) 479 | fprintf(stderr, "* Please delete %s\n", destination); 480 | fprintf(stderr, "* To remove all AppImage desktop integration, run\n"); 481 | fprintf(stderr, " find ~/.local/share -name 'appimagekit_*' -exec rm {} \\;\n\n"); 482 | exit(0); 483 | } 484 | 485 | if (install) { 486 | if (((appimage_location != NULL)) && ((own_desktop_file_location != NULL))) { 487 | printf("Running from within %s\n", appimage_location); 488 | if ((!g_file_test("/usr/bin/appimaged", G_FILE_TEST_EXISTS)) && 489 | (!g_file_test(global_autostart_file, G_FILE_TEST_EXISTS)) && 490 | (!g_file_test(global_systemd_file, G_FILE_TEST_EXISTS))) { 491 | printf("%s is not installed, moving it to %s\n", argv[0], installed_appimaged_location); 492 | g_mkdir_with_parents(user_bin_dir, 0755); 493 | gchar* command = g_strdup_printf("mv \"%s\" \"%s\"", appimage_location, installed_appimaged_location); 494 | system(command); 495 | /* When appimaged installs itself, then to the $XDG_CONFIG_HOME/autostart/ directory, falling back to ~/.config/autostart/ */ 496 | fprintf(stderr, "Installing to autostart: %s\n", own_desktop_file_location); 497 | g_mkdir_with_parents(g_path_get_dirname(destination), 0755); 498 | gchar* command2 = g_strdup_printf("cp \"%s\" \"%s\"", own_desktop_file_location, destination); 499 | system(command2); 500 | if (g_file_test(installed_appimaged_location, G_FILE_TEST_EXISTS)) 501 | fprintf(stderr, "* Installed %s\n", installed_appimaged_location); 502 | if (g_file_test(destination, G_FILE_TEST_EXISTS)) { 503 | gchar* command3 = g_strdup_printf("sed -i -e 's|^Exec=.*|Exec=%s|g' '%s'", 504 | installed_appimaged_location, destination); 505 | if (verbose) 506 | fprintf(stderr, "%s\n", command3); 507 | system(command3); 508 | fprintf(stderr, "* Installed %s\n", destination); 509 | } 510 | if (g_file_test(installed_appimaged_location, G_FILE_TEST_EXISTS)) 511 | fprintf(stderr, "\nTo uninstall, run %s --uninstall and follow the instructions\n\n", 512 | installed_appimaged_location); 513 | char* title; 514 | char* body; 515 | title = g_strdup_printf("Please log out"); 516 | body = g_strdup_printf("and log in again to complete the installation"); 517 | notify(title, body, 15); 518 | exit(0); 519 | } 520 | } else { 521 | printf("Not running from within an AppImage. This binary cannot be installed in this way.\n"); 522 | exit(1); 523 | } 524 | } 525 | 526 | /* When we run from inside an AppImage, then we check if we are installed 527 | * in a per-user location and if not, we install ourselves there */ 528 | if (!no_install && (appimage_location != NULL && own_desktop_file_location != NULL)) { 529 | if ((!g_file_test("/usr/bin/appimaged", G_FILE_TEST_EXISTS)) && 530 | ((!g_file_test(global_autostart_file, G_FILE_TEST_EXISTS)) || 531 | (!g_file_test(destination, G_FILE_TEST_EXISTS))) && 532 | (!g_file_test(global_systemd_file, G_FILE_TEST_EXISTS)) && 533 | (!g_file_test(installed_appimaged_location, G_FILE_TEST_EXISTS)) && 534 | (g_file_test(own_desktop_file_location, G_FILE_TEST_IS_REGULAR))) { 535 | char* title; 536 | char* body; 537 | title = g_strdup_printf("Not installed\n"); 538 | body = g_strdup_printf("Please run %s --install", argv[0]); 539 | notify(title, body, 15); 540 | exit(1); 541 | } 542 | } 543 | 544 | // check which update programs are available. 545 | check_update_programs(); 546 | 547 | // Workaround for: Directory '/home/me/.local/share/mime/packages' does not exist! # https://github.com/AppImage/appimaged/issues/93 548 | g_mkdir_with_parents(g_build_filename(g_get_home_dir(), ".local/share/mime/packages", NULL), 0755); 549 | 550 | // set the time 551 | time_last_update = g_get_real_time(); 552 | time_last_change = time_last_update; 553 | 554 | // launch the update thread 555 | pthread_t update_thread; 556 | if (pthread_create(&update_thread, NULL, thread_update_desktop, NULL) != 0) { 557 | THREADSAFE_G_PRINT("Failed to create update thread."); 558 | exit(1); 559 | } 560 | 561 | add_dir_to_watch(user_bin_dir); 562 | add_dir_to_watch(g_get_user_special_dir(G_USER_DIRECTORY_DOWNLOAD)); 563 | add_dir_to_watch(g_build_filename(g_get_home_dir(), "/bin", NULL)); 564 | add_dir_to_watch(g_build_filename(g_get_home_dir(), "/.bin", NULL)); 565 | add_dir_to_watch(g_build_filename(g_get_home_dir(), "/Applications", NULL)); 566 | add_dir_to_watch(g_build_filename("/Applications", NULL)); 567 | add_dir_to_watch(g_build_filename("/opt", NULL)); 568 | add_dir_to_watch(g_build_filename("/usr/local/bin", NULL)); 569 | 570 | // Watch "/Applications" on all mounted partitions, if it exists. 571 | // TODO: Notice when partitions are mounted and unmounted (patches welcome!) 572 | struct mntent* ent; 573 | FILE* aFile; 574 | aFile = setmntent("/proc/mounts", "r"); 575 | if (aFile == NULL) { 576 | perror("setmntent"); 577 | exit(1); 578 | } 579 | while (NULL != (ent = getmntent(aFile))) { 580 | gchar* applicationsdir = NULL; 581 | applicationsdir = g_build_filename(ent->mnt_dir, "Applications", NULL); 582 | if (applicationsdir != NULL) { 583 | if (g_file_test(applicationsdir, G_FILE_TEST_IS_DIR)) { 584 | add_dir_to_watch(applicationsdir); 585 | } 586 | } 587 | g_free(applicationsdir); 588 | } 589 | endmntent(aFile); 590 | 591 | struct inotify_event* event = inotifytools_next_event(-1); 592 | while (event) { 593 | if (verbose) { 594 | inotifytools_printf(event, "%w%f %e\n"); 595 | } 596 | fflush(stdout); 597 | handle_event(event); 598 | fflush(stdout); 599 | event = inotifytools_next_event(-1); 600 | } 601 | } 602 | -------------------------------------------------------------------------------- /src/notify.c: -------------------------------------------------------------------------------- 1 | // copied from https://github.com/AppImage/AppImageKit 2 | // Copyright (C) AppImage team 2017 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* Try to show a notification on the GUI, fall back to the command line 10 | * timeout is the timeout in milliseconds. timeout = NULL seems to trigger a 11 | * GUI error dialog rather than a notification */ 12 | int notify(char *title, char *body, int timeout) 13 | { 14 | /* http://stackoverflow.com/questions/13204177/how-to-find-out-if-running-from-terminal-or-gui */ 15 | if (isatty(fileno(stdin))){ 16 | /* We were launched from the command line. */ 17 | printf("\n%s\n", title); 18 | printf("%s\n", body); 19 | } 20 | else 21 | { 22 | /* We were launched from inside the desktop */ 23 | printf("\n%s\n", title); 24 | printf("%s\n", body); 25 | /* https://debian-administration.org/article/407/Creating_desktop_notifications */ 26 | void *handle, *n; 27 | typedef void (*notify_init_t)(char *); 28 | typedef void *(*notify_notification_new_t)( char *, char *, char *, char *); 29 | typedef void (*notify_notification_set_timeout_t)( void *, int ); 30 | typedef void (*notify_notification_show_t)(void *, char *); 31 | handle = NULL; 32 | if(handle == NULL) 33 | handle= dlopen("libnotify.so.3", RTLD_LAZY); 34 | if(handle == NULL) 35 | handle= dlopen("libnotify.so.4", RTLD_LAZY); 36 | if(handle == NULL) 37 | handle= dlopen("libnotify.so.5", RTLD_LAZY); 38 | if(handle == NULL) 39 | handle= dlopen("libnotify.so.6", RTLD_LAZY); 40 | if(handle == NULL) 41 | handle= dlopen("libnotify.so.7", RTLD_LAZY); 42 | if(handle == NULL) 43 | handle= dlopen("libnotify.so.8", RTLD_LAZY); 44 | 45 | if(handle == NULL) 46 | { 47 | printf("Failed to open libnotify.\n\n" ); 48 | } 49 | notify_init_t init = (notify_init_t)dlsym(handle, "notify_init"); 50 | if ( init == NULL ) 51 | { 52 | dlclose( handle ); 53 | return 1; 54 | } 55 | init("AppImage"); 56 | 57 | notify_notification_new_t nnn = (notify_notification_new_t)dlsym(handle, "notify_notification_new"); 58 | if ( nnn == NULL ) 59 | { 60 | dlclose( handle ); 61 | return 1; 62 | } 63 | n = nnn(title, body, NULL, NULL); 64 | notify_notification_set_timeout_t nnst = (notify_notification_set_timeout_t)dlsym(handle, "notify_notification_set_timeout"); 65 | if ( nnst == NULL ) 66 | { 67 | dlclose( handle ); 68 | return 1; 69 | } 70 | nnst(n, timeout); 71 | notify_notification_show_t show = (notify_notification_show_t)dlsym(handle, "notify_notification_show"); 72 | if ( init == NULL ) 73 | { 74 | dlclose( handle ); 75 | return 1; 76 | } 77 | show(n, NULL ); 78 | dlclose(handle ); 79 | } 80 | return 0; 81 | } 82 | 83 | /* 84 | int main( int argc, char *argv[] ) 85 | { 86 | char *title; 87 | char *body; 88 | printf("%s\n", "Test"); 89 | title = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua."; 90 | body = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."; 91 | notify(title, body, 3000); // 3 seconds timeout 92 | return 0; 93 | } 94 | */ 95 | -------------------------------------------------------------------------------- /src/notify.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int notify(char *title, char *body, int timeout); 4 | -------------------------------------------------------------------------------- /travis/check_latest_libappimage_tag.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | pushd lib/libappimage 4 | 5 | latesttag=$(git describe --tags) 6 | echo "checking out libappimage: ${latesttag}" 7 | git checkout "${latesttag}" 8 | 9 | popd 10 | -------------------------------------------------------------------------------- /travis/test-appimage.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # now check appimaged 4 | timeout "$TIMEOUT" out/appimaged-"$ARCH".AppImage --no-install 5 | 6 | if [ $? -ne 124 ]; then 7 | echo "Error: appimaged was not terminated by timeout as expected" >&2 8 | exit 1 9 | fi 10 | -------------------------------------------------------------------------------- /travis/travis-build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -x 4 | set -e 5 | 6 | if [ "$ARCH" == "" ]; then 7 | echo "Error: \$ARCH not set" 8 | exit 1 9 | fi 10 | 11 | TEMP_BASE=/tmp 12 | 13 | BUILD_DIR=$(mktemp -d -p "$TEMP_BASE" appimaged-build-XXXXXX) 14 | 15 | cleanup () { 16 | if [ -d "$BUILD_DIR" ]; then 17 | rm -rf "$BUILD_DIR" 18 | fi 19 | } 20 | 21 | trap cleanup EXIT 22 | 23 | # store repo root as variable 24 | REPO_ROOT=$(readlink -f $(dirname $(dirname $0))) 25 | OLD_CWD=$(readlink -f .) 26 | 27 | if [ "$CI" != "" ] && [ "$KEY" != "" ]; then 28 | # clean up and download data from GitHub 29 | # don't worry about removing those data -- they'll be removed by the exit hook 30 | wget https://github.com/AppImage/AppImageKit/files/584665/data.zip -O data.tar.gz.gpg 31 | set +x ; echo "$KEY" | gpg2 --batch --passphrase-fd 0 --no-tty --skip-verify --output data.tar.gz --decrypt data.tar.gz.gpg || true 32 | tar xf data.tar.gz 33 | chown -R "$USER" .gnu*/ 34 | chmod 0700 .gnu*/ 35 | export GNUPGHOME=$(readlink -f .gnu*/) 36 | fi 37 | 38 | pushd "$BUILD_DIR" 39 | 40 | ## build up-to-date version of CMake 41 | #wget https://cmake.org/files/v3.12/cmake-3.12.0.tar.gz -O- | tar xz 42 | #pushd cmake-*/ 43 | #./configure --prefix="$BUILD_DIR"/bin 44 | #make install -j$(nproc) 45 | #popd 46 | # 47 | #export PATH="$BUILD_DIR"/bin:"$PATH" 48 | 49 | if [ "$ARCH" == "i386" ]; then 50 | export EXTRA_CMAKE_ARGS=("-DCMAKE_TOOLCHAIN_FILE=$REPO_ROOT/cmake/toolchains/i386-linux-gnu.cmake") 51 | fi 52 | 53 | cmake "$REPO_ROOT" -DCMAKE_INSTALL_PREFIX=/usr "${EXTRA_CMAKE_ARGS[@]}" 54 | 55 | make -j$(nproc) 56 | 57 | # make .deb 58 | cpack -V -G DEB 59 | 60 | # make .rpm 61 | cpack -V -G RPM 62 | 63 | # make AppImages 64 | mkdir -p appdir 65 | make install DESTDIR=appdir 66 | 67 | wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-"$ARCH".AppImage 68 | chmod +x linuxdeploy-"$ARCH".AppImage 69 | ./linuxdeploy-"$ARCH".AppImage --appimage-extract 70 | mv squashfs-root/ linuxdeploy/ 71 | 72 | linuxdeploy/AppRun --list-plugins 73 | 74 | export UPDATE_INFORMATION="gh-releases-zsync|AppImage|appimaged|continuous|appimaged*$ARCH*.AppImage.zsync" 75 | export SIGN=1 76 | export VERBOSE=1 77 | linuxdeploy/AppRun --appdir appdir --output appimage 78 | 79 | mv appimaged*.{AppImage,deb,rpm}* "$OLD_CWD/" 80 | --------------------------------------------------------------------------------