├── .gitignore ├── .gitmodules ├── ci ├── Dockerfile └── install-deps.sh ├── resources ├── appimage-thumbnailer.thumbnailer ├── appimage-thumbnailer.desktop └── CMakeLists.txt ├── lib └── CMakeLists.txt ├── src ├── appimage-icon-extract.h ├── appimage-icon-extract.c ├── kde-appimage-thumbnailer.cpp ├── CMakeLists.txt └── gnome-appimage-thumbnailer.c ├── .github └── workflows │ └── ci.yml ├── cmake ├── cpack_settings.cmake └── cpack_deb.cmake ├── README.md ├── CMakeLists.txt ├── LICENSE └── Makefile /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | packages/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/libappimage"] 2 | path = lib/libappimage 3 | url = https://github.com/AppImage/libappimage 4 | -------------------------------------------------------------------------------- /ci/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster 2 | ENV CI=1 3 | COPY ./install-deps.sh / 4 | RUN bash -xe install-deps.sh 5 | WORKDIR /ws 6 | -------------------------------------------------------------------------------- /resources/appimage-thumbnailer.thumbnailer: -------------------------------------------------------------------------------- 1 | [Thumbnailer Entry] 2 | TryExec=appimage-thumbnailer 3 | Exec=appimage-thumbnailer %i %o %s 4 | MimeType=application/vnd.appimage;application/x-iso9660-appimage; 5 | 6 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(LIBAPPIMAGE_DESKTOP_INTEGRATION_ENABLED OFF CACHE BOOL "") 2 | set(LIBAPPIMAGE_THUMBNAILER_ENABLED OFF CACHE BOOL "") 3 | 4 | if(NOT USE_SYSTEM_LIBAPPIMAGE) 5 | add_subdirectory(libappimage EXCLUDE_FROM_ALL) 6 | endif() 7 | -------------------------------------------------------------------------------- /resources/appimage-thumbnailer.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Service 3 | Name=AppImage Thumbnailer 4 | X-KDE-ServiceTypes=ThumbCreator 5 | MimeType=application/vnd.appimage;application/x-iso9660-appimage; 6 | X-KDE-Library=appimage-thumbnailer 7 | CacheThumbnail=true 8 | -------------------------------------------------------------------------------- /src/appimage-icon-extract.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Matteo Bernardini 3 | */ 4 | 5 | #pragma once 6 | #include 7 | #include 8 | 9 | typedef struct { 10 | uint8_t* data; 11 | size_t data_size; 12 | } ait_icon_t; 13 | 14 | ait_icon_t* appimage_thumbnailer_icon_extract(char const* path_to_appimage, unsigned const preferred_size); 15 | void appimage_thumbnailer_destroy_icon(ait_icon_t* icon); 16 | -------------------------------------------------------------------------------- /resources/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # == gnome thumbnailer == 2 | install( 3 | FILES appimage-thumbnailer.thumbnailer 4 | DESTINATION ${CMAKE_INSTALL_DATADIR}/thumbnailers/ 5 | COMPONENT appimage-thumbnailer-gnome 6 | EXCLUDE_FROM_ALL 7 | ) 8 | 9 | # == kde thumbnailer == 10 | install( 11 | FILES appimage-thumbnailer.desktop 12 | DESTINATION ${SERVICES_INSTALL_DIR}/ 13 | COMPONENT appimage-thumbnailer-kde 14 | EXCLUDE_FROM_ALL 15 | ) 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous builds 2 | on: push 3 | 4 | jobs: 5 | build: 6 | name: Build Thumbnailer 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Checkout 🛎️ 11 | uses: actions/checkout@v2 12 | with: 13 | submodules: 'recursive' 14 | 15 | - name: Restore Docker Cache 🔧 16 | uses: satackey/action-docker-layer-caching@v0.0.11 17 | continue-on-error: true 18 | 19 | - name: Build 🔧 20 | run: make ci 21 | -------------------------------------------------------------------------------- /cmake/cpack_settings.cmake: -------------------------------------------------------------------------------- 1 | # general CPack options 2 | 3 | set(PROJECT_VERSION ${APPIMAGETHUMBNAILER_VERSION}) 4 | set(CPACK_GENERATOR "DEB") 5 | set(CPACK_COMPONENTS_ALL appimage-thumbnailer-gnome appimage-thumbnailer-kde) 6 | 7 | # global options 8 | set(CPACK_PACKAGE_CONTACT "Matteo Bernardini") 9 | set(CPACK_PACKAGE_HOMEPAGE "https://github.com/mttbernardini/appimage-thumbnailer") 10 | set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") 11 | 12 | # versioning 13 | set(CPACK_PACKAGE_VERSION ${APPIMAGETHUMBNAILER_VERSION}) 14 | -------------------------------------------------------------------------------- /cmake/cpack_deb.cmake: -------------------------------------------------------------------------------- 1 | set(CPACK_DEB_COMPONENT_INSTALL ON) 2 | set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) 3 | set(CPACK_DEBIAN_COMPRESSION_TYPE xz) 4 | set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON) 5 | set(CPACK_DEBIAN_PACKAGE_DEPENDS "shared-mime-info") # provides appimage mime type 6 | set(CPACK_DEBIAN_PACKAGE_SUGGESTS "appimagelauncher") 7 | 8 | # gnome thumbnailer 9 | set(CPACK_DEBIAN_APPIMAGE-THUMBNAILER-GNOME_PACKAGE_NAME "gnome-appimage-thumbnailer") 10 | 11 | # kde thumbnailer 12 | set(CPACK_DEBIAN_APPIMAGE-THUMBNAILER-KDE_PACKAGE_NAME "kde-appimage-thumbnailer") 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thumbnailer for AppImages 2 | 3 | This project contains a thumbnailer for AppImages, compatible with GNOME and KDE based DEs. 4 | 5 | For now the code is still in draft and has not been thoroughly tested yet. 6 | 7 | ## Building 8 | 9 | Clone this repository. Remember to clone submodules as well for dependencies (`libappimage`): 10 | 11 | ```sh 12 | git clone --recursive https://github.com/mttbernardini/appimage-thumbnailer 13 | ``` 14 | 15 | A `Makefile` is provided to run the build process in a isolated Docker container: 16 | 17 | ```sh 18 | make help 19 | ``` 20 | 21 | ## Roadmap 22 | 23 | - [x] Write thumbnailer in C/C++ to make it usable by both GNOME and KDE, using `libappimage` 24 | - [x] Set up build system and dependencies 25 | - [ ] Set up distro packaging under the name of `appimage-thumbnailer` 26 | 27 | --- 28 | © 2021 Matteo Bernardini 29 | 30 | Licensed under the MIT License 31 | -------------------------------------------------------------------------------- /ci/install-deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [[ "$CI" == "" ]]; then 4 | echo "Caution: this script is supposed to run inside a (disposable) CI environment" 5 | echo "It will alter a system, and should not be run on workstations or alike" 6 | echo "You can export CI=1 to prevent this error from being shown again" 7 | exit 3 8 | fi 9 | 10 | packages=( 11 | # libappimage deps 12 | libfuse-dev 13 | desktop-file-utils 14 | ca-certificates 15 | gcc-multilib 16 | g++-multilib 17 | make 18 | build-essential 19 | git 20 | automake 21 | autoconf 22 | libtool 23 | patch 24 | wget 25 | vim-common 26 | desktop-file-utils 27 | pkg-config 28 | libarchive-dev 29 | librsvg2-dev 30 | librsvg2-bin 31 | liblzma-dev 32 | cmake 33 | lcov 34 | gcovr 35 | 36 | # gnome-part deps 37 | 38 | # kde-part deps 39 | libkf5kio-dev 40 | extra-cmake-modules 41 | ) 42 | 43 | apt-get update 44 | apt-get -y --no-install-recommends install "${packages[@]}" 45 | -------------------------------------------------------------------------------- /src/appimage-icon-extract.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Matteo Bernardini 3 | */ 4 | 5 | #include 6 | #include "appimage-icon-extract.h" 7 | 8 | ait_icon_t* appimage_thumbnailer_icon_extract(char const* path_to_appimage, unsigned const preferred_size) 9 | { 10 | /** 11 | * Extracts AppImage icon. If an icon of `preferred_size` is found it will be returned, otherwise fallback to `.DirIcon`. 12 | * If not found (non-compliant AppImage), `NULL` is returned 13 | */ 14 | 15 | ait_icon_t* icon = malloc(sizeof(ait_icon_t)); 16 | if (!icon) 17 | { 18 | return NULL; 19 | } 20 | 21 | bool found = appimage_read_file_into_buffer_following_symlinks(path_to_appimage, ".DirIcon", &icon->data, &icon->data_size); 22 | 23 | if (!found) 24 | { 25 | free(icon); 26 | return NULL; 27 | } 28 | 29 | return icon; 30 | } 31 | 32 | void appimage_thumbnailer_destroy_icon(ait_icon_t* icon) 33 | { 34 | free(icon->data); 35 | free(icon); 36 | } 37 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.6) 2 | project(appimage-thumbnailer VERSION 0.1.1) 3 | 4 | option(USE_SYSTEM_LIBAPPIMAGE "link againts system provided libappimage instead of bundled one") 5 | 6 | # == setup dependencies == 7 | find_package(PkgConfig REQUIRED) 8 | 9 | # system libappimage 10 | if(USE_SYSTEM_LIBAPPIMAGE) 11 | find_package(libappimage REQUIRED) 12 | endif() 13 | 14 | # gnome dependencies 15 | include(GNUInstallDirs) 16 | 17 | # kde dependencies 18 | find_package(ECM 1.0.0 REQUIRED NO_MODULE) # needed to be able to find KF5 below 19 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) 20 | set(KDE_INSTALL_DIRS_NO_CMAKE_VARIABLES ON) 21 | include(KDEInstallDirs) 22 | find_package(Qt5 CONFIG REQUIRED COMPONENTS Core) 23 | find_package(KF5 REQUIRED COMPONENTS KIO) 24 | 25 | 26 | # == setup sub-dirs == 27 | add_subdirectory("lib") 28 | add_subdirectory("src") 29 | add_subdirectory("resources") 30 | 31 | # == packaging == 32 | include("cmake/cpack_settings.cmake") 33 | include("cmake/cpack_deb.cmake") 34 | include(CPack) 35 | -------------------------------------------------------------------------------- /src/kde-appimage-thumbnailer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Matteo Bernardini 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | extern "C" { 10 | #include "appimage-icon-extract.h" 11 | } 12 | 13 | class AppImageThumbnailer : public ThumbCreator 14 | { 15 | public: 16 | AppImageThumbnailer() {}; 17 | ~AppImageThumbnailer() override {}; 18 | 19 | bool create(const QString& path, int width, int height, QImage& img) override 20 | { 21 | ait_icon_t* icon = appimage_thumbnailer_icon_extract(path.toStdString().c_str(), width); 22 | if (!icon) 23 | { 24 | return false; 25 | } 26 | 27 | bool created = img.loadFromData(icon->data, icon->data_size); 28 | img = img.scaled(width, height, Qt::KeepAspectRatio); 29 | appimage_thumbnailer_destroy_icon(icon); 30 | return created; 31 | }; 32 | 33 | Flags flags() const override 34 | { 35 | return (Flags)(0); 36 | }; 37 | }; 38 | 39 | extern "C" 40 | { 41 | Q_DECL_EXPORT ThumbCreator* new_creator() 42 | { 43 | return new AppImageThumbnailer(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Matteo Bernardini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # compilation setup 2 | set(CMAKE_CXX_STANDARD 11) 3 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 4 | 5 | set(CMAKE_C_STANDARD 99) 6 | set(CMAKE_C_STANDARD_REQUIRED ON) 7 | 8 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 9 | add_compile_options(-Wall -Wpedantic) 10 | add_link_options(-Wl,--as-needed) 11 | 12 | # icon extractor 13 | add_library(appimage-icon-extract STATIC "appimage-icon-extract.c") 14 | target_link_libraries(appimage-icon-extract libappimage_static) 15 | 16 | # gnome thumbnailer 17 | add_executable(gnome-appimage-thumbnailer "gnome-appimage-thumbnailer.c") 18 | target_link_libraries(gnome-appimage-thumbnailer appimage-icon-extract) 19 | set_target_properties(gnome-appimage-thumbnailer PROPERTIES OUTPUT_NAME "appimage-thumbnailer") 20 | install(TARGETS gnome-appimage-thumbnailer 21 | DESTINATION ${CMAKE_INSTALL_BINDIR} 22 | COMPONENT appimage-thumbnailer-gnome 23 | EXCLUDE_FROM_ALL 24 | ) 25 | 26 | # kde thumbnailer 27 | add_library(kde-appimage-thumbnailer MODULE "kde-appimage-thumbnailer.cpp") 28 | target_link_libraries(kde-appimage-thumbnailer Qt5::Core KF5::KIOWidgets appimage-icon-extract) 29 | set_target_properties(kde-appimage-thumbnailer PROPERTIES OUTPUT_NAME "appimage-thumbnailer") 30 | install(TARGETS kde-appimage-thumbnailer 31 | DESTINATION ${KDE_INSTALL_PLUGINDIR} 32 | COMPONENT appimage-thumbnailer-kde 33 | EXCLUDE_FROM_ALL 34 | ) 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DOCKER_IMAGE := appimage-thumbnailer 2 | DOCKER_RUN := docker run -i --rm --user "$$(id -u)" -v "$$(realpath .):/ws" 3 | BUID_TYPE := RelWithDebInfo 4 | 5 | .PHONY: help image deps ci package shell clean 6 | 7 | help: 8 | @echo "## available targets ##" 9 | @echo "image build Docker image for reproducible and isolated builds" 10 | @echo "deps install dependencies locally (not necessary if building in Docker)" 11 | @echo "ci build project using cmake (in Docker container)" 12 | @echo "package generate packages using cpack (in Docker container)" 13 | @echo "shell run bash in Docker container for development inspection" 14 | @echo "clean clean build directory" 15 | @echo 16 | @echo "## settable variables#" 17 | @echo "IGNORE_DOCKER run targets in host machine rather than Docker container" 18 | @echo "DOCKER_IMAGE name of Docker image (default: appimage-thumbnailer)" 19 | @echo "BUILD_TYPE cmake build type (default: RelWithDebInfo)" 20 | 21 | image: 22 | docker build -t $(DOCKER_IMAGE) -f ci/Dockerfile ci/ 23 | 24 | deps: 25 | bash ci/install-deps.sh 26 | 27 | ci: $(if $(IGNORE_DOCKER),,image) 28 | $(if $(IGNORE_DOCKER),,$(DOCKER_RUN) $(DOCKER_IMAGE)) bash -exc "cmake -DCMAKE_BUILD_TYPE=$(BUID_TYPE) -B build .; cmake --build build" 29 | 30 | package: $(if $(IGNORE_DOCKER),,image) 31 | $(if $(IGNORE_DOCKER),,$(DOCKER_RUN) $(DOCKER_IMAGE)) bash -exc "cd build; cpack -V" 32 | mkdir -p packages/ 33 | -mv build/*.deb packages/ 34 | -mv build/*.rpm packages/ 35 | 36 | shell: image 37 | $(DOCKER_RUN) -t $(DOCKER_IMAGE) bash 38 | 39 | clean: 40 | rm -rf build/ 41 | -------------------------------------------------------------------------------- /src/gnome-appimage-thumbnailer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Matteo Bernardini 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "appimage-icon-extract.h" 10 | 11 | static GdkPixbuf* rescale_icon(ait_icon_t* icon, unsigned target_size) 12 | { 13 | GInputStream* icon_stream = NULL; 14 | GdkPixbuf* icon_pixbuf = NULL; 15 | GdkPixbuf* rescaled_pixbuf = NULL; 16 | GError* icon_error = NULL; 17 | 18 | icon_stream = g_memory_input_stream_new_from_data(icon->data, icon->data_size, NULL); 19 | if (!icon_stream) 20 | { 21 | fputs("AIT - cannot allocate GInputStream\n", stderr); 22 | goto rescale_icon_epilogue; 23 | } 24 | 25 | icon_pixbuf = gdk_pixbuf_new_from_stream(icon_stream, NULL, &icon_error); 26 | if (!icon_pixbuf) 27 | { 28 | fprintf(stderr, "AIT - error creating GdkPixbuf: %s\n", icon_error->message); 29 | goto rescale_icon_epilogue; 30 | } 31 | 32 | rescaled_pixbuf = gdk_pixbuf_scale_simple(icon_pixbuf, target_size, target_size, GDK_INTERP_BILINEAR); 33 | 34 | if (!rescaled_pixbuf) 35 | { 36 | fputs("AIT - cannot allocate resized GdkPixbuf\n", stderr); 37 | goto rescale_icon_epilogue; 38 | } 39 | 40 | rescale_icon_epilogue: 41 | g_clear_object(&icon_stream); 42 | g_clear_object(&icon_pixbuf); 43 | g_clear_error(&icon_error); 44 | 45 | return rescaled_pixbuf; 46 | } 47 | 48 | 49 | int main(int argc, char* argv[]) 50 | { 51 | if (argc < 4) 52 | { 53 | fputs("usage: appimage-thumbnailer \n", stderr); 54 | return 1; 55 | } 56 | 57 | char* appimage_path = argv[1]; 58 | char* output_path = argv[2]; 59 | // TODO: this is a workaround to avoid ugly frame rendering on icon (tested on Cinnamon). 60 | // there has to be a better way to do this without affecting image quality. 61 | // NB: size < 128 has no frame, but we want to avoid having bigger icons 62 | // than default ones, which are sized 52. 63 | unsigned size = 52; 64 | 65 | ait_icon_t* icon = appimage_thumbnailer_icon_extract(appimage_path, size); 66 | if (!icon) 67 | { 68 | return 1; 69 | } 70 | 71 | GdkPixbuf* rescaled_icon = rescale_icon(icon, size); 72 | appimage_thumbnailer_destroy_icon(icon); 73 | if (!rescaled_icon) 74 | { 75 | return 1; 76 | } 77 | 78 | bool saved = gdk_pixbuf_save(rescaled_icon, output_path, "png", NULL, NULL); 79 | g_clear_object(&rescaled_icon); 80 | 81 | return saved ? 0 : 1; 82 | } 83 | --------------------------------------------------------------------------------