├── .clang-format ├── .clang-tidy ├── .github └── workflows │ ├── obs-upload.sh │ └── obs-upload.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── debian ├── changelog ├── changelog.in ├── control ├── copyright ├── rules └── source │ └── format ├── docs └── ueberzugpp.1.in ├── include ├── application.hpp ├── canvas.hpp ├── dimensions.hpp ├── flags.hpp ├── image.hpp ├── os.hpp ├── process.hpp ├── terminal.hpp ├── tmux.hpp ├── util.hpp ├── util │ ├── dbus.hpp │ ├── egl.hpp │ ├── ptr.hpp │ ├── socket.hpp │ └── x11.hpp ├── version.hpp.in └── window.hpp ├── plugins └── wayfire │ ├── .gitignore │ ├── CMakeLists.txt │ └── ueberzugpp.cpp ├── scripts ├── fifo │ ├── fzf-fifo │ └── img-fifo ├── fzfub ├── img ├── lf │ ├── cleaner │ ├── lfub │ └── preview └── sockets.py └── src ├── application.cpp ├── canvas.cpp ├── canvas ├── chafa.cpp ├── chafa.hpp ├── iterm2 │ ├── chunk.cpp │ ├── chunk.hpp │ ├── iterm2.cpp │ └── iterm2.hpp ├── kitty │ ├── chunk.cpp │ ├── chunk.hpp │ ├── kitty.cpp │ └── kitty.hpp ├── sixel.cpp ├── sixel.hpp ├── stdout.hpp ├── wayland │ ├── config.cpp │ ├── config.hpp │ ├── config │ │ ├── dummy.cpp │ │ ├── dummy.hpp │ │ ├── hyprland.cpp │ │ ├── hyprland.hpp │ │ ├── sway.cpp │ │ ├── sway.hpp │ │ ├── wayfire.cpp │ │ └── wayfire.hpp │ ├── wayland.cpp │ ├── wayland.hpp │ └── window │ │ ├── shm.cpp │ │ ├── shm.hpp │ │ ├── waylandegl.cpp │ │ ├── waylandegl.hpp │ │ ├── waylandshm.cpp │ │ ├── waylandshm.hpp │ │ └── waylandwindow.hpp └── x11 │ ├── window │ ├── x11.cpp │ ├── x11.hpp │ ├── x11egl.cpp │ └── x11egl.hpp │ ├── x11.cpp │ └── x11.hpp ├── dimensions.cpp ├── flags.cpp ├── image.cpp ├── image ├── libvips.cpp ├── libvips.hpp ├── opencv.cpp └── opencv.hpp ├── main.cpp ├── os.cpp ├── process ├── apple.cpp └── linux.cpp ├── terminal.cpp ├── tmux.cpp └── util ├── dbus.cpp ├── egl.cpp ├── socket.cpp ├── util.cpp └── x11.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: LLVM 3 | 4 | Language: Cpp 5 | Standard: c++20 6 | 7 | AllowShortFunctionsOnASingleLine: InlineOnly 8 | IndentCaseLabels: true 9 | IndentWidth: 4 10 | PPIndentWidth: 2 11 | IndentPPDirectives: AfterHash 12 | ColumnLimit: 120 13 | InsertNewlineAtEOF: true 14 | AlwaysBreakTemplateDeclarations: Yes 15 | MaxEmptyLinesToKeep: 1 16 | BreakBeforeBraces: Linux 17 | PackConstructorInitializers: Never 18 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: -*, 3 | readability-*, 4 | modernize-*, 5 | portability-*, 6 | performance-*, 7 | concurrency-*, 8 | cppcoreguidelines-*, -cppcoreguidelines-pro-*, -cppcoreguidelines-special-member-functions, -cppcoreguidelines-non-private-member-variables-in-classes, -cppcoreguidelines-owning-memory 9 | misc-*, -misc-non-private-member-variables-in-classes, 10 | bugprone-*, -bugprone-easily-swappable-parameters, 11 | google-*, -google-runtime-references, -google-readability-todo 12 | WarningsAsErrors: '*' 13 | HeaderFilterRegex: 'compute_samples/' 14 | AnalyzeTemporaryDtors: false 15 | FormatStyle: file 16 | CheckOptions: 17 | - key: readability-identifier-naming.ClassMemberCase 18 | value: lower_case 19 | - key: readability-identifier-naming.ClassMemberSuffix 20 | value: '' 21 | - key: readability-identifier-naming.VariableCase 22 | value: lower_case 23 | - key: readability-identifier-naming.FunctionCase 24 | value: lower_case 25 | - key: readability-identifier-naming.EnumCase 26 | value: lower_case 27 | - key: readability-identifier-naming.NamespaceCase 28 | value: lower_case 29 | -------------------------------------------------------------------------------- /.github/workflows/obs-upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | DATETIME=$(date -R) 5 | VERSION=$(grep "set(UEBERZUGPP_VERSION" CMakeLists.txt | grep -oP '\(\K[^\)]+' | cut -f 2 -d ' ') 6 | TARFILE="ueberzugpp_$VERSION.tar.xz" 7 | DSCFILE="ueberzugpp.dsc" 8 | SPECFILE="ueberzugpp.spec" 9 | declare -a FILES=("$TARFILE" "$DSCFILE" "$SPECFILE") 10 | 11 | sed -e "s;@@VERSION@@;${VERSION};g" -e "s;@@DATETIME@@;${DATETIME};g" < debian/changelog.in > debian/changelog 12 | 13 | tar cfJ "$TARFILE" ./* 14 | 15 | cat > "$DSCFILE" << EOF 16 | Format: 3.0 (native) 17 | Source: ueberzugpp 18 | Binary: ueberzugpp 19 | Architecture: any 20 | Version: $VERSION 21 | Maintainer: JustKidding 22 | Homepage: https://github.com/jstkdng/ueberzugpp 23 | Standards-Version: 4.5.1 24 | Build-Depends: $(sed -n '/^Build-Depends:/,/^$/p' < debian/control | tr -d '\n' | cut -f 2 -d : | sed -r 's;^ *| *$;;g') 25 | Package-List: 26 | ueberzugpp deb misc optional arch=any 27 | Checksums-Sha1: 28 | $(sha1sum < "$TARFILE" | cut -f 1 -d ' ') $(stat -c '%s' "$TARFILE") $TARFILE 29 | Checksums-Sha256: 30 | $(sha256sum < "$TARFILE" | cut -f 1 -d ' ') $(stat -c '%s' "$TARFILE") $TARFILE 31 | Files: 32 | $(md5sum < "$TARFILE" | cut -f 1 -d ' ') $(stat -c '%s' "$TARFILE") $TARFILE 33 | EOF 34 | 35 | cat > "$SPECFILE" << EOF 36 | # 37 | # spec file for package ueberzugpp 38 | # 39 | # Copyright (c) 2023 SUSE LLC 40 | # 41 | # All modifications and additions to the file contributed by third parties 42 | # remain the property of their copyright owners, unless otherwise agreed 43 | # upon. The license for this file, and modifications and additions to the 44 | # file, is the same license as for the pristine package itself (unless the 45 | # license for the pristine package is not an Open Source License, in which 46 | # case the license is the MIT License). An "Open Source License" is a 47 | # license that conforms to the Open Source Definition (Version 1.9) 48 | # published by the Open Source Initiative. 49 | 50 | # Please submit bugfixes or comments via https://bugs.opensuse.org/ 51 | # 52 | 53 | 54 | %define short_name ueberzug 55 | Name: ueberzugpp 56 | Version: $VERSION 57 | Release: 0 58 | Summary: Utility to render images in terminals 59 | License: GPL-3.0-or-later 60 | URL: https://github.com/jstkdng/%{name} 61 | Source: https://github.com/jstkdng/%{name}/archive/refs/tags/v%{version}.tar.gz#/%{name}_%{version}.tar.xz 62 | BuildRequires: automake 63 | BuildRequires: cmake 64 | BuildRequires: cmake(spdlog) 65 | BuildRequires: extra-cmake-modules 66 | BuildRequires: gcc-c++ 67 | BuildRequires: make 68 | BuildRequires: range-v3-devel 69 | BuildRequires: pkgconfig(openssl) 70 | BuildRequires: pkgconfig(CLI11) 71 | BuildRequires: pkgconfig(chafa) 72 | BuildRequires: pkgconfig(libsixel) 73 | BuildRequires: pkgconfig(nlohmann_json) 74 | BuildRequires: pkgconfig(opencv4) 75 | BuildRequires: pkgconfig(tbb) 76 | BuildRequires: pkgconfig(vips) 77 | BuildRequires: pkgconfig(wayland-client) 78 | BuildRequires: pkgconfig(wayland-protocols) 79 | BuildRequires: pkgconfig(xcb-image) 80 | 81 | %description 82 | Überzug++ is a C++ command line utility which allows to draw images 83 | on terminals by using child windows or using sixel on supported 84 | terminals. (This is a drop-in replacement for the now defunct 85 | ueberzug project.) 86 | 87 | Advantages over w3mimgdisplay and ueberzug: 88 | 89 | - support for wayland (sway only) 90 | - no race conditions as a new window is created to display images 91 | - "expose" events will be processed, so that images will be 92 | redrawn when switching workspaces 93 | - tmux support on X11 94 | - terminals without the WINDOWID environment variable are supported 95 | - chars are used as position and size unit 96 | - A lot of image formats are supported (through opencv and libvips) 97 | - GIF and animated WEBP support on X11 and Sixel 98 | - Resized images are cached for faster viewing 99 | 100 | %prep 101 | %autosetup -c 102 | 103 | %build 104 | %cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_SKIP_RPATH=YES -DCMAKE_BUILD_TYPE=Release -DENABLE_WAYLAND=ON 105 | %cmake_build 106 | 107 | %install 108 | %cmake_install 109 | 110 | %files 111 | %{_bindir}/%{short_name} 112 | %{_bindir}/%{short_name}pp 113 | %license LICENSE 114 | %doc README.md 115 | %{_mandir}/man1/%{short_name}.1.gz 116 | %{_mandir}/man1/%{short_name}pp.1.gz 117 | 118 | %changelog 119 | 120 | EOF 121 | 122 | BASE_URL="https://api.opensuse.org/source/$OBS_PROJECT/ueberzugpp" 123 | for FILE in "${FILES[@]}" 124 | do 125 | URL="$BASE_URL/$FILE?rev=upload" 126 | curl -XPUT -H 'Content-Type: application/octet-stream' -u "$OBS_AUTH" --data-binary "@$FILE" "$URL" 127 | done 128 | 129 | curl -XPOST -u "$OBS_AUTH" "$BASE_URL" -F "cmd=commit" 130 | 131 | -------------------------------------------------------------------------------- /.github/workflows/obs-upload.yml: -------------------------------------------------------------------------------- 1 | name: 'Build OBS packages' 2 | 3 | on: 4 | workflow_dispatch: 5 | release: 6 | types: [published] 7 | 8 | jobs: 9 | obs-upload: 10 | env: 11 | OBS_AUTH: ${{ secrets.OBS_AUTH }} 12 | OBS_PROJECT: ${{ secrets.OBS_PROJECT }} 13 | runs-on: ubuntu-latest 14 | container: archlinux:base-devel 15 | steps: 16 | - name: Install packages 17 | run: pacman -Syu --noconfirm git 18 | - uses: actions/checkout@v3 19 | with: 20 | submodules: true 21 | - name: Run OBS Upload Script 22 | run: .github/workflows/obs-upload.sh 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .cache/ 3 | .kdev4/ 4 | .ccls-cache/ 5 | /.vs 6 | cmake-build-debug/ 7 | .idea 8 | *.json 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/CLI11"] 2 | path = third_party/CLI11 3 | url = https://github.com/CLIUtils/CLI11 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Merit 2 | 3 | 1. The project creators, lead developers, core team, constitute 4 | the managing members of the project and have final say in every decision 5 | of the project, technical or otherwise, including overruling previous decisions. 6 | There are no limitations to this decisional power. 7 | 8 | 2. Contributions are an expected result of your membership on the project. 9 | Don't expect others to do your work or help you with your work forever. 10 | 11 | 3. All members have the same opportunities to seek any challenge they want 12 | within the project. 13 | 14 | 4. Authority or position in the project will be proportional 15 | to the accrued contribution. Seniority must be earned. 16 | 17 | 5. Software is evolutive: the better implementations must supersede lesser 18 | implementations. Technical advantage is the primary evaluation metric. 19 | 20 | 6. This is a space for technical prowess; topics outside of the project 21 | will not be tolerated. 22 | 23 | 7. Non technical conflicts will be discussed in a separate space. Disruption 24 | of the project will not be allowed. 25 | 26 | 8. Individual characteristics, including but not limited to, 27 | body, sex, sexual preference, race, language, religion, nationality, 28 | or political preferences are irrelevant in the scope of the project and 29 | will not be taken into account concerning your value or that of your contribution 30 | to the project. 31 | 32 | 9. Discuss or debate the idea, not the person. 33 | 34 | 10. There is no room for ambiguity: Ambiguity will be met with questioning; 35 | further ambiguity will be met with silence. It is the responsibility 36 | of the originator to provide requested context. 37 | 38 | 11. If something is illegal outside the scope of the project, it is illegal 39 | in the scope of the project. This Code of Merit does not take precedence over 40 | governing law. 41 | 42 | 12. This Code of Merit governs the technical procedures of the project not the 43 | activities outside of it. 44 | 45 | 13. Participation on the project equates to agreement of this Code of Merit. 46 | 47 | 14. No objectives beyond the stated objectives of this project are relevant 48 | to the project. Any intent to deviate the project from its original purpose 49 | of existence will constitute grounds for remedial action which may include 50 | expulsion from the project. 51 | 52 | This document is adapted from the Code of Merit, version 1.0. 53 | See: https://codeofmerit.org/. 54 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | ueberzugpp (2.8.6) unstable; urgency=medium 2 | 3 | * Initial release. 4 | 5 | -- JustKidding Wed, 31 May 2023 07:35:31 -0400 6 | -------------------------------------------------------------------------------- /debian/changelog.in: -------------------------------------------------------------------------------- 1 | ueberzugpp (@@VERSION@@) unstable; urgency=medium 2 | 3 | * Initial release. 4 | 5 | -- JustKidding @@DATETIME@@ 6 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: ueberzugpp 2 | Maintainer: JustKidding 3 | Homepage: https://github.com/jstkdng/ueberzugpp 4 | Section: misc 5 | Priority: optional 6 | Standards-Version: 4.5.1 7 | Conflicts: ueberzug 8 | Build-Depends: 9 | debhelper (>= 13), 10 | debhelper-compat (= 13), 11 | cmake (>= 3.18), 12 | libchafa-dev, 13 | libvips-dev, 14 | nlohmann-json3-dev, 15 | libspdlog-dev, 16 | libfmt-dev, 17 | libopencv-videoio-dev, 18 | libopencv-dev, 19 | libopencv-imgproc-dev, 20 | libopencv-imgcodecs-dev, 21 | libopencv-core-dev, 22 | libsixel-dev, 23 | libssl-dev, 24 | libxcb-image0-dev, 25 | libxcb-res0-dev, 26 | wayland-protocols, 27 | libwayland-dev, 28 | librange-v3-dev, 29 | extra-cmake-modules 30 | 31 | Package: ueberzugpp 32 | Architecture: any 33 | Depends: ${shlibs:Depends}, ${misc:Depends} 34 | Description: Display images inside the terminal. 35 | Überzug++ is a command line utility written in C++ which 36 | allows to draw images on terminals by using X11/wayland 37 | child windows, sixels, kitty and iterm2. 38 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Upstream-Name: ueberzugpp 3 | Upstream-Contact: JustKidding 4 | Source: https://github.com/jstkdng/ueberzugpp 5 | 6 | Files: * 7 | Copyright: JustKidding 8 | License: GPL-3+ with OpenSSL exception 9 | Display images inside a terminal 10 | Copyright (C) 2023 JustKidding 11 | . 12 | This program is free software: you can redistribute it and/or modify 13 | it under the terms of the GNU General Public License as published by 14 | the Free Software Foundation, either version 3 of the License, or 15 | (at your option) any later version. 16 | . 17 | In addition, as a special exception, the author of this program gives 18 | permission to link the code of its release with the OpenSSL project's 19 | "OpenSSL" library (or with modified versions of it that use the same 20 | license as the "OpenSSL" library), and distribute the linked executables. 21 | You must obey the GNU General Public License in all respects for all of 22 | the code used other than "OpenSSL". If you modify this file, you may 23 | extend this exception to your version of the file, but you are not 24 | obligated to do so. If you do not wish to do so, delete this exception 25 | statement from your version. 26 | . 27 | This program is distributed in the hope that it will be useful, 28 | but WITHOUT ANY WARRANTY; without even the implied warranty of 29 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 30 | GNU General Public License for more details. 31 | . 32 | You should have received a copy of the GNU General Public License 33 | along with this program. If not, see . 34 | Comment: 35 | On Debian systems, the full text of the GNU General Public License 36 | version 3 can be found in the file '/usr/share/common-licenses/GPL-3'. 37 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # See debhelper(7) (uncomment to enable) 3 | export DH_VERBOSE := 1 4 | %: 5 | dh $@ --buildsystem=cmake 6 | 7 | override_dh_auto_configure: 8 | dh_auto_configure -- \ 9 | -DENABLE_WAYLAND=ON \ 10 | -DFETCHCONTENT_FULLY_DISCONNECTED=ON 11 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /docs/ueberzugpp.1.in: -------------------------------------------------------------------------------- 1 | .TH UEBERZUGPP 1 "2023 May" "Ueberzugpp @ueberzugpp_VERSION@" 2 | 3 | .SH NAME 4 | ueberzugpp \- display images in a terminal 5 | 6 | .SH SYNOPSIS 7 | .SY ueberzugpp 8 | .RI [ options ] 9 | 10 | .SH DESCRIPTION 11 | .PP 12 | .B ueberzugpp 13 | is a program meant to display images in a terminal in a with an IPC. 14 | 15 | .SH OPTIONS 16 | 17 | .TP 18 | .BR \-h ", " \-\-help 19 | Show help text. 20 | 21 | .TP 22 | .BR \-\-version 23 | Show version 24 | 25 | .TP 26 | .BR \-\-use\-escape\-codes 27 | Use escape codes to get terminal capabilities 28 | 29 | .TP 30 | .BR \-\-no\-stdin 31 | Do not listen on stdin for commands 32 | 33 | .TP 34 | .BR \-\-no\-cache 35 | Disable caching of resized images 36 | 37 | .TP 38 | .BR \-\-no\-opencv 39 | Do not use OpenCV, use Libvips instead 40 | 41 | .TP 42 | .BR \-o ", " \-\-output 43 | Image output method, valid values for this include: 44 | .PP 45 | .RS 46 | .I x11 " (May not be available if disabled in compilation)" 47 | .br 48 | .I sixel 49 | .br 50 | .I kitty 51 | .br 52 | .I iterm2 53 | .br 54 | .I wayland " (May not be available if disabled in compilation)" 55 | .br 56 | .I chafa 57 | .RE 58 | 59 | .TP 60 | .BR \-p ", " \-\-parser 61 | .B UNUSED ", " 62 | only present for backwards compatibility 63 | 64 | .TP 65 | .BR \-l ", " \-\-loader 66 | .B UNUSED ", " 67 | only present for backwards compatibility 68 | 69 | .SH STDIN 70 | 71 | .PP 72 | Ueberzugpp reads commands through stdin. Or through the unix socket located at /tmp/ueberzug_${USER}.sock 73 | .PP 74 | Commands should be in JSON form, as described in the JSON IPC section 75 | 76 | .SH JSON IPC 77 | 78 | .PP 79 | There are two actions, 80 | .I add 81 | and 82 | .I remove 83 | .PP 84 | 85 | .SS 86 | .B add 87 | action json schema 88 | .PP 89 | Requried Keys 90 | 91 | .RE 92 | .TP 93 | .B action " (string)" 94 | should be 95 | .I add 96 | 97 | .TP 98 | .B path " (string)" 99 | the path to the image to use 100 | 101 | .TP 102 | .B identifier " (string)" 103 | an identifier for the image, so that it can be removed with the remove action 104 | 105 | .TP 106 | .B One of, width/height, or max_width/max_height 107 | 108 | .TP 109 | .B width " (integer)" 110 | width of the image 111 | 112 | .TP 113 | .B max_width " (integer)" 114 | maximum width of the image 115 | 116 | .TP 117 | .B height " (integer)" 118 | height of the image 119 | 120 | .TP 121 | .B max_height " (integer)" 122 | maximum height of the image 123 | 124 | .TP 125 | .B x " (integer)" 126 | the column position in the terminal 127 | 128 | .TP 129 | .B y " (integer)" 130 | the row position in the terminal 131 | 132 | .PP 133 | Optional keys 134 | 135 | .TP 136 | .B scaler " (string)" 137 | can be fit_contain or forced_cover. 138 | .br 139 | Both base the scale on whichever is larger, the width, or height of the image 140 | 141 | .RE 142 | 143 | .SS 144 | .B remove 145 | action json schema 146 | .PP 147 | Requried Keys 148 | 149 | .RS 150 | .TP 151 | .B action " (string)" 152 | should be remove 153 | 154 | .TP 155 | .B identifier " (string)" 156 | The identifier of the image to remove 157 | 158 | .RE 159 | 160 | .SH EXAMPLE 161 | 162 | .PP 163 | Create a fifo file named fifo, and have an image in the current folder named image.png for this example to work 164 | 165 | .PP 166 | ueberzugpp layer -o sixel < fifo & 167 | 168 | .PP 169 | echo '{"path": "./image.png", "action": "add", "identifier": "image", "x": 0, "y": 0, "width": 20, "height": 20}' > fifo 170 | -------------------------------------------------------------------------------- /include/application.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef APPLICATION_H 18 | #define APPLICATION_H 19 | 20 | #include "canvas.hpp" 21 | #include "flags.hpp" 22 | #include "os.hpp" 23 | #include "terminal.hpp" 24 | #include "util/ptr.hpp" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | 34 | class Application 35 | { 36 | public: 37 | explicit Application(const char *executable); 38 | ~Application(); 39 | 40 | void execute(std::string_view cmd); 41 | void command_loop(); 42 | void handle_tmux_hook(std::string_view hook); 43 | 44 | inline static std::atomic stop_flag = false; // NOLINT 45 | inline static const int parent_pid = os::get_ppid(); 46 | 47 | static void print_version(); 48 | static void print_header(); 49 | 50 | private: 51 | std::unique_ptr terminal; 52 | std::unique_ptr canvas; 53 | 54 | std::shared_ptr flags; 55 | std::shared_ptr logger; 56 | 57 | cn_unique_ptr f_stderr; 58 | std::thread socket_thread; 59 | 60 | void setup_logger(); 61 | void set_silent(); 62 | void socket_loop(); 63 | void daemonize(); 64 | }; 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /include/canvas.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef CANVAS_H 18 | #define CANVAS_H 19 | 20 | #include "image.hpp" 21 | 22 | #include 23 | 24 | class Canvas 25 | { 26 | public: 27 | static auto create() -> std::unique_ptr; 28 | virtual ~Canvas() = default; 29 | 30 | virtual void add_image(const std::string &identifier, std::unique_ptr new_image) = 0; 31 | virtual void remove_image(const std::string &identifier) = 0; 32 | 33 | virtual void show() {} 34 | virtual void hide() {} 35 | virtual void toggle() {} 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /include/dimensions.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef DIMENSIONS_H 18 | #define DIMENSIONS_H 19 | 20 | #include 21 | #include 22 | 23 | #include "terminal.hpp" 24 | 25 | class Dimensions 26 | { 27 | public: 28 | Dimensions(const Terminal *terminal, uint16_t xcoord, uint16_t ycoord, int max_w, int max_h, std::string scaler); 29 | 30 | [[nodiscard]] auto xpixels() const -> int; 31 | [[nodiscard]] auto ypixels() const -> int; 32 | [[nodiscard]] auto max_wpixels() const -> int; 33 | [[nodiscard]] auto max_hpixels() const -> int; 34 | 35 | uint16_t x; 36 | uint16_t y; 37 | uint16_t max_w; 38 | uint16_t max_h; 39 | uint16_t padding_horizontal; 40 | uint16_t padding_vertical; 41 | std::string scaler; 42 | const Terminal *terminal; 43 | 44 | private: 45 | uint16_t orig_x; 46 | uint16_t orig_y; 47 | 48 | void read_offsets(); 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /include/flags.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef FLAGS_H 18 | #define FLAGS_H 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | // singleton 25 | class Flags 26 | { 27 | public: 28 | static auto instance() -> std::shared_ptr 29 | { 30 | static std::shared_ptr instance{new Flags}; 31 | return instance; 32 | } 33 | 34 | Flags(const Flags &) = delete; 35 | Flags(Flags &) = delete; 36 | auto operator=(const Flags &) -> Flags & = delete; 37 | auto operator=(Flags &) -> Flags & = delete; 38 | 39 | bool no_stdin = false; 40 | bool silent = false; 41 | bool use_escape_codes = false; 42 | bool print_version = false; 43 | bool no_cache = false; 44 | bool no_opencv = false; 45 | bool use_opengl = false; 46 | std::string output; 47 | std::string pid_file; 48 | bool origin_center = false; 49 | int32_t scale_factor = 1; 50 | bool needs_scaling = false; 51 | 52 | std::string cmd_id; 53 | std::string cmd_action; 54 | std::string cmd_socket; 55 | std::string cmd_x; 56 | std::string cmd_y; 57 | std::string cmd_max_width; 58 | std::string cmd_max_height; 59 | std::string cmd_file_path; 60 | 61 | private: 62 | Flags(); 63 | std::filesystem::path config_file; 64 | 65 | void read_config_file(); 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /include/image.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef IMAGE_H 18 | #define IMAGE_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "dimensions.hpp" 26 | #include "terminal.hpp" 27 | 28 | class Image 29 | { 30 | public: 31 | static auto load(const nlohmann::json &command, const Terminal *terminal) -> std::unique_ptr; 32 | static auto check_cache(const Dimensions &dimensions, const std::filesystem::path &orig_path) -> std::string; 33 | static auto get_dimensions(const nlohmann::json &json, const Terminal *terminal) -> std::shared_ptr; 34 | 35 | virtual ~Image() = default; 36 | 37 | [[nodiscard]] virtual auto dimensions() const -> const Dimensions & = 0; 38 | [[nodiscard]] virtual auto width() const -> int = 0; 39 | [[nodiscard]] virtual auto height() const -> int = 0; 40 | [[nodiscard]] virtual auto size() const -> size_t = 0; 41 | [[nodiscard]] virtual auto data() const -> const unsigned char * = 0; 42 | [[nodiscard]] virtual auto channels() const -> int = 0; 43 | 44 | [[nodiscard]] virtual auto frame_delay() const -> int { return -1; } 45 | [[nodiscard]] virtual auto is_animated() const -> bool { return false; } 46 | [[nodiscard]] virtual auto filename() const -> std::string = 0; 47 | virtual auto next_frame() -> void {} 48 | 49 | protected: 50 | [[nodiscard]] auto get_new_sizes(double max_width, double max_height, std::string_view scaler, 51 | int scale_factor = 0) const -> std::pair; 52 | }; 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /include/os.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef NAMESPACE_OS_H 18 | #define NAMESPACE_OS_H 19 | 20 | #include 21 | #include 22 | 23 | namespace os 24 | { 25 | 26 | auto exec(const std::string &cmd) -> std::string; 27 | auto getenv(const std::string &var) -> std::optional; 28 | auto read_data_from_fd(int filde, char sep = '\n') -> std::string; 29 | auto read_data_from_stdin(char sep = '\n') -> std::string; 30 | auto wait_for_data_on_fd(int filde, int waitms) -> bool; 31 | auto wait_for_data_on_stdin(int waitms) -> bool; 32 | 33 | auto get_pid() -> int; 34 | auto get_ppid() -> int; 35 | 36 | void get_process_info(int pid); 37 | void daemonize(); 38 | 39 | } // namespace os 40 | 41 | #endif 42 | -------------------------------------------------------------------------------- /include/process.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef PROCESS_INFO_H 18 | #define PROCESS_INFO_H 19 | 20 | #include 21 | 22 | class Process 23 | { 24 | public: 25 | explicit Process(int pid); 26 | ~Process() = default; 27 | 28 | int pid; 29 | char state; 30 | int ppid; 31 | int process_group_id; 32 | int session_id; 33 | int tty_nr; 34 | int minor_dev; 35 | std::string pty_path; 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /include/terminal.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef TERMINAL_H 18 | #define TERMINAL_H 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include "flags.hpp" 28 | #include "os.hpp" 29 | 30 | class Terminal 31 | { 32 | public: 33 | Terminal(); 34 | ~Terminal(); 35 | 36 | uint16_t font_width; 37 | uint16_t font_height; 38 | uint16_t padding_horizontal; 39 | uint16_t padding_vertical; 40 | uint16_t rows; 41 | uint16_t cols; 42 | int pid = os::get_pid(); 43 | int terminal_pid; 44 | unsigned int x11_wid; 45 | std::string term; 46 | std::string term_program; 47 | std::string detected_output; 48 | 49 | private: 50 | auto get_terminal_size() -> void; 51 | static auto guess_padding(uint16_t chars, double pixels) -> double; 52 | static auto guess_font_size(uint16_t chars, float pixels, float padding) -> float; 53 | static auto read_raw_str(std::string_view esc) -> std::string; 54 | 55 | void init_termios(); 56 | void reset_termios() const; 57 | 58 | void check_sixel_support(); 59 | void check_kitty_support(); 60 | void check_iterm2_support(); 61 | 62 | void get_terminal_size_escape_code(); 63 | void get_terminal_size_xtsm(); 64 | void get_fallback_x11_terminal_sizes(); 65 | void get_fallback_wayland_terminal_sizes(); 66 | 67 | void open_first_pty(); 68 | void set_detected_output(); 69 | 70 | int pty_fd; 71 | int xpixel = 0; 72 | int ypixel = 0; 73 | uint16_t fallback_xpixel = 0; 74 | uint16_t fallback_ypixel = 0; 75 | 76 | bool supports_sixel = false; 77 | bool supports_kitty = false; 78 | bool supports_x11 = false; 79 | bool supports_iterm2 = false; 80 | bool supports_wayland = false; 81 | 82 | std::shared_ptr flags; 83 | std::shared_ptr logger; 84 | 85 | struct termios old_term; 86 | struct termios new_term; 87 | }; 88 | 89 | #endif 90 | -------------------------------------------------------------------------------- /include/tmux.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef NAMESPACE_TMUX_H 18 | #define NAMESPACE_TMUX_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | namespace tmux 26 | { 27 | auto get_session_id() -> std::string; 28 | auto get_pane() -> std::string; 29 | 30 | auto is_used() -> bool; 31 | auto is_window_focused() -> bool; 32 | 33 | auto get_client_pids() -> std::optional>; 34 | 35 | auto get_offset() -> std::pair; 36 | 37 | auto get_pane_offset() -> std::pair; 38 | 39 | auto get_statusbar_offset() -> int; 40 | 41 | void handle_hook(std::string_view hook, int pid); 42 | void register_hooks(); 43 | void unregister_hooks(); 44 | } // namespace tmux 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /include/util.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef NAMESPACE_UTIL_H 18 | #define NAMESPACE_UTIL_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include "flags.hpp" 30 | #include "os.hpp" 31 | #include "process.hpp" 32 | 33 | namespace util 34 | { 35 | auto str_split(std::string_view str, std::string_view delim) -> std::vector; 36 | auto get_process_tree(int pid) -> std::vector; 37 | auto get_process_tree_v2(int pid) -> std::vector; 38 | auto get_b2_hash_ssl(std::string_view str) -> std::string; 39 | auto get_cache_path() -> std::string; 40 | auto get_cache_file_save_location(const std::filesystem::path &path) -> std::string; 41 | auto get_log_filename() -> std::string; 42 | auto get_socket_path(int pid = os::get_pid()) -> std::string; 43 | void send_socket_message(std::string_view msg, std::string_view endpoint); 44 | auto base64_encode(const unsigned char *input, size_t length) -> std::string; 45 | void base64_encode_v2(const unsigned char *input, size_t length, unsigned char *out); 46 | void move_cursor(int row, int col); 47 | void save_cursor_position(); 48 | void restore_cursor_position(); 49 | void benchmark(const std::function &func); 50 | void send_command(const Flags &flags); 51 | void clear_terminal_area(int xcoord, int ycoord, int width, int height); 52 | auto generate_random_string(std::size_t length) -> std::string; 53 | auto round_up(int num_to_round, int multiple) -> int; 54 | auto temp_directory_path() -> std::filesystem::path; 55 | 56 | auto read_exif_rotation(const std::filesystem::path &path) -> std::optional; 57 | template 58 | auto generate_random_number(T min, T max = std::numeric_limits::max()) -> T 59 | { 60 | std::random_device dev; 61 | std::mt19937 rng(dev()); 62 | std::uniform_int_distribution dist(min, max); 63 | return dist(rng); 64 | } 65 | } // namespace util 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /include/util/dbus.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef UEBERZUGPP_DBUS_HPP 18 | #define UEBERZUGPP_DBUS_HPP 19 | 20 | #include 21 | #include 22 | 23 | class DbusUtil 24 | { 25 | public: 26 | explicit DbusUtil(const std::string &address); 27 | ~DbusUtil(); 28 | 29 | private: 30 | DBusConnection *connection = nullptr; 31 | }; 32 | 33 | #endif // UEBERZUGPP_DBUS_HPP 34 | -------------------------------------------------------------------------------- /include/util/egl.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef UTIL_EGL_H 18 | #define UTIL_EGL_H 19 | 20 | #include "image.hpp" 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include 28 | #define GL_GLEXT_PROTOTYPES 29 | #include 30 | 31 | template 32 | class EGLUtil 33 | { 34 | public: 35 | EGLUtil(EGLenum platform, T *native_display, const EGLAttrib *attrib = nullptr); 36 | ~EGLUtil(); 37 | 38 | void get_texture_from_image(const Image &image, GLuint texture) const; 39 | void run_contained(EGLSurface surface, EGLContext context, const std::function &func) const; 40 | void make_current(EGLSurface surface, EGLContext context) const; 41 | void restore() const; 42 | [[nodiscard]] auto create_surface(V *native_window) const -> EGLSurface; 43 | [[nodiscard]] auto create_context(EGLSurface surface) const -> EGLContext; 44 | 45 | EGLDisplay display; 46 | 47 | private: 48 | EGLConfig config; 49 | std::shared_ptr logger; 50 | 51 | [[nodiscard]] auto error_to_string() const -> std::string; 52 | }; 53 | 54 | #endif 55 | -------------------------------------------------------------------------------- /include/util/ptr.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef UTIL_PTR_H 18 | #define UTIL_PTR_H 19 | 20 | #include 21 | 22 | template 23 | struct deleter_from_fn { 24 | template 25 | constexpr void operator()(T *arg) const 26 | { 27 | fn(const_cast *>(arg)); 28 | } 29 | }; 30 | 31 | template 32 | struct deleter_from_fn_null { 33 | template 34 | constexpr void operator()(T *arg) const 35 | { 36 | if (arg != nullptr) { 37 | fn(const_cast *>(arg)); 38 | } 39 | } 40 | }; 41 | 42 | // custom unique pointer 43 | template 44 | using c_unique_ptr = std::unique_ptr>; 45 | 46 | // custom unique pointer that checks for null before deleting 47 | template 48 | using cn_unique_ptr = std::unique_ptr>; 49 | 50 | template 51 | using unique_C_ptr = std::unique_ptr>; 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /include/util/socket.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef UTIL_SOCKET_H 18 | #define UTIL_SOCKET_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | class UnixSocket 26 | { 27 | public: 28 | UnixSocket(); 29 | explicit UnixSocket(std::string_view endpoint); 30 | ~UnixSocket(); 31 | 32 | void connect_to_endpoint(std::string_view endpoint); 33 | void bind_to_endpoint(std::string_view endpoint) const; 34 | [[nodiscard]] auto wait_for_connections(int waitms) const -> int; 35 | [[nodiscard]] auto read_data_from_connection(int filde) -> std::vector; 36 | void write(const void *data, std::size_t len) const; 37 | void read(void *data, std::size_t len) const; 38 | [[nodiscard]] auto read_until_empty() const -> std::string; 39 | 40 | private: 41 | int fd; 42 | bool connected = true; 43 | std::string buffer; 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /include/util/x11.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef X11_UTIL_H 18 | #define X11_UTIL_H 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | class X11Util 28 | { 29 | public: 30 | X11Util(); 31 | explicit X11Util(xcb_connection_t *connection); 32 | ~X11Util(); 33 | 34 | [[nodiscard]] auto get_server_window_ids() const -> std::vector; 35 | [[nodiscard]] auto get_pid_window_map() const -> std::unordered_map; 36 | [[nodiscard]] auto get_window_dimensions(xcb_window_t window) const -> std::pair; 37 | [[nodiscard]] auto get_parent_window(int pid) const -> xcb_window_t; 38 | [[nodiscard]] auto window_has_properties(xcb_window_t window, std::initializer_list properties) const 39 | -> bool; 40 | 41 | bool connected = false; 42 | 43 | private: 44 | xcb_connection_t *connection; 45 | xcb_screen_t *screen = nullptr; 46 | bool owns_connection = true; 47 | }; 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /include/version.hpp.in: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #define ueberzugpp_VERSION_MAJOR @ueberzugpp_VERSION_MAJOR@ 18 | #define ueberzugpp_VERSION_MINOR @ueberzugpp_VERSION_MINOR@ 19 | #define ueberzugpp_VERSION_PATCH @ueberzugpp_VERSION_PATCH@ 20 | -------------------------------------------------------------------------------- /include/window.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef WINDOW_H 18 | #define WINDOW_H 19 | 20 | #include 21 | #include 22 | 23 | class Window 24 | { 25 | public: 26 | virtual ~Window() = default; 27 | virtual void draw() = 0; 28 | virtual void generate_frame() = 0; 29 | virtual void show() {}; 30 | virtual void hide() {}; 31 | }; 32 | 33 | template 34 | concept WindowType = std::is_base_of::value; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /plugins/wayfire/.gitignore: -------------------------------------------------------------------------------- 1 | .cache/ 2 | -------------------------------------------------------------------------------- /plugins/wayfire/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Display images inside a terminal 2 | # Copyright (C) 2023 JustKidding 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 3 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program. If not, see . 16 | 17 | cmake_minimum_required(VERSION 3.18) 18 | 19 | set(CMAKE_C_STANDARD 17) 20 | set(CMAKE_C_STANDARD_REQUIRED ON) 21 | set(CMAKE_C_EXTENSIONS OFF) 22 | set(CMAKE_CXX_STANDARD 20) 23 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 24 | set(CMAKE_CXX_EXTENSIONS OFF) 25 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 26 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type.") 27 | 28 | cmake_policy(SET CMP0065 NEW) 29 | set(CMAKE_ENABLE_EXPORTS ON) 30 | 31 | project(wayfire-ueberzugpp LANGUAGES CXX C VERSION 0.0.1) 32 | add_library(ueberzugpp SHARED) 33 | 34 | list(APPEND PLUGIN_SOURCES "ueberzugpp.cpp") 35 | 36 | find_package(PkgConfig REQUIRED) 37 | 38 | find_package(ECM REQUIRED NO_MODULE) 39 | list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) 40 | find_package(WaylandProtocols REQUIRED) 41 | find_package(WaylandScanner REQUIRED) 42 | 43 | ecm_add_wayland_server_protocol(PLUGIN_SOURCES 44 | PROTOCOL "${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml" 45 | BASENAME "xdg-shell") 46 | file(CREATE_LINK 47 | "wayland-xdg-shell-server-protocol.h" 48 | "${PROJECT_BINARY_DIR}/xdg-shell-protocol.h" SYMBOLIC) 49 | 50 | pkg_check_modules(WAYFIRE REQUIRED IMPORTED_TARGET wayfire) 51 | pkg_check_modules(WLROOTS REQUIRED IMPORTED_TARGET wlroots) 52 | pkg_check_modules(WFCONFIG REQUIRED IMPORTED_TARGET wf-config) 53 | 54 | add_compile_definitions(WLR_USE_UNSTABLE WAYFIRE_PLUGIN) 55 | target_compile_options(ueberzugpp PRIVATE 56 | $<$: 57 | -Wall -Wextra -Wpedantic -Werror 58 | > 59 | ) 60 | 61 | target_include_directories(ueberzugpp PRIVATE "${PROJECT_BINARY_DIR}") 62 | target_sources(ueberzugpp PRIVATE ${PLUGIN_SOURCES}) 63 | target_link_libraries(ueberzugpp 64 | PkgConfig::WAYFIRE 65 | PkgConfig::WLROOTS 66 | PkgConfig::WFCONFIG) 67 | -------------------------------------------------------------------------------- /plugins/wayfire/ueberzugpp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace wf 17 | { 18 | class ueberzugpp_surface_t 19 | { 20 | std::weak_ptr terminal; 21 | std::shared_ptr surface; 22 | std::shared_ptr translation; 23 | wlr_xdg_toplevel *toplevel; 24 | 25 | wf::wl_listener_wrapper on_toplevel_destroy; 26 | 27 | public: 28 | 29 | ueberzugpp_surface_t(const std::weak_ptr& _terminal, 30 | wlr_xdg_toplevel *_toplevel): 31 | terminal(_terminal), 32 | toplevel(_toplevel) 33 | { 34 | // We create a custom surface node for the ueberzugpp window and wrap it in a translation node, so that 35 | // we can control its position. 36 | // We add the translation node as a subsurface of the terminal. 37 | surface = std::make_shared(_toplevel->base->surface, true); 38 | translation = std::make_shared(); 39 | 40 | translation->set_children_list({surface}); 41 | 42 | auto term = _terminal.lock(); 43 | wf::scene::add_front(term->get_surface_root_node(), translation); 44 | 45 | // Finally, wait for the toplevel to be destroyed 46 | on_toplevel_destroy.set_callback([this] (auto) 47 | { 48 | destroy_callback(); 49 | }); 50 | on_toplevel_destroy.connect(&toplevel->base->events.destroy); 51 | } 52 | 53 | std::function destroy_callback; 54 | 55 | ~ueberzugpp_surface_t() 56 | { 57 | wf::scene::remove_child(translation); 58 | } 59 | 60 | void set_offset(int xcoord, int ycoord) 61 | { 62 | translation->set_offset({xcoord, ycoord}); 63 | const auto term = terminal.lock(); 64 | if (!term) { 65 | return; 66 | } 67 | term->damage(); 68 | } 69 | }; 70 | 71 | 72 | class ueberzugpp_mapper : public wf::plugin_interface_t 73 | { 74 | public: 75 | void init() override 76 | { 77 | ipc_repo->register_method("ueberzugpp/set_offset", set_offset); 78 | wf::get_core().connect(&on_new_xdg_surface); 79 | } 80 | 81 | void fini() override 82 | { 83 | ipc_repo->unregister_method("ueberzugpp/set_offset"); 84 | } 85 | 86 | ipc::method_callback set_offset = [this] (nlohmann::json json) 87 | { 88 | // NOLINTBEGIN 89 | WFJSON_EXPECT_FIELD(json, "app-id", string); 90 | WFJSON_EXPECT_FIELD(json, "x", number_integer); 91 | WFJSON_EXPECT_FIELD(json, "y", number_integer); 92 | // NOLINTEND 93 | 94 | const std::string app_id = json["app-id"]; 95 | const int xcoord = json["x"]; 96 | const int ycoord = json["y"]; 97 | 98 | const auto search = surfaces.find(app_id); 99 | if (search == surfaces.end()) 100 | { 101 | return ipc::json_error("Unknown ueberzugpp window with appid " + app_id); 102 | } 103 | 104 | search->second->set_offset(xcoord, ycoord); 105 | return ipc::json_ok(); 106 | }; 107 | 108 | shared_data::ref_ptr_t ipc_repo; 109 | 110 | // When a new xdg_toplevel is created, we need to check whether it is an ueberzugpp window by looking at 111 | // its app-id. If it is indeed an ueberzugpp window, we take over the toplevel (by setting 112 | // use_default_implementation=false) and create our own ueberzugpp_surface. In addition, we directly map 113 | // the surface to the currently focused view, if it exists. 114 | wf::signal::connection_t on_new_xdg_surface = [this] (new_xdg_surface_signal *event) 115 | { 116 | if (!event->use_default_implementation) { 117 | return; 118 | } 119 | if (event->surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) { 120 | return; 121 | } 122 | 123 | const auto* toplevel_title = event->surface->toplevel->title; 124 | const std::string appid = toplevel_title != nullptr ? toplevel_title : "null"; 125 | const std::string search_for = "ueberzugpp_"; 126 | if (appid.find(search_for) != std::string::npos) 127 | { 128 | auto terminal = wf::get_core().get_active_output()->get_active_view(); 129 | if (!terminal) 130 | { 131 | LOGE("Which window to map ueberzugpp to????"); 132 | return; 133 | } 134 | 135 | event->use_default_implementation = false; 136 | const auto [iter, was_inserted] = surfaces.insert_or_assign(appid, 137 | std::make_unique(terminal->weak_from_this(), event->surface->toplevel)); 138 | iter->second->destroy_callback = [this, appid] 139 | { 140 | surfaces.erase(appid); 141 | }; 142 | } 143 | }; 144 | 145 | std::unordered_map> surfaces; 146 | }; 147 | } // end namespace wf 148 | 149 | // NOLINTNEXTLINE 150 | DECLARE_WAYFIRE_PLUGIN(wf::ueberzugpp_mapper); 151 | -------------------------------------------------------------------------------- /scripts/fifo/fzf-fifo: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This is a simple script that allows you to have image preview 4 | # in fzf, using ueberzugpp with fifo. For this script to work 5 | # you must add it to path, because it requires to call itself 6 | # during the preview process in fzf (line 42 -> $0). 7 | # Example usage: 8 | # ls | fzf-fifo 9 | # find $HOME/pix/ -type f -iname "*.jpg" | fzf-fifo 10 | 11 | FIFO="/tmp/fzf_preview_fifo" 12 | [ -p "$FIFO" ] || mkfifo "$FIFO" 13 | 14 | start_ueberzugpp() { 15 | ueberzugpp layer --silent <"$FIFO" & 16 | exec 3>"${FIFO}" 17 | } 18 | 19 | cleanup() { 20 | exec 3>&- 21 | } 22 | trap cleanup HUP INT QUIT TERM EXIT 23 | 24 | preview_image() { 25 | echo '{"path": "'"$1"'", "action": "add", ''"identifier": "fzfpreview", '\ 26 | '"x": "'"$FZF_PREVIEW_LEFT"'", "y": "'"$FZF_PREVIEW_TOP"'", '\ 27 | '"width": "'"$FZF_PREVIEW_COLUMNS"'", "height": "'"$FZF_PREVIEW_LINES"'"}' \ 28 | >"$FIFO" 29 | } 30 | 31 | case "$1" in 32 | -W) 33 | shift 34 | preview_image "$@" 35 | exit 36 | ;; 37 | esac 38 | 39 | main() { 40 | start_ueberzugpp 41 | selected_image=$(fzf --preview "$0 -W {}" --preview-window=up:60%:wrap) 42 | [ -n "$selected_image" ] && echo "Selected image: $selected_image" 43 | rm "$FIFO" 44 | } 45 | main 46 | -------------------------------------------------------------------------------- /scripts/fifo/img-fifo: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This is a simple script that allows you to use ueberzugpp to 4 | # preview images in the terminal by providing the x and y 5 | # coordinates of the image, the width and height of the image, 6 | # and the path to the image file, with $1, $2, $3, $4 and $5 7 | # as arguments, respectively. 8 | # Example usage: 9 | # ./img-fifo 0 0 30 30 image.jpg 10 | # Use Ctrl+C to exit. 11 | 12 | FIFO="/tmp/preview_fifo" 13 | [ -p "$FIFO" ] || mkfifo "$FIFO" 14 | 15 | start_ueberzugpp() { 16 | ueberzugpp layer --silent <"$FIFO" & 17 | exec 3>"${FIFO}" 18 | } 19 | 20 | cleanup() { 21 | rm -f "$FIFO" 22 | } 23 | trap cleanup HUP INT QUIT TERM EXIT 24 | 25 | preview_image() { 26 | echo '{"path": "'"$5"'", "action": "add", "identifier": "img-fifo", "x": "'"$1"'", "y": "'"$2"'", "width": "'"$3"'", "height": "'"$4"'"}' >"$FIFO" 27 | } 28 | 29 | start_ueberzugpp 30 | preview_image "$1" "$2" "$3" "$4" "$5" 31 | wait 32 | -------------------------------------------------------------------------------- /scripts/fzfub: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | case "$(uname -a)" in 4 | *Darwin*) UEBERZUG_TMP_DIR="$TMPDIR" ;; 5 | *) UEBERZUG_TMP_DIR="/tmp" ;; 6 | esac 7 | 8 | cleanup() { 9 | ueberzugpp cmd -s "$SOCKET" -a exit 10 | } 11 | trap cleanup HUP INT QUIT TERM EXIT 12 | 13 | UB_PID_FILE="$UEBERZUG_TMP_DIR/.$(uuidgen)" 14 | ueberzugpp layer --no-stdin --silent --use-escape-codes --pid-file "$UB_PID_FILE" 15 | UB_PID=$(cat "$UB_PID_FILE") 16 | 17 | export SOCKET="$UEBERZUG_TMP_DIR"/ueberzugpp-"$UB_PID".socket 18 | 19 | # run fzf with preview 20 | fzf --reverse --preview="ueberzugpp cmd -s $SOCKET -i fzfpreview -a add \ 21 | -x \$FZF_PREVIEW_LEFT -y \$FZF_PREVIEW_TOP \ 22 | --max-width \$FZF_PREVIEW_COLUMNS --max-height \$FZF_PREVIEW_LINES \ 23 | -f {}" 24 | 25 | ueberzugpp cmd -s "$SOCKET" -a exit 26 | -------------------------------------------------------------------------------- /scripts/img: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # This is a simple script that allows you to use ueberzugpp to 5 | # preview images in the terminal by providing the x and y 6 | # coordinates of the image, the width and height of the image, 7 | # and the path to the image file, with $1, $2, $3, $4 and $5 8 | # as arguments, respectively. 9 | # Example usage: 10 | # ./img 0 0 30 30 image.jpg 11 | 12 | UB_PID=0 13 | UB_SOCKET="" 14 | 15 | case "$(uname -a)" in 16 | *Darwin*) UEBERZUG_TMP_DIR="$TMPDIR" ;; 17 | *) UEBERZUG_TMP_DIR="/tmp" ;; 18 | esac 19 | 20 | cleanup() { 21 | ueberzugpp cmd -s "$UB_SOCKET" -a exit 22 | } 23 | trap cleanup HUP INT QUIT TERM EXIT 24 | 25 | UB_PID_FILE="$UEBERZUG_TMP_DIR/.$(uuidgen)" 26 | ueberzugpp layer --no-stdin --silent --use-escape-codes --pid-file "$UB_PID_FILE" 27 | UB_PID="$(cat "$UB_PID_FILE")" 28 | export UB_SOCKET="$UEBERZUG_TMP_DIR"/ueberzugpp-"$UB_PID".socket 29 | ueberzugpp cmd -s "$UB_SOCKET" -a add -i PREVIEW -x "$1" -y "$2" --max-width "$3" --max-height "$4" -f "$5" 30 | sleep 2 31 | -------------------------------------------------------------------------------- /scripts/lf/cleaner: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ueberzugpp cmd -s $UB_SOCKET -a remove -i PREVIEW 4 | -------------------------------------------------------------------------------- /scripts/lf/lfub: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # This is a wrapper script for lf that allows it to create image previews with 4 | # ueberzug. This works in concert with the lf configuration file and the 5 | # lf-cleaner script. 6 | 7 | set -e 8 | 9 | UB_PID=0 10 | UB_SOCKET="" 11 | 12 | case "$(uname -a)" in 13 | *Darwin*) UEBERZUG_TMP_DIR="$TMPDIR" ;; 14 | *) UEBERZUG_TMP_DIR="/tmp" ;; 15 | esac 16 | 17 | cleanup() { 18 | exec 3>&- 19 | ueberzugpp cmd -s "$UB_SOCKET" -a exit 20 | } 21 | 22 | if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then 23 | lf "$@" 24 | else 25 | [ ! -d "$HOME/.cache/lf" ] && mkdir -p "$HOME/.cache/lf" 26 | UB_PID_FILE="$UEBERZUG_TMP_DIR/.$(uuidgen)" 27 | ueberzugpp layer --silent --no-stdin --use-escape-codes --pid-file "$UB_PID_FILE" 28 | UB_PID=$(cat "$UB_PID_FILE") 29 | rm "$UB_PID_FILE" 30 | UB_SOCKET="$UEBERZUG_TMP_DIR/ueberzugpp-${UB_PID}.socket" 31 | export UB_PID UB_SOCKET 32 | trap cleanup HUP INT QUIT TERM EXIT 33 | lf "$@" 3>&- 34 | fi 35 | -------------------------------------------------------------------------------- /scripts/lf/preview: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | image() { 4 | FILE_PATH="$1" 5 | X=$4 6 | Y=$5 7 | MW=$(($2 - 1)) 8 | MH=$3 9 | ueberzugpp cmd -s "$UB_SOCKET" -a add -i PREVIEW -x "$X" -y "$Y" --max-width "$MW" --max-height "$MH" -f "$FILE_PATH" 10 | exit 1 11 | } 12 | 13 | batorcat() { 14 | file="$1" 15 | shift 16 | if command -v bat >/dev/null 2>&1; then 17 | bat --color=always --style=plain --pager=never "$file" "$@" 18 | else 19 | cat "$file" 20 | fi 21 | } 22 | 23 | CACHE="$HOME/.cache/lf/thumbnail.$(stat --printf '%n\0%i\0%F\0%s\0%W\0%Y' -- "$(readlink -f "$1")" | sha256sum | awk '{print $1}'))" 24 | 25 | case "$(printf "%s\n" "$(readlink -f "$1")" | tr '[:upper:]' '[:lower:]')" in 26 | *.tgz | *.tar.gz) tar tzf "$1" ;; 27 | *.tar.bz2 | *.tbz2) tar tjf "$1" ;; 28 | *.tar.txz | *.txz) xz --list "$1" ;; 29 | *.tar) tar tf "$1" ;; 30 | *.zip | *.jar | *.war | *.ear | *.oxt) unzip -l "$1" ;; 31 | *.rar) unrar l "$1" ;; 32 | *.7z) 7z l "$1" ;; 33 | *.[1-8]) man "$1" | col -b ;; 34 | *.o) nm "$1" ;; 35 | *.torrent) transmission-show "$1" ;; 36 | *.iso) iso-info --no-header -l "$1" ;; 37 | *.odt | *.ods | *.odp | *.sxw) odt2txt "$1" ;; 38 | *.doc) catdoc "$1" ;; 39 | *.docx) docx2txt "$1" - ;; 40 | *.xls | *.xlsx) 41 | ssconvert --export-type=Gnumeric_stf:stf_csv "$1" "fd://1" | batorcat --language=csv 42 | ;; 43 | *.wav | *.mp3 | *.flac | *.m4a | *.wma | *.ape | *.ac3 | *.og[agx] | *.spx | *.opus | *.as[fx] | *.mka) 44 | exiftool "$1" 45 | ;; 46 | *.pdf) 47 | [ ! -f "${CACHE}.jpg" ] && pdftoppm -jpeg -f 1 -singlefile "$1" "$CACHE" 48 | image "${CACHE}.jpg" "$2" "$3" "$4" "$5" 49 | ;; 50 | *.avi | *.mp4 | *.wmv | *.dat | *.3gp | *.ogv | *.mkv | *.mpg | *.mpeg | *.vob | *.fl[icv] | *.m2v | *.mov | *.webm | *.ts | *.mts | *.m4v | *.r[am] | *.qt | *.divx) 51 | [ ! -f "${CACHE}.jpg" ] && ffmpegthumbnailer -i "$1" -o "${CACHE}.jpg" -s 0 -q 5 52 | image "${CACHE}.jpg" "$2" "$3" "$4" "$5" 53 | ;; 54 | *.bmp | *.jpg | *.jpeg | *.png | *.xpm | *.webp | *.gif | *.jfif) 55 | image "$1" "$2" "$3" "$4" "$5" 56 | ;; 57 | *.svg) 58 | [ ! -f "${CACHE}.jpg" ] && convert "$1" "${CACHE}.jpg" 59 | image "${CACHE}.jpg" "$2" "$3" "$4" "$5" 60 | ;; 61 | *) 62 | batorcat "$1" 63 | ;; 64 | esac 65 | exit 0 66 | -------------------------------------------------------------------------------- /scripts/sockets.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import time 3 | import os 4 | 5 | cmd1 = """ 6 | {"action":"add","identifier":"preview","max_height":21,"max_width":118,"path":"/tmp/a.png","x":10,"y":15} 7 | """ 8 | 9 | cmd2 = """ 10 | {"action":"add","identifier":"preview","max_height":47,"max_width":118,"path":"/tmp/b.png","x":10,"y":15} 11 | """ 12 | 13 | cmd_exit = """ 14 | {"action":"remove","identifier":"preview"} 15 | """ 16 | 17 | s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 18 | connected = False 19 | while not connected: 20 | try: 21 | s.connect(f"/tmp/ueberzugpp-30468.socket") 22 | connected = True 23 | except Exception: 24 | time.sleep(0.05) 25 | 26 | s.sendall(str.encode(cmd1 + "\n")) 27 | time.sleep(2) 28 | s.sendall(str.encode(cmd_exit + "\n")) 29 | s.sendall(str.encode(cmd2 + "\n")) 30 | time.sleep(2) 31 | s.sendall(str.encode(cmd_exit + "\n")) 32 | s.close() 33 | -------------------------------------------------------------------------------- /src/canvas.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "canvas/chafa.hpp" 18 | #include "canvas/iterm2/iterm2.hpp" 19 | #include "canvas/kitty/kitty.hpp" 20 | #include "canvas/sixel.hpp" 21 | #include "canvas/stdout.hpp" 22 | #ifdef ENABLE_X11 23 | # include "canvas/x11/x11.hpp" 24 | #endif 25 | #ifdef ENABLE_WAYLAND 26 | # include "canvas/wayland/wayland.hpp" 27 | #endif 28 | #include "flags.hpp" 29 | #include 30 | 31 | auto Canvas::create() -> std::unique_ptr 32 | { 33 | const auto flags = Flags::instance(); 34 | const auto logger = spdlog::get("main"); 35 | 36 | #ifdef ENABLE_WAYLAND 37 | if (flags->output == "wayland") { 38 | return std::make_unique(); 39 | } 40 | #else 41 | logger->debug("Wayland support not compiled in the binary"); 42 | #endif 43 | #ifdef ENABLE_X11 44 | if (flags->output == "x11") { 45 | return std::make_unique(); 46 | } 47 | #else 48 | logger->debug("X11 support not compiled in the binary"); 49 | #endif 50 | 51 | if (flags->output == "kitty") { 52 | return std::make_unique>(flags->output); 53 | } 54 | if (flags->output == "iterm2") { 55 | return std::make_unique>(flags->output); 56 | } 57 | if (flags->output == "sixel") { 58 | return std::make_unique>(flags->output); 59 | } 60 | if (flags->output == "chafa") { 61 | return std::make_unique>(flags->output); 62 | } 63 | throw std::runtime_error(fmt::format("output backend not supported (backend is {})", flags->output)); 64 | } 65 | -------------------------------------------------------------------------------- /src/canvas/chafa.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "chafa.hpp" 18 | #include "dimensions.hpp" 19 | #include "terminal.hpp" 20 | #include "util.hpp" 21 | #include "util/ptr.hpp" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | 30 | void gstring_delete(GString *str) 31 | { 32 | g_string_free(str, true); 33 | } 34 | 35 | Chafa::Chafa(std::unique_ptr new_image, std::mutex *stdout_mutex) 36 | : symbol_map(chafa_symbol_map_new()), 37 | config(chafa_canvas_config_new()), 38 | image(std::move(new_image)), 39 | stdout_mutex(stdout_mutex) 40 | { 41 | const auto envp = c_unique_ptr{g_get_environ()}; 42 | term_info = chafa_term_db_detect(chafa_term_db_get_default(), envp.get()); 43 | 44 | const auto dims = image->dimensions(); 45 | x = dims.x + 1; 46 | y = dims.y + 1; 47 | horizontal_cells = std::ceil(static_cast(image->width()) / dims.terminal->font_width); 48 | vertical_cells = std::ceil(static_cast(image->height()) / dims.terminal->font_height); 49 | 50 | chafa_symbol_map_add_by_tags(symbol_map, CHAFA_SYMBOL_TAG_BLOCK); 51 | chafa_symbol_map_add_by_tags(symbol_map, CHAFA_SYMBOL_TAG_BORDER); 52 | chafa_symbol_map_add_by_tags(symbol_map, CHAFA_SYMBOL_TAG_SPACE); 53 | chafa_symbol_map_remove_by_tags(symbol_map, CHAFA_SYMBOL_TAG_WIDE); 54 | chafa_canvas_config_set_symbol_map(config, symbol_map); 55 | chafa_canvas_config_set_pixel_mode(config, CHAFA_PIXEL_MODE_SYMBOLS); 56 | chafa_canvas_config_set_geometry(config, horizontal_cells, vertical_cells); 57 | } 58 | 59 | Chafa::~Chafa() 60 | { 61 | chafa_canvas_unref(canvas); 62 | chafa_canvas_config_unref(config); 63 | chafa_symbol_map_unref(symbol_map); 64 | chafa_term_info_unref(term_info); 65 | 66 | const std::scoped_lock lock{*stdout_mutex}; 67 | util::clear_terminal_area(x, y, horizontal_cells, vertical_cells); 68 | } 69 | 70 | void Chafa::draw() 71 | { 72 | canvas = chafa_canvas_new(config); 73 | chafa_canvas_draw_all_pixels(canvas, CHAFA_PIXEL_BGRA8_UNASSOCIATED, image->data(), image->width(), image->height(), 74 | image->width() * 4); 75 | 76 | #ifdef CHAFA_VERSION_1_14 77 | GString **lines = nullptr; 78 | gint lines_length = 0; 79 | 80 | chafa_canvas_print_rows(canvas, term_info, &lines, &lines_length); 81 | auto ycoord = y; 82 | const std::scoped_lock lock{*stdout_mutex}; 83 | util::save_cursor_position(); 84 | for (int i = 0; i < lines_length; ++i) { 85 | const auto line = c_unique_ptr{lines[i]}; 86 | util::move_cursor(ycoord++, x); 87 | std::cout << line->str; 88 | } 89 | g_free(lines); 90 | #else 91 | const auto result = c_unique_ptr{chafa_canvas_print(canvas, term_info)}; 92 | auto ycoord = y; 93 | const auto lines = util::str_split(result->str, "\n"); 94 | 95 | const std::scoped_lock lock{*stdout_mutex}; 96 | util::save_cursor_position(); 97 | ranges::for_each(lines, [this, &ycoord](const std::string &line) { 98 | util::move_cursor(ycoord++, x); 99 | std::cout << line; 100 | }); 101 | #endif 102 | std::cout << std::flush; 103 | util::restore_cursor_position(); 104 | } 105 | -------------------------------------------------------------------------------- /src/canvas/chafa.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef CHAFA_WINDOW_H 18 | #define CHAFA_WINDOW_H 19 | 20 | #include "image.hpp" 21 | #include "window.hpp" 22 | 23 | #include 24 | 25 | #include 26 | 27 | class Chafa : public Window 28 | { 29 | public: 30 | Chafa(std::unique_ptr new_image, std::mutex *stdout_mutex); 31 | ~Chafa() override; 32 | 33 | void draw() override; 34 | void generate_frame() override{}; 35 | 36 | private: 37 | ChafaTermInfo *term_info = nullptr; 38 | ChafaSymbolMap *symbol_map = nullptr; 39 | ChafaCanvasConfig *config = nullptr; 40 | ChafaCanvas *canvas = nullptr; 41 | 42 | std::unique_ptr image; 43 | std::mutex *stdout_mutex; 44 | 45 | int x; 46 | int y; 47 | int horizontal_cells = 0; 48 | int vertical_cells = 0; 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/canvas/iterm2/chunk.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "chunk.hpp" 18 | #include "util.hpp" 19 | 20 | Iterm2Chunk::Iterm2Chunk(uint64_t size) 21 | { 22 | auto bufsize = 4*((size+2)/3); 23 | buffer.resize(size, 0); 24 | result.resize(bufsize + 1, 0); 25 | } 26 | 27 | void Iterm2Chunk::set_size(uint64_t size) 28 | { 29 | this->size = size; 30 | } 31 | 32 | auto Iterm2Chunk::get_size() const -> uint64_t 33 | { 34 | return size; 35 | } 36 | 37 | auto Iterm2Chunk::get_buffer() -> char* 38 | { 39 | return buffer.data(); 40 | } 41 | 42 | auto Iterm2Chunk::get_result() -> char* 43 | { 44 | return result.data(); 45 | } 46 | 47 | void Iterm2Chunk::process_chunk(std::unique_ptr& chunk) 48 | { 49 | util::base64_encode_v2(reinterpret_cast(chunk->get_buffer()), 50 | chunk->get_size(), reinterpret_cast(chunk->get_result())); 51 | } 52 | 53 | void Iterm2Chunk::operator()(std::unique_ptr& chunk) const 54 | { 55 | process_chunk(chunk); 56 | } 57 | -------------------------------------------------------------------------------- /src/canvas/iterm2/chunk.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef ITERM2_CHUNK_H 18 | #define ITERM2_CHUNK_H 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | class Iterm2Chunk 25 | { 26 | public: 27 | Iterm2Chunk() = default; 28 | explicit Iterm2Chunk(uint64_t size); 29 | 30 | void operator()(std::unique_ptr& chunk) const; 31 | void static process_chunk(std::unique_ptr& chunk); 32 | 33 | [[nodiscard]] auto get_size() const -> uint64_t; 34 | void set_size(uint64_t size); 35 | auto get_buffer() -> char*; 36 | auto get_result() -> char*; 37 | 38 | private: 39 | uint64_t size; 40 | std::vector buffer; 41 | std::vector result; 42 | 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/canvas/iterm2/iterm2.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "iterm2.hpp" 18 | #include "chunk.hpp" 19 | #include "dimensions.hpp" 20 | #include "image.hpp" 21 | #include "terminal.hpp" 22 | #include "util.hpp" 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | #ifdef HAVE_STD_EXECUTION_H 33 | # include 34 | #else 35 | # include 36 | #endif 37 | 38 | #include 39 | 40 | namespace fs = std::filesystem; 41 | 42 | Iterm2::Iterm2(std::unique_ptr new_image, std::mutex *stdout_mutex) 43 | : image(std::move(new_image)), 44 | stdout_mutex(stdout_mutex) 45 | { 46 | const auto dims = image->dimensions(); 47 | x = dims.x + 1; 48 | y = dims.y + 1; 49 | horizontal_cells = std::ceil(static_cast(image->width()) / dims.terminal->font_width); 50 | vertical_cells = std::ceil(static_cast(image->height()) / dims.terminal->font_height); 51 | } 52 | 53 | Iterm2::~Iterm2() 54 | { 55 | const std::scoped_lock lock{*stdout_mutex}; 56 | util::clear_terminal_area(x, y, horizontal_cells, vertical_cells); 57 | } 58 | 59 | void Iterm2::draw() 60 | { 61 | str.append("\033]1337;File=inline=1;"); 62 | const auto filename = image->filename(); 63 | const auto num_bytes = fs::file_size(filename); 64 | const auto encoded_filename = 65 | util::base64_encode(reinterpret_cast(filename.c_str()), filename.size()); 66 | str.append(fmt::format("size={};name={};width={}px;height={}px:", num_bytes, encoded_filename, image->width(), 67 | image->height())); 68 | 69 | const int chunk_size = 1023; 70 | const auto chunks = process_chunks(filename, chunk_size, num_bytes); 71 | const int num_chunks = std::ceil(static_cast(num_bytes) / chunk_size); 72 | const uint64_t bytes_per_chunk = 4 * ((chunk_size + 2) / 3) + 100; 73 | str.reserve((num_chunks + 2) * bytes_per_chunk); 74 | 75 | ranges::for_each(chunks, [this](const std::unique_ptr &chunk) { str.append(chunk->get_result()); }); 76 | str.append("\a"); 77 | 78 | const std::scoped_lock lock{*stdout_mutex}; 79 | util::save_cursor_position(); 80 | util::move_cursor(y, x); 81 | std::cout << str << std::flush; 82 | util::restore_cursor_position(); 83 | str.clear(); 84 | } 85 | 86 | auto Iterm2::process_chunks(const std::string &filename, int chunk_size, size_t num_bytes) 87 | -> std::vector> 88 | { 89 | const int num_chunks = std::ceil(static_cast(num_bytes) / chunk_size); 90 | std::vector> chunks; 91 | chunks.reserve(num_chunks + 2); 92 | 93 | std::ifstream ifs(filename); 94 | while (ifs.good()) { 95 | auto chunk = std::make_unique(chunk_size); 96 | ifs.read(chunk->get_buffer(), chunk_size); 97 | chunk->set_size(ifs.gcount()); 98 | chunks.push_back(std::move(chunk)); 99 | } 100 | 101 | #ifdef HAVE_STD_EXECUTION_H 102 | std::for_each(std::execution::par_unseq, chunks.begin(), chunks.end(), Iterm2Chunk::process_chunk); 103 | #else 104 | oneapi::tbb::parallel_for_each(chunks.begin(), chunks.end(), Iterm2Chunk()); 105 | #endif 106 | 107 | return chunks; 108 | } 109 | -------------------------------------------------------------------------------- /src/canvas/iterm2/iterm2.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef ITERM2_CANVAS_H 18 | #define ITERM2_CANVAS_H 19 | 20 | #include "chunk.hpp" 21 | #include "image.hpp" 22 | #include "window.hpp" 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | class Iterm2 : public Window 29 | { 30 | public: 31 | Iterm2(std::unique_ptr new_image, std::mutex *stdout_mutex); 32 | ~Iterm2() override; 33 | 34 | void draw() override; 35 | void generate_frame() override{}; 36 | 37 | private: 38 | std::unique_ptr image; 39 | std::mutex *stdout_mutex; 40 | std::string str; 41 | 42 | int x; 43 | int y; 44 | int horizontal_cells = 0; 45 | int vertical_cells = 0; 46 | 47 | static auto process_chunks(const std::string &filename, int chunk_size, size_t num_bytes) 48 | -> std::vector>; 49 | }; 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/canvas/kitty/chunk.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "chunk.hpp" 18 | #include "util.hpp" 19 | 20 | KittyChunk::KittyChunk(const unsigned char* ptr, uint64_t size): 21 | ptr(ptr), 22 | size(size) 23 | { 24 | uint64_t bufsize = 4*((size+2)/3); 25 | result.resize(bufsize + 1, 0); 26 | } 27 | 28 | void KittyChunk::process_chunk(KittyChunk& chunk) 29 | { 30 | util::base64_encode_v2(chunk.get_ptr(), chunk.get_size(), 31 | reinterpret_cast(chunk.get_result())); 32 | } 33 | 34 | void KittyChunk::operator()(KittyChunk& chunk) const 35 | { 36 | process_chunk(chunk); 37 | } 38 | 39 | auto KittyChunk::get_result() -> char* 40 | { 41 | return result.data(); 42 | } 43 | 44 | auto KittyChunk::get_ptr() const -> const unsigned char* 45 | { 46 | return ptr; 47 | } 48 | 49 | auto KittyChunk::get_size() const -> uint64_t 50 | { 51 | return size; 52 | } 53 | -------------------------------------------------------------------------------- /src/canvas/kitty/chunk.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef KITTY_CHUNK_H 18 | #define KITTY_CHUNK_H 19 | 20 | #include 21 | #include 22 | 23 | class KittyChunk 24 | { 25 | public: 26 | KittyChunk() = default; 27 | KittyChunk(const unsigned char* ptr, uint64_t size); 28 | 29 | void operator()(KittyChunk& chunk) const; 30 | 31 | auto get_result() -> char*; 32 | [[nodiscard]] auto get_ptr() const -> const unsigned char*; 33 | [[nodiscard]] auto get_size() const -> uint64_t; 34 | 35 | static void process_chunk(KittyChunk& chunk); 36 | private: 37 | const unsigned char* ptr; 38 | uint64_t size; 39 | std::vector result; 40 | }; 41 | 42 | #endif 43 | -------------------------------------------------------------------------------- /src/canvas/kitty/kitty.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "kitty.hpp" 18 | #include "dimensions.hpp" 19 | #include "util.hpp" 20 | 21 | #include 22 | 23 | #include 24 | 25 | #ifdef HAVE_STD_EXECUTION_H 26 | # include 27 | #else 28 | # include 29 | #endif 30 | 31 | Kitty::Kitty(std::unique_ptr new_image, std::mutex *stdout_mutex) 32 | : image(std::move(new_image)), 33 | stdout_mutex(stdout_mutex), 34 | id(util::generate_random_number(1)) 35 | { 36 | const auto dims = image->dimensions(); 37 | x = dims.x + 1; 38 | y = dims.y + 1; 39 | } 40 | 41 | Kitty::~Kitty() 42 | { 43 | const std::scoped_lock lock{*stdout_mutex}; 44 | std::cout << fmt::format("\033_Ga=d,d=i,i={}\033\\", id) << std::flush; 45 | } 46 | 47 | void Kitty::draw() 48 | { 49 | generate_frame(); 50 | } 51 | 52 | void Kitty::generate_frame() 53 | { 54 | const int bits_per_channel = 8; 55 | auto chunks = process_chunks(); 56 | str.append(fmt::format("\033_Ga=T,m=1,i={},q=2,f={},s={},v={};{}\033\\", id, image->channels() * bits_per_channel, 57 | image->width(), image->height(), chunks.front().get_result())); 58 | 59 | for (auto chunk = std::next(std::begin(chunks)); chunk != std::prev(std::end(chunks)); std::advance(chunk, 1)) { 60 | str.append("\033_Gm=1,q=2;"); 61 | str.append(chunk->get_result()); 62 | str.append("\033\\"); 63 | } 64 | 65 | str.append("\033_Gm=0,q=2;"); 66 | str.append(chunks.back().get_result()); 67 | str.append("\033\\"); 68 | 69 | const std::scoped_lock lock{*stdout_mutex}; 70 | util::save_cursor_position(); 71 | util::move_cursor(y, x); 72 | std::cout << str << std::flush; 73 | util::restore_cursor_position(); 74 | str.clear(); 75 | } 76 | 77 | auto Kitty::process_chunks() -> std::vector 78 | { 79 | const uint64_t chunk_size = 3068; 80 | uint64_t num_chunks = image->size() / chunk_size; 81 | uint64_t last_chunk_size = image->size() % chunk_size; 82 | if (last_chunk_size == 0) { 83 | last_chunk_size = chunk_size; 84 | num_chunks--; 85 | } 86 | const uint64_t bytes_per_chunk = 4 * ((chunk_size + 2) / 3) + 100; 87 | str.reserve((num_chunks + 2) * bytes_per_chunk); 88 | 89 | std::vector chunks; 90 | chunks.reserve(num_chunks + 2); 91 | const auto *ptr = image->data(); 92 | 93 | uint64_t idx = 0; 94 | for (; idx < num_chunks; idx++) { 95 | chunks.emplace_back(ptr + idx * chunk_size, chunk_size); 96 | } 97 | chunks.emplace_back(ptr + idx * chunk_size, last_chunk_size); 98 | 99 | #ifdef HAVE_STD_EXECUTION_H 100 | std::for_each(std::execution::par_unseq, std::begin(chunks), std::end(chunks), KittyChunk::process_chunk); 101 | #else 102 | oneapi::tbb::parallel_for_each(std::begin(chunks), std::end(chunks), KittyChunk()); 103 | #endif 104 | 105 | return chunks; 106 | } 107 | -------------------------------------------------------------------------------- /src/canvas/kitty/kitty.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef KITTY_WINDOW_H 18 | #define KITTY_WINDOW_H 19 | 20 | #include "chunk.hpp" 21 | #include "image.hpp" 22 | #include "window.hpp" 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | class Kitty : public Window 29 | { 30 | public: 31 | Kitty(std::unique_ptr new_image, std::mutex *stdout_mutex); 32 | ~Kitty() override; 33 | 34 | void draw() override; 35 | void generate_frame() override; 36 | 37 | private: 38 | std::string str; 39 | std::unique_ptr image; 40 | std::mutex *stdout_mutex; 41 | uint32_t id; 42 | int x; 43 | int y; 44 | 45 | auto process_chunks() -> std::vector; 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/canvas/sixel.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "sixel.hpp" 18 | #include "dimensions.hpp" 19 | #include "terminal.hpp" 20 | #include "util.hpp" 21 | 22 | #include 23 | #include 24 | 25 | namespace fs = std::filesystem; 26 | 27 | Sixel::Sixel(std::unique_ptr new_image, std::mutex *stdout_mutex) 28 | : image(std::move(new_image)), 29 | stdout_mutex(stdout_mutex) 30 | { 31 | const auto dims = image->dimensions(); 32 | x = dims.x + 1; 33 | y = dims.y + 1; 34 | horizontal_cells = std::ceil(static_cast(image->width()) / dims.terminal->font_width); 35 | vertical_cells = std::ceil(static_cast(image->height()) / dims.terminal->font_height); 36 | 37 | const auto draw_callback = [](char *data, int size, void *priv) -> int { 38 | auto *str = static_cast(priv); 39 | str->append(data, size); 40 | return size; 41 | }; 42 | sixel_output_new(&output, draw_callback, &str, nullptr); 43 | 44 | const auto file_size = fs::file_size(image->filename()); 45 | constexpr auto reserve_ratio = 50; 46 | str.reserve(file_size * reserve_ratio); 47 | 48 | // create dither and palette from image 49 | sixel_dither_new(&dither, -1, nullptr); 50 | sixel_dither_initialize(dither, const_cast(image->data()), image->width(), image->height(), 51 | SIXEL_PIXELFORMAT_RGB888, SIXEL_LARGE_LUM, SIXEL_REP_CENTER_BOX, SIXEL_QUALITY_HIGH); 52 | } 53 | 54 | Sixel::~Sixel() 55 | { 56 | can_draw.store(false); 57 | if (draw_thread.joinable()) { 58 | draw_thread.join(); 59 | } 60 | sixel_dither_destroy(dither); 61 | sixel_output_destroy(output); 62 | 63 | const std::scoped_lock lock{*stdout_mutex}; 64 | util::clear_terminal_area(x, y, horizontal_cells, vertical_cells); 65 | } 66 | 67 | void Sixel::draw() 68 | { 69 | if (!image->is_animated()) { 70 | generate_frame(); 71 | return; 72 | } 73 | 74 | // start drawing loop 75 | draw_thread = std::thread([this] { 76 | while (can_draw.load()) { 77 | generate_frame(); 78 | image->next_frame(); 79 | std::this_thread::sleep_for(std::chrono::milliseconds(image->frame_delay())); 80 | } 81 | }); 82 | } 83 | 84 | void Sixel::generate_frame() 85 | { 86 | // output sixel content to stream 87 | sixel_encode(const_cast(image->data()), image->width(), image->height(), 3 /*unused*/, dither, 88 | output); 89 | 90 | const std::scoped_lock lock{*stdout_mutex}; 91 | util::save_cursor_position(); 92 | util::move_cursor(y, x); 93 | std::cout << str << std::flush; 94 | util::restore_cursor_position(); 95 | str.clear(); 96 | } 97 | -------------------------------------------------------------------------------- /src/canvas/sixel.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef SIXEL_WINDOW_H 18 | #define SIXEL_WINDOW_H 19 | 20 | #include "image.hpp" 21 | #include "window.hpp" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | class Sixel : public Window 32 | { 33 | public: 34 | Sixel(std::unique_ptr new_image, std::mutex *stdout_mutex); 35 | ~Sixel() override; 36 | 37 | void draw() override; 38 | void generate_frame() override; 39 | 40 | private: 41 | std::unique_ptr image; 42 | std::mutex *stdout_mutex; 43 | 44 | std::string str; 45 | std::thread draw_thread; 46 | std::atomic can_draw{true}; 47 | 48 | int x; 49 | int y; 50 | int horizontal_cells = 0; 51 | int vertical_cells = 0; 52 | 53 | sixel_dither_t *dither = nullptr; 54 | sixel_output_t *output = nullptr; 55 | }; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/canvas/stdout.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef STDOUT_CANVAS_H 18 | #define STDOUT_CANVAS_H 19 | 20 | #include "canvas.hpp" 21 | #include "image.hpp" 22 | #include "window.hpp" 23 | 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | template 32 | class StdoutCanvas : public Canvas 33 | { 34 | public: 35 | explicit StdoutCanvas(const std::string &output) 36 | { 37 | logger = spdlog::get(output); 38 | logger->info("Canvas created"); 39 | } 40 | 41 | ~StdoutCanvas() override = default; 42 | 43 | void add_image(const std::string &identifier, std::unique_ptr new_image) override 44 | { 45 | logger->info("Displaying image with id {}", identifier); 46 | images.erase(identifier); 47 | const auto [entry, success] = 48 | images.emplace(identifier, std::make_unique(std::move(new_image), &stdout_mutex)); 49 | entry->second->draw(); 50 | } 51 | 52 | void remove_image(const std::string &identifier) override 53 | { 54 | logger->info("Removing image with id {}", identifier); 55 | images.erase(identifier); 56 | } 57 | 58 | private: 59 | std::mutex stdout_mutex; 60 | std::shared_ptr logger; 61 | std::unordered_map> images; 62 | }; 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /src/canvas/wayland/config.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "config.hpp" 18 | #include "os.hpp" 19 | #include "config/sway.hpp" 20 | #include "config/hyprland.hpp" 21 | #include "config/wayfire.hpp" 22 | #include "config/dummy.hpp" 23 | 24 | auto WaylandConfig::get() -> std::unique_ptr 25 | { 26 | const auto sway_sock = os::getenv("SWAYSOCK"); 27 | if (sway_sock.has_value()) { 28 | return std::make_unique(sway_sock.value()); 29 | } 30 | 31 | const auto hypr_sig = os::getenv("HYPRLAND_INSTANCE_SIGNATURE"); 32 | if (hypr_sig.has_value()) { 33 | return std::make_unique(hypr_sig.value()); 34 | } 35 | 36 | const auto wayfire_sock = os::getenv("WAYFIRE_SOCKET"); 37 | if (wayfire_sock.has_value()) { 38 | return std::make_unique(wayfire_sock.value()); 39 | } 40 | 41 | return std::make_unique(); 42 | } 43 | 44 | -------------------------------------------------------------------------------- /src/canvas/wayland/config.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef WAYLAND_CONFIG_H 18 | #define WAYLAND_CONFIG_H 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | struct WaylandWindowGeometry { 25 | int width; 26 | int height; 27 | int x; 28 | int y; 29 | }; 30 | 31 | class WaylandConfig 32 | { 33 | public: 34 | static auto get() -> std::unique_ptr; 35 | 36 | virtual ~WaylandConfig() = default; 37 | 38 | virtual auto get_focused_output_name() -> std::string = 0; 39 | virtual auto get_window_info() -> struct WaylandWindowGeometry = 0; 40 | virtual auto is_dummy() -> bool { return false; } 41 | virtual void initial_setup(std::string_view appid) = 0; 42 | virtual void move_window(std::string_view appid, int xcoord, int ycoord) = 0; 43 | }; 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/canvas/wayland/config/dummy.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "dummy.hpp" 18 | 19 | auto DummyWaylandConfig::get_window_info() -> struct WaylandWindowGeometry 20 | { 21 | return {}; 22 | } 23 | 24 | void DummyWaylandConfig::initial_setup([[maybe_unused]] std::string_view appid) 25 | {} 26 | 27 | void DummyWaylandConfig::move_window([[maybe_unused]] std::string_view appid, 28 | [[maybe_unused]] int xcoord, [[maybe_unused]] int ycoord) 29 | {} 30 | -------------------------------------------------------------------------------- /src/canvas/wayland/config/dummy.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef DUMMY_WAYLAND_CONFIG_H 18 | #define DUMMY_WAYLAND_CONFIG_H 19 | 20 | #include "../config.hpp" 21 | 22 | class DummyWaylandConfig : public WaylandConfig 23 | { 24 | public: 25 | DummyWaylandConfig() = default; 26 | ~DummyWaylandConfig() override = default; 27 | 28 | auto get_focused_output_name() -> std::string override { return {}; }; 29 | auto get_window_info() -> struct WaylandWindowGeometry override; 30 | auto is_dummy() -> bool override { return true; } 31 | void initial_setup(std::string_view appid) override; 32 | void move_window(std::string_view appid, int xcoord, int ycoord) override; 33 | }; 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/canvas/wayland/config/hyprland.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "hyprland.hpp" 18 | #include "os.hpp" 19 | #include "tmux.hpp" 20 | #include "util/socket.hpp" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | using njson = nlohmann::json; 28 | namespace fs = std::filesystem; 29 | 30 | HyprlandSocket::HyprlandSocket(const std::string_view signature) 31 | : logger(spdlog::get("wayland")) 32 | { 33 | const auto socket_base_dir = os::getenv("XDG_RUNTIME_DIR").value_or("/tmp"); 34 | const auto socket_rel_path = fmt::format("hypr/{}/.socket.sock", signature); 35 | socket_path = fmt::format("{}/{}", socket_base_dir, socket_rel_path); 36 | // XDG_RUNTIME_DIR set but hyprland < 0.40 37 | if (!fs::exists(socket_path)) { 38 | socket_path = fmt::format("/tmp/{}", socket_rel_path); 39 | } 40 | 41 | logger->info("Using hyprland socket {}", socket_path); 42 | const auto active = request_result("j/activewindow"); 43 | address = active.at("address"); 44 | set_active_monitor(); 45 | } 46 | 47 | void HyprlandSocket::set_active_monitor() 48 | { 49 | const auto monitors = request_result("j/monitors"); 50 | for (const auto &monitor : monitors) { 51 | bool focused = monitor.at("focused"); 52 | if (focused) { 53 | output_name = monitor.at("name"); 54 | output_scale = monitor.at("scale"); 55 | break; 56 | } 57 | } 58 | } 59 | 60 | auto HyprlandSocket::get_focused_output_name() -> std::string 61 | { 62 | return output_name; 63 | } 64 | 65 | auto HyprlandSocket::request_result(const std::string_view payload) -> nlohmann::json 66 | { 67 | const UnixSocket socket{socket_path}; 68 | socket.write(payload.data(), payload.size()); 69 | const std::string result = socket.read_until_empty(); 70 | return njson::parse(result); 71 | } 72 | 73 | void HyprlandSocket::request(const std::string_view payload) 74 | { 75 | const UnixSocket socket{socket_path}; 76 | logger->debug("Running socket command {}", payload); 77 | socket.write(payload.data(), payload.length()); 78 | } 79 | 80 | auto HyprlandSocket::get_active_window() -> nlohmann::json 81 | { 82 | // recalculate address in case it changed 83 | if (tmux::is_used()) { 84 | const auto active = request_result("j/activewindow"); 85 | address = active.at("address"); 86 | } 87 | const auto clients = request_result("j/clients"); 88 | const auto client = ranges::find_if(clients, [this](const njson &json) { return json.at("address") == address; }); 89 | if (client == clients.end()) { 90 | throw std::runtime_error("Active window not found"); 91 | } 92 | return *client; 93 | } 94 | 95 | auto HyprlandSocket::get_window_info() -> struct WaylandWindowGeometry { 96 | const auto terminal = get_active_window(); 97 | const auto &sizes = terminal.at("size"); 98 | const auto &coords = terminal.at("at"); 99 | 100 | return { 101 | .width = sizes.at(0), 102 | .height = sizes.at(1), 103 | .x = coords.at(0), 104 | .y = coords.at(1), 105 | }; 106 | } 107 | 108 | void HyprlandSocket::initial_setup(const std::string_view appid) 109 | { 110 | disable_focus(appid); 111 | enable_floating(appid); 112 | remove_borders(appid); 113 | remove_rounding(appid); 114 | } 115 | 116 | void HyprlandSocket::remove_rounding(const std::string_view appid) 117 | { 118 | const auto payload = fmt::format("/keyword windowrulev2 rounding 0,title:{}", appid); 119 | request(payload); 120 | } 121 | 122 | void HyprlandSocket::disable_focus(const std::string_view appid) 123 | { 124 | const auto payload = fmt::format("/keyword windowrulev2 nofocus,title:{}", appid); 125 | request(payload); 126 | } 127 | 128 | void HyprlandSocket::enable_floating(const std::string_view appid) 129 | { 130 | const auto payload = fmt::format("/keyword windowrulev2 float,title:{}", appid); 131 | request(payload); 132 | } 133 | 134 | void HyprlandSocket::remove_borders(const std::string_view appid) 135 | { 136 | const auto payload = fmt::format("/keyword windowrulev2 noborder,title:{}", appid); 137 | request(payload); 138 | } 139 | 140 | void HyprlandSocket::move_window(const std::string_view appid, int xcoord, int ycoord) 141 | { 142 | int res_x = xcoord; 143 | int res_y = ycoord; 144 | if (output_scale > 1.0F) { 145 | const int offset = 10; 146 | res_x = res_x / 2 + offset; 147 | res_y = res_y / 2 + offset; 148 | } 149 | const auto payload = fmt::format("/dispatch movewindowpixel exact {} {},title:{}", res_x, res_y, appid); 150 | request(payload); 151 | } 152 | -------------------------------------------------------------------------------- /src/canvas/wayland/config/hyprland.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef HYPRLAND_SOCKET_H 18 | #define HYPRLAND_SOCKET_H 19 | 20 | #include "../config.hpp" 21 | 22 | #include 23 | #include 24 | 25 | class HyprlandSocket : public WaylandConfig 26 | { 27 | public: 28 | explicit HyprlandSocket(std::string_view signature); 29 | ~HyprlandSocket() override = default; 30 | 31 | auto get_window_info() -> struct WaylandWindowGeometry override; 32 | auto get_focused_output_name() -> std::string override; 33 | void initial_setup(std::string_view appid) override; 34 | void move_window(std::string_view appid, int xcoord, int ycoord) override; 35 | 36 | private: 37 | void disable_focus(std::string_view appid); 38 | void enable_floating(std::string_view appid); 39 | void remove_borders(std::string_view appid); 40 | void remove_rounding(std::string_view appid); 41 | void request(std::string_view payload); 42 | auto request_result(std::string_view payload) -> nlohmann::json; 43 | auto get_active_window() -> nlohmann::json; 44 | void set_active_monitor(); 45 | 46 | std::shared_ptr logger; 47 | std::string socket_path; 48 | std::string address; 49 | std::string output_name; 50 | float output_scale = 1.0F; 51 | }; 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /src/canvas/wayland/config/sway.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "sway.hpp" 18 | #include "application.hpp" 19 | #include "os.hpp" 20 | #include "tmux.hpp" 21 | #include "util.hpp" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | using njson = nlohmann::json; 32 | 33 | constexpr auto ipc_magic = std::string_view{"i3-ipc"}; 34 | constexpr auto ipc_header_size = ipc_magic.size() + 8; 35 | 36 | SwaySocket::SwaySocket(const std::string_view endpoint) 37 | : socket(endpoint), 38 | logger(spdlog::get("wayland")) 39 | { 40 | logger->info("Using sway socket {}", endpoint); 41 | set_active_output_info(); 42 | } 43 | 44 | struct __attribute__((packed)) ipc_header { 45 | std::array magic; 46 | uint32_t len; 47 | uint32_t type; 48 | }; 49 | 50 | auto SwaySocket::get_window_info() -> struct WaylandWindowGeometry { 51 | const auto nodes = get_nodes(); 52 | const auto window = get_active_window(nodes); 53 | const auto &rect = window.at("rect"); 54 | return {.width = rect.at("width"), 55 | .height = rect.at("height"), 56 | .x = rect.at("x").get() - output_info.x, 57 | .y = rect.at("y").get() - output_info.y}; 58 | } 59 | 60 | auto SwaySocket::get_active_window(const std::vector& nodes) -> nlohmann::json 61 | { 62 | const auto pids = tmux::get_client_pids().value_or(std::vector{Application::parent_pid}); 63 | 64 | for (const auto pid : pids) { 65 | const auto tree = util::get_process_tree(pid); 66 | const auto found = ranges::find_if(nodes, [&tree](const njson &json) -> bool { 67 | try { 68 | return ranges::find(tree, json.at("pid").get()) != tree.end(); 69 | } catch (const njson::out_of_range &err) { 70 | return false; 71 | } 72 | }); 73 | if (found != nodes.end()) { 74 | return *found; 75 | } 76 | } 77 | return nullptr; 78 | } 79 | 80 | auto SwaySocket::get_focused_output_name() -> std::string 81 | { 82 | return output_info.name; 83 | }; 84 | 85 | void SwaySocket::set_active_output_info() 86 | { 87 | const auto outputs = ipc_message(IPC_GET_OUTPUTS); 88 | for (const auto &node : outputs) { 89 | bool focused = node.at("focused"); 90 | if (focused) { 91 | const auto &rect = node.at("rect"); 92 | output_info = { 93 | .x = rect.at("x"), 94 | .y = rect.at("y"), 95 | .scale = node.at("scale"), 96 | .name = node.at("name"), 97 | }; 98 | break; 99 | } 100 | } 101 | } 102 | 103 | void SwaySocket::initial_setup(const std::string_view appid) 104 | { 105 | disable_focus(appid); 106 | enable_floating(appid); 107 | } 108 | 109 | void SwaySocket::disable_focus(const std::string_view appid) 110 | { 111 | const auto payload = fmt::format(R"(no_focus [app_id="{}"])", appid); 112 | ipc_command(payload); 113 | } 114 | 115 | void SwaySocket::enable_floating(const std::string_view appid) 116 | { 117 | ipc_command(appid, "floating enable"); 118 | } 119 | 120 | void SwaySocket::move_window(const std::string_view appid, int xcoord, int ycoord) 121 | { 122 | int res_x = xcoord; 123 | int res_y = ycoord; 124 | if (output_info.scale > 1.0F) { 125 | res_x = res_x / 2; 126 | res_y = res_y / 2; 127 | } 128 | const auto payload = fmt::format(R"([app_id="{}"] move position {} {})", appid, res_x, res_y); 129 | ipc_command(payload); 130 | } 131 | 132 | auto SwaySocket::ipc_message(ipc_message_type type, const std::string_view payload) const -> nlohmann::json 133 | { 134 | struct ipc_header header; 135 | header.len = payload.size(); 136 | header.type = type; 137 | ipc_magic.copy(header.magic.data(), ipc_magic.size()); 138 | 139 | if (!payload.empty()) { 140 | logger->debug("Running socket command {}", payload); 141 | } 142 | socket.write(&header, ipc_header_size); 143 | socket.write(payload.data(), payload.size()); 144 | 145 | socket.read(&header, ipc_header_size); 146 | std::string buff(header.len, 0); 147 | socket.read(buff.data(), buff.size()); 148 | return njson::parse(buff); 149 | } 150 | 151 | auto SwaySocket::get_nodes() const -> std::vector 152 | { 153 | logger->debug("Obtaining sway tree"); 154 | const auto tree = ipc_message(IPC_GET_TREE); 155 | std::stack nodes_st; 156 | std::vector nodes_vec; 157 | 158 | nodes_st.push(tree); 159 | 160 | while (!nodes_st.empty()) { 161 | const auto top = nodes_st.top(); 162 | nodes_st.pop(); 163 | nodes_vec.push_back(top); 164 | for (const auto &node : top.at("nodes")) { 165 | nodes_st.push(node); 166 | } 167 | for (const auto &node : top.at("floating_nodes")) { 168 | nodes_st.push(node); 169 | } 170 | } 171 | return nodes_vec; 172 | } 173 | 174 | void SwaySocket::ipc_command(const std::string_view appid, const std::string_view command) const 175 | { 176 | const auto payload = fmt::format(R"(for_window [app_id="{}"] {})", appid, command); 177 | ipc_command(payload); 178 | } 179 | 180 | void SwaySocket::ipc_command(const std::string_view payload) const 181 | { 182 | std::ignore = ipc_message(IPC_COMMAND, payload); 183 | } 184 | -------------------------------------------------------------------------------- /src/canvas/wayland/config/sway.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef SWAY_SOCKET_H 18 | #define SWAY_SOCKET_H 19 | 20 | #include "../config.hpp" 21 | #include "util/socket.hpp" 22 | 23 | #include 24 | #include 25 | 26 | enum ipc_message_type { IPC_COMMAND = 0, IPC_GET_WORKSPACES = 1, IPC_GET_OUTPUTS = 3, IPC_GET_TREE = 4 }; 27 | 28 | struct SwayOutputInfo { 29 | int x; 30 | int y; 31 | float scale; 32 | std::string name; 33 | }; 34 | 35 | class SwaySocket : public WaylandConfig 36 | { 37 | public: 38 | explicit SwaySocket(std::string_view endpoint); 39 | ~SwaySocket() override = default; 40 | 41 | auto get_focused_output_name() -> std::string override; 42 | auto get_window_info() -> struct WaylandWindowGeometry override; 43 | void initial_setup(std::string_view appid) override; 44 | void move_window(std::string_view appid, int xcoord, int ycoord) override; 45 | static auto get_active_window(const std::vector &nodes) -> nlohmann::json; 46 | 47 | private: 48 | void disable_focus(std::string_view appid); 49 | void enable_floating(std::string_view appid); 50 | void ipc_command(std::string_view appid, std::string_view command) const; 51 | void ipc_command(std::string_view payload) const; 52 | void set_active_output_info(); 53 | 54 | [[nodiscard]] auto get_nodes() const -> std::vector; 55 | [[nodiscard]] auto ipc_message(ipc_message_type type, std::string_view payload = "") const -> nlohmann::json; 56 | 57 | UnixSocket socket; 58 | std::shared_ptr logger; 59 | struct SwayOutputInfo output_info; 60 | }; 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/canvas/wayland/config/wayfire.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "wayfire.hpp" 18 | 19 | using njson = nlohmann::json; 20 | 21 | WayfireSocket::WayfireSocket(const std::string_view endpoint): 22 | socket(endpoint), 23 | logger(spdlog::get("wayland")) 24 | { 25 | logger->info("Using wayfire socket {}", endpoint); 26 | } 27 | 28 | auto WayfireSocket::get_window_info() -> struct WaylandWindowGeometry 29 | { 30 | const auto response = request("window-rules/get-focused-view"); 31 | const auto& info = response.at("info"); 32 | const auto& geometry = info.at("geometry"); 33 | const int decoration_height = 25; 34 | return { 35 | .width = geometry.at("width"), 36 | .height = geometry.at("height").get() - decoration_height, 37 | .x = 0, 38 | .y = 0 39 | }; 40 | } 41 | 42 | void WayfireSocket::initial_setup([[maybe_unused]] const std::string_view appid) 43 | { 44 | // all is handled by the ueberzug plugin 45 | } 46 | 47 | void WayfireSocket::move_window(const std::string_view appid, int xcoord, int ycoord) 48 | { 49 | const njson payload_data = { 50 | {"app-id", appid}, 51 | {"x", xcoord}, 52 | {"y", ycoord} 53 | }; 54 | std::ignore = request("ueberzugpp/set_offset", payload_data); 55 | } 56 | 57 | auto WayfireSocket::request(const std::string_view method, const njson& data) const -> njson 58 | { 59 | const njson json = { 60 | {"method", method}, 61 | {"data", data} 62 | }; 63 | const auto payload = json.dump(); 64 | const uint32_t payload_size = payload.length(); 65 | socket.write(&payload_size, sizeof(uint32_t)); 66 | socket.write(payload.c_str(), payload_size); 67 | 68 | uint32_t response_size = 0; 69 | socket.read(&response_size, sizeof(uint32_t)); 70 | std::string buffer (response_size, 0); 71 | socket.read(buffer.data(), response_size); 72 | return njson::parse(buffer); 73 | } 74 | -------------------------------------------------------------------------------- /src/canvas/wayland/config/wayfire.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef WAYFIRE_SOCKET_H 18 | #define WAYFIRE_SOCKET_H 19 | 20 | #include "../config.hpp" 21 | #include "util/socket.hpp" 22 | 23 | #include 24 | #include 25 | 26 | class WayfireSocket : public WaylandConfig 27 | { 28 | public: 29 | explicit WayfireSocket(std::string_view endpoint); 30 | ~WayfireSocket() override = default; 31 | 32 | auto get_window_info() -> struct WaylandWindowGeometry override; 33 | auto get_focused_output_name() -> std::string override { return {}; }; 34 | void initial_setup(std::string_view appid) override; 35 | void move_window(std::string_view appid, int xcoord, int ycoord) override; 36 | 37 | private: 38 | [[nodiscard]] auto request(std::string_view method, const nlohmann::json &data = {}) const -> nlohmann::json; 39 | 40 | UnixSocket socket; 41 | std::shared_ptr logger; 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/canvas/wayland/wayland.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef WAYLAND_CANVAS_H 18 | #define WAYLAND_CANVAS_H 19 | 20 | #include "canvas.hpp" 21 | #include "config.hpp" 22 | #include "flags.hpp" 23 | #include "wayland-xdg-shell-client-protocol.h" 24 | #include "window/waylandwindow.hpp" 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | 33 | #ifdef ENABLE_OPENGL 34 | # include "util/egl.hpp" 35 | # include 36 | #endif 37 | 38 | class WaylandCanvas : public Canvas 39 | { 40 | public: 41 | explicit WaylandCanvas(); 42 | ~WaylandCanvas() override; 43 | 44 | static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, 45 | uint32_t version); 46 | static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial); 47 | 48 | static void output_scale(void *data, struct wl_output *output, int32_t scale); 49 | static void output_name(void *data, struct wl_output *output, const char *name); 50 | static void output_done(void *data, struct wl_output *output); 51 | 52 | void add_image(const std::string &identifier, std::unique_ptr new_image) override; 53 | void remove_image(const std::string &identifier) override; 54 | void show() override; 55 | void hide() override; 56 | 57 | struct wl_compositor *compositor = nullptr; 58 | struct wl_shm *wl_shm = nullptr; 59 | struct xdg_wm_base *xdg_base = nullptr; 60 | 61 | std::pair output_pair; 62 | std::unordered_map output_info; 63 | 64 | private: 65 | struct wl_display *display = nullptr; 66 | struct wl_registry *registry = nullptr; 67 | std::thread event_handler; 68 | 69 | std::shared_ptr logger; 70 | std::unique_ptr config; 71 | std::shared_ptr flags; 72 | std::unordered_map> windows; 73 | 74 | #ifdef ENABLE_OPENGL 75 | std::unique_ptr> egl; 76 | bool egl_available = true; 77 | #endif 78 | 79 | struct XdgStructAgg xdg_agg; 80 | 81 | void handle_events(); 82 | }; 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /src/canvas/wayland/window/shm.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "shm.hpp" 18 | #include "util.hpp" 19 | #include "util/ptr.hpp" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | WaylandShm::WaylandShm(int width, int height, int scale_factor, struct wl_shm *shm) 32 | : shm(shm), 33 | width(width), 34 | height(height), 35 | stride(width * 4), 36 | pool_size(height * stride * scale_factor) 37 | { 38 | const int path_size = 32; 39 | shm_path = fmt::format("/{}", util::generate_random_string(path_size)); 40 | create_shm_file(); 41 | allocate_pool_buffers(); 42 | } 43 | 44 | void WaylandShm::create_shm_file() 45 | { 46 | fd = memfd_create("ueberzugpp-shm", 0); 47 | if (fd == -1) { 48 | throw std::system_error(errno, std::system_category()); 49 | } 50 | int res = ftruncate(fd, pool_size); 51 | if (res == -1) { 52 | throw std::system_error(errno, std::system_category()); 53 | } 54 | auto *pool_ptr = mmap(nullptr, pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 55 | if (pool_ptr == MAP_FAILED) { 56 | throw std::system_error(errno, std::system_category()); 57 | } 58 | pool_data = static_cast(pool_ptr); 59 | } 60 | 61 | void WaylandShm::allocate_pool_buffers() 62 | { 63 | const auto pool = c_unique_ptr{wl_shm_create_pool(shm, fd, pool_size)}; 64 | buffer = wl_shm_pool_create_buffer(pool.get(), 0, width, height, stride, WL_SHM_FORMAT_ARGB8888); 65 | } 66 | 67 | WaylandShm::~WaylandShm() 68 | { 69 | shm_unlink(shm_path.c_str()); 70 | close(fd); 71 | munmap(pool_data, pool_size); 72 | if (buffer != nullptr) { 73 | wl_buffer_destroy(buffer); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/canvas/wayland/window/shm.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef WAYLAND_SHM_H 18 | #define WAYLAND_SHM_H 19 | 20 | #include 21 | #include 22 | 23 | class WaylandShm 24 | { 25 | public: 26 | WaylandShm(int width, int height, int scale_factor, struct wl_shm *shm); 27 | ~WaylandShm(); 28 | 29 | struct wl_buffer *buffer = nullptr; 30 | uint8_t *pool_data; 31 | 32 | private: 33 | void create_shm_file(); 34 | void allocate_pool_buffers(); 35 | 36 | struct wl_shm *shm = nullptr; 37 | 38 | int fd = 0; 39 | std::string shm_path; 40 | 41 | int width = 0; 42 | int height = 0; 43 | int stride = 0; 44 | int pool_size = 0; 45 | }; 46 | 47 | #endif 48 | -------------------------------------------------------------------------------- /src/canvas/wayland/window/waylandegl.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "waylandegl.hpp" 18 | #include "dimensions.hpp" 19 | #include "util.hpp" 20 | 21 | #include 22 | #include 23 | 24 | constexpr int id_len = 10; 25 | 26 | constexpr struct xdg_surface_listener xdg_surface_listener_egl = { 27 | .configure = WaylandEglWindow::xdg_surface_configure, 28 | }; 29 | 30 | constexpr struct wl_callback_listener frame_listener_egl = {.done = WaylandEglWindow::wl_surface_frame_done}; 31 | 32 | WaylandEglWindow::WaylandEglWindow(struct wl_compositor *compositor, struct xdg_wm_base *xdg_base, 33 | const EGLUtil *egl, 34 | std::unique_ptr new_image, WaylandConfig *new_config, 35 | struct XdgStructAgg *xdg_agg) 36 | : compositor(compositor), 37 | xdg_base(xdg_base), 38 | surface(wl_compositor_create_surface(compositor)), 39 | xdg_surface(xdg_wm_base_get_xdg_surface(xdg_base, surface)), 40 | xdg_toplevel(xdg_surface_get_toplevel(xdg_surface)), 41 | image(std::move(new_image)), 42 | config(new_config), 43 | egl_window(wl_egl_window_create(surface, image->width(), image->height())), 44 | egl(egl), 45 | appid(fmt::format("ueberzugpp_{}", util::generate_random_string(id_len))), 46 | xdg_agg(xdg_agg) 47 | { 48 | config->initial_setup(appid); 49 | opengl_setup(); 50 | xdg_setup(); 51 | } 52 | 53 | WaylandEglWindow::~WaylandEglWindow() 54 | { 55 | opengl_cleanup(); 56 | delete_xdg_structs(); 57 | delete_wayland_structs(); 58 | } 59 | 60 | void WaylandEglWindow::opengl_cleanup() 61 | { 62 | egl->run_contained(egl_context, egl_surface, [this] { 63 | glDeleteTextures(1, &texture); 64 | glDeleteFramebuffers(1, &fbo); 65 | }); 66 | eglDestroySurface(egl->display, egl_surface); 67 | eglDestroyContext(egl->display, egl_context); 68 | } 69 | 70 | void WaylandEglWindow::finish_init() 71 | { 72 | auto xdg = std::make_unique(); 73 | xdg->ptr = weak_from_this(); 74 | this_ptr = xdg.get(); 75 | xdg_agg->ptrs.push_back(std::move(xdg)); 76 | setup_listeners(); 77 | visible = true; 78 | } 79 | 80 | void WaylandEglWindow::opengl_setup() 81 | { 82 | egl_surface = egl->create_surface(egl_window); 83 | if (egl_surface == EGL_NO_SURFACE) { 84 | throw std::runtime_error(""); 85 | } 86 | 87 | egl_context = egl->create_context(egl_surface); 88 | if (egl_context == EGL_NO_CONTEXT) { 89 | throw std::runtime_error(""); 90 | } 91 | 92 | egl->run_contained(egl_surface, egl_context, [this] { 93 | eglSwapInterval(egl->display, 0); 94 | glGenFramebuffers(1, &fbo); 95 | glGenTextures(1, &texture); 96 | }); 97 | } 98 | 99 | void WaylandEglWindow::setup_listeners() 100 | { 101 | xdg_surface_add_listener(xdg_surface, &xdg_surface_listener_egl, this_ptr); 102 | wl_surface_commit(surface); 103 | 104 | if (image->is_animated()) { 105 | callback = wl_surface_frame(surface); 106 | wl_callback_add_listener(callback, &frame_listener_egl, this_ptr); 107 | } 108 | } 109 | 110 | void WaylandEglWindow::xdg_setup() 111 | { 112 | xdg_toplevel_set_app_id(xdg_toplevel, appid.c_str()); 113 | xdg_toplevel_set_title(xdg_toplevel, appid.c_str()); 114 | } 115 | 116 | void WaylandEglWindow::delete_xdg_structs() 117 | { 118 | if (xdg_toplevel != nullptr) { 119 | xdg_toplevel_destroy(xdg_toplevel); 120 | xdg_toplevel = nullptr; 121 | } 122 | if (xdg_surface != nullptr) { 123 | xdg_surface_destroy(xdg_surface); 124 | xdg_surface = nullptr; 125 | } 126 | } 127 | 128 | void WaylandEglWindow::delete_wayland_structs() 129 | { 130 | if (egl_window != nullptr) { 131 | wl_egl_window_destroy(egl_window); 132 | egl_window = nullptr; 133 | } 134 | if (surface != nullptr) { 135 | wl_surface_destroy(surface); 136 | surface = nullptr; 137 | } 138 | } 139 | 140 | void WaylandEglWindow::draw() 141 | { 142 | load_framebuffer(); 143 | 144 | wl_surface_commit(surface); 145 | move_window(); 146 | } 147 | 148 | void WaylandEglWindow::load_framebuffer() 149 | { 150 | std::scoped_lock lock{egl_mutex}; 151 | egl->run_contained(egl_surface, egl_context, [this] { 152 | egl->get_texture_from_image(*image, texture); 153 | glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); 154 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); 155 | glBlitFramebuffer(0, 0, image->width(), image->height(), 0, 0, image->width(), image->height(), 156 | GL_COLOR_BUFFER_BIT, GL_NEAREST); 157 | 158 | eglSwapBuffers(egl->display, egl_surface); 159 | }); 160 | } 161 | 162 | void WaylandEglWindow::move_window() 163 | { 164 | const auto dims = image->dimensions(); 165 | const auto cur_window = config->get_window_info(); 166 | const int wayland_x = dims.xpixels() + dims.padding_horizontal; 167 | const int wayland_y = dims.ypixels() + dims.padding_vertical; 168 | const int xcoord = cur_window.x + wayland_x; 169 | const int ycoord = cur_window.y + wayland_y; 170 | config->move_window(appid, xcoord, ycoord); 171 | } 172 | 173 | void WaylandEglWindow::generate_frame() 174 | { 175 | std::this_thread::sleep_for(std::chrono::milliseconds(image->frame_delay())); 176 | callback = wl_surface_frame(surface); 177 | wl_callback_add_listener(callback, &frame_listener_egl, this_ptr); 178 | 179 | image->next_frame(); 180 | load_framebuffer(); 181 | 182 | wl_surface_commit(surface); 183 | } 184 | 185 | void WaylandEglWindow::show() 186 | { 187 | if (visible) { 188 | return; 189 | } 190 | visible = true; 191 | xdg_surface = xdg_wm_base_get_xdg_surface(xdg_base, surface); 192 | xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); 193 | xdg_setup(); 194 | setup_listeners(); 195 | } 196 | 197 | void WaylandEglWindow::hide() 198 | { 199 | if (!visible) { 200 | return; 201 | } 202 | visible = false; 203 | const std::scoped_lock lock{draw_mutex}; 204 | delete_xdg_structs(); 205 | wl_surface_attach(surface, nullptr, 0, 0); 206 | wl_surface_commit(surface); 207 | } 208 | 209 | void WaylandEglWindow::xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) 210 | { 211 | xdg_surface_ack_configure(xdg_surface, serial); 212 | const auto *tmp = static_cast(data); 213 | const auto window = tmp->ptr.lock(); 214 | if (!window) { 215 | return; 216 | } 217 | auto *egl_window = dynamic_cast(window.get()); 218 | egl_window->draw(); 219 | } 220 | 221 | void WaylandEglWindow::wl_surface_frame_done(void *data, struct wl_callback *callback, [[maybe_unused]] uint32_t time) 222 | { 223 | wl_callback_destroy(callback); 224 | const auto *tmp = static_cast(data); 225 | const auto window = tmp->ptr.lock(); 226 | if (!window) { 227 | return; 228 | } 229 | auto *egl_window = dynamic_cast(window.get()); 230 | const std::scoped_lock lock{egl_window->draw_mutex}; 231 | if (!egl_window->visible) { 232 | return; 233 | } 234 | egl_window->generate_frame(); 235 | } 236 | -------------------------------------------------------------------------------- /src/canvas/wayland/window/waylandegl.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef WAYLAND_EGL_WINDOW_H 18 | #define WAYLAND_EGL_WINDOW_H 19 | 20 | #include "../config.hpp" 21 | #include "image.hpp" 22 | #include "util/egl.hpp" 23 | #include "wayland-xdg-shell-client-protocol.h" 24 | #include "waylandwindow.hpp" 25 | 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | class WaylandEglWindow : public WaylandWindow 33 | { 34 | public: 35 | WaylandEglWindow(struct wl_compositor *compositor, struct xdg_wm_base *xdg_base, 36 | const EGLUtil *egl, std::unique_ptr new_image, 37 | WaylandConfig *new_config, struct XdgStructAgg *xdg_agg); 38 | ~WaylandEglWindow() override; 39 | static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial); 40 | static void wl_surface_frame_done(void *data, struct wl_callback *callback, uint32_t time); 41 | 42 | void draw() override; 43 | void generate_frame() override; 44 | void show() override; 45 | void hide() override; 46 | 47 | void finish_init() override; 48 | 49 | private: 50 | struct wl_compositor *compositor; 51 | struct xdg_wm_base *xdg_base; 52 | 53 | struct wl_surface *surface = nullptr; 54 | struct xdg_surface *xdg_surface = nullptr; 55 | struct xdg_toplevel *xdg_toplevel = nullptr; 56 | struct wl_callback *callback; 57 | 58 | std::unique_ptr image; 59 | WaylandConfig *config; 60 | 61 | EGLSurface egl_surface; 62 | EGLContext egl_context; 63 | 64 | struct wl_egl_window *egl_window = nullptr; 65 | const EGLUtil *egl; 66 | 67 | GLuint texture; 68 | GLuint fbo; 69 | 70 | std::mutex draw_mutex; 71 | std::mutex egl_mutex; 72 | 73 | std::string appid; 74 | void *this_ptr; 75 | struct XdgStructAgg *xdg_agg; 76 | bool visible = false; 77 | 78 | void move_window(); 79 | void delete_wayland_structs(); 80 | void delete_xdg_structs(); 81 | 82 | void opengl_cleanup(); 83 | void xdg_setup(); 84 | void setup_listeners(); 85 | void opengl_setup(); 86 | void load_framebuffer(); 87 | }; 88 | #endif 89 | -------------------------------------------------------------------------------- /src/canvas/wayland/window/waylandshm.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "waylandshm.hpp" 18 | #include "dimensions.hpp" 19 | #include "shm.hpp" 20 | #include "util.hpp" 21 | 22 | #include 23 | 24 | constexpr int id_len = 10; 25 | 26 | constexpr struct xdg_surface_listener xdg_surface_listener = { 27 | .configure = WaylandShmWindow::xdg_surface_configure, 28 | }; 29 | 30 | constexpr struct wl_callback_listener frame_listener = {.done = WaylandShmWindow::wl_surface_frame_done}; 31 | 32 | void WaylandShmWindow::xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) 33 | { 34 | xdg_surface_ack_configure(xdg_surface, serial); 35 | const auto *tmp = static_cast(data); 36 | const auto window = tmp->ptr.lock(); 37 | if (!window) { 38 | return; 39 | } 40 | auto *shm_window = dynamic_cast(window.get()); 41 | shm_window->wl_draw(shm_window->output_scale); 42 | } 43 | 44 | void WaylandShmWindow::wl_surface_frame_done(void *data, struct wl_callback *callback, [[maybe_unused]] uint32_t time) 45 | { 46 | wl_callback_destroy(callback); 47 | const auto *tmp = static_cast(data); 48 | const auto window = tmp->ptr.lock(); 49 | if (!window) { 50 | return; 51 | } 52 | auto *shm_window = dynamic_cast(window.get()); 53 | const std::scoped_lock lock{shm_window->draw_mutex}; 54 | if (!shm_window->visible) { 55 | return; 56 | } 57 | shm_window->generate_frame(); 58 | } 59 | 60 | WaylandShmWindow::WaylandShmWindow(WaylandCanvas *canvas, std::unique_ptr new_image, 61 | struct XdgStructAgg *xdg_agg, WaylandConfig *config) 62 | : config(config), 63 | xdg_base(canvas->xdg_base), 64 | surface(wl_compositor_create_surface(canvas->compositor)), 65 | xdg_surface(xdg_wm_base_get_xdg_surface(xdg_base, surface)), 66 | xdg_toplevel(xdg_surface_get_toplevel(xdg_surface)), 67 | image(std::move(new_image)), 68 | appid(fmt::format("ueberzugpp_{}", util::generate_random_string(id_len))), 69 | xdg_agg(xdg_agg) 70 | { 71 | config->initial_setup(appid); 72 | xdg_setup(); 73 | output_scale = canvas->output_info.at(config->get_focused_output_name()); 74 | shm = std::make_unique(image->width(), image->height(), output_scale, canvas->wl_shm); 75 | } 76 | 77 | void WaylandShmWindow::finish_init() 78 | { 79 | auto xdg = std::make_unique(); 80 | xdg->ptr = weak_from_this(); 81 | this_ptr = xdg.get(); 82 | xdg_agg->ptrs.push_back(std::move(xdg)); 83 | 84 | setup_listeners(); 85 | visible = true; 86 | } 87 | 88 | void WaylandShmWindow::setup_listeners() 89 | { 90 | xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, this_ptr); 91 | wl_surface_commit(surface); 92 | 93 | if (image->is_animated()) { 94 | callback = wl_surface_frame(surface); 95 | wl_callback_add_listener(callback, &frame_listener, this_ptr); 96 | } 97 | } 98 | 99 | void WaylandShmWindow::xdg_setup() 100 | { 101 | xdg_toplevel_set_app_id(xdg_toplevel, appid.c_str()); 102 | xdg_toplevel_set_title(xdg_toplevel, appid.c_str()); 103 | } 104 | 105 | WaylandShmWindow::~WaylandShmWindow() 106 | { 107 | delete_xdg_structs(); 108 | delete_wayland_structs(); 109 | } 110 | 111 | void WaylandShmWindow::wl_draw(int32_t scale_factor) 112 | { 113 | std::memcpy(shm->pool_data, image->data(), image->size()); 114 | wl_surface_attach(surface, shm->buffer, 0, 0); 115 | wl_surface_set_buffer_scale(surface, scale_factor); 116 | wl_surface_commit(surface); 117 | move_window(); 118 | } 119 | 120 | void WaylandShmWindow::show() 121 | { 122 | if (visible) { 123 | return; 124 | } 125 | visible = true; 126 | xdg_surface = xdg_wm_base_get_xdg_surface(xdg_base, surface); 127 | xdg_toplevel = xdg_surface_get_toplevel(xdg_surface); 128 | xdg_setup(); 129 | setup_listeners(); 130 | } 131 | 132 | void WaylandShmWindow::hide() 133 | { 134 | if (!visible) { 135 | return; 136 | } 137 | visible = false; 138 | const std::scoped_lock lock{draw_mutex}; 139 | delete_xdg_structs(); 140 | wl_surface_attach(surface, nullptr, 0, 0); 141 | wl_surface_commit(surface); 142 | } 143 | 144 | void WaylandShmWindow::delete_xdg_structs() 145 | { 146 | if (xdg_toplevel != nullptr) { 147 | xdg_toplevel_destroy(xdg_toplevel); 148 | xdg_toplevel = nullptr; 149 | } 150 | if (xdg_surface != nullptr) { 151 | xdg_surface_destroy(xdg_surface); 152 | xdg_surface = nullptr; 153 | } 154 | } 155 | 156 | void WaylandShmWindow::delete_wayland_structs() 157 | { 158 | if (surface != nullptr) { 159 | wl_surface_destroy(surface); 160 | surface = nullptr; 161 | } 162 | } 163 | 164 | void WaylandShmWindow::move_window() 165 | { 166 | const auto dims = image->dimensions(); 167 | const auto cur_window = config->get_window_info(); 168 | const int wayland_x = dims.xpixels() + dims.padding_horizontal; 169 | const int wayland_y = dims.ypixels() + dims.padding_vertical; 170 | const int xcoord = cur_window.x + wayland_x; 171 | const int ycoord = cur_window.y + wayland_y; 172 | config->move_window(appid, xcoord, ycoord); 173 | } 174 | 175 | void WaylandShmWindow::generate_frame() 176 | { 177 | std::this_thread::sleep_for(std::chrono::milliseconds(image->frame_delay())); 178 | callback = wl_surface_frame(surface); 179 | wl_callback_add_listener(callback, &frame_listener, this_ptr); 180 | 181 | image->next_frame(); 182 | std::memcpy(shm->pool_data, image->data(), image->size()); 183 | wl_surface_attach(surface, shm->buffer, 0, 0); 184 | wl_surface_damage_buffer(surface, 0, 0, image->width(), image->height()); 185 | wl_surface_commit(surface); 186 | } 187 | -------------------------------------------------------------------------------- /src/canvas/wayland/window/waylandshm.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef WAYLAND_SHM_WINDOW_H 18 | #define WAYLAND_SHM_WINDOW_H 19 | 20 | #include "../wayland.hpp" 21 | #include "image.hpp" 22 | #include "shm.hpp" 23 | #include "wayland-xdg-shell-client-protocol.h" 24 | #include "waylandwindow.hpp" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | class WaylandShmWindow : public WaylandWindow 33 | { 34 | public: 35 | WaylandShmWindow(WaylandCanvas *canvas, std::unique_ptr new_image, struct XdgStructAgg *xdg_agg, 36 | WaylandConfig *config); 37 | ~WaylandShmWindow() override; 38 | static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial); 39 | static void wl_surface_frame_done(void *data, struct wl_callback *callback, uint32_t time); 40 | 41 | void draw() override {} 42 | void wl_draw(int32_t scale_factor) override; 43 | void generate_frame() override; 44 | void show() override; 45 | void hide() override; 46 | 47 | void finish_init() override; 48 | 49 | std::mutex draw_mutex; 50 | std::atomic visible{false}; 51 | std::unique_ptr shm; 52 | int32_t output_scale; 53 | 54 | private: 55 | WaylandConfig *config; 56 | 57 | struct xdg_wm_base *xdg_base = nullptr; 58 | struct wl_surface *surface = nullptr; 59 | struct xdg_surface *xdg_surface = nullptr; 60 | struct xdg_toplevel *xdg_toplevel = nullptr; 61 | struct wl_callback *callback; 62 | 63 | std::unique_ptr image; 64 | std::string appid; 65 | 66 | struct XdgStructAgg *xdg_agg; 67 | void *this_ptr; 68 | 69 | void move_window(); 70 | void xdg_setup(); 71 | 72 | void setup_listeners(); 73 | void delete_wayland_structs(); 74 | void delete_xdg_structs(); 75 | }; 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /src/canvas/wayland/window/waylandwindow.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef WAYLAND_WINDOW_H 18 | #define WAYLAND_WINDOW_H 19 | 20 | #include "window.hpp" 21 | 22 | #include 23 | #include 24 | 25 | class WaylandWindow: 26 | public Window, 27 | public std::enable_shared_from_this 28 | { 29 | public: 30 | ~WaylandWindow() override = default; 31 | 32 | virtual void wl_draw([[maybe_unused]] int32_t scale_factor) {}; 33 | virtual void finish_init() = 0; 34 | }; 35 | 36 | struct XdgStruct 37 | { 38 | std::weak_ptr ptr; 39 | }; 40 | 41 | struct XdgStructAgg 42 | { 43 | std::vector> ptrs; 44 | }; 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/canvas/x11/window/x11.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "x11.hpp" 18 | #include "dimensions.hpp" 19 | 20 | #include 21 | 22 | #include 23 | 24 | constexpr std::string_view win_name = "ueberzugpp"; 25 | 26 | X11Window::X11Window(xcb_connection_t *connection, xcb_screen_t *screen, xcb_window_t window, xcb_window_t parent, 27 | std::shared_ptr image) 28 | : connection(connection), 29 | screen(screen), 30 | window(window), 31 | parent(parent), 32 | gc(xcb_generate_id(connection)), 33 | image(std::move(image)) 34 | { 35 | logger = spdlog::get("X11"); 36 | create(); 37 | change_title(); 38 | } 39 | 40 | void X11Window::create() 41 | { 42 | const uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP; 43 | struct xcb_create_window_value_list_t value_list; 44 | value_list.background_pixel = screen->black_pixel; 45 | value_list.border_pixel = screen->black_pixel; 46 | value_list.event_mask = XCB_EVENT_MASK_EXPOSURE; 47 | value_list.colormap = screen->default_colormap; 48 | 49 | const auto dimensions = image->dimensions(); 50 | const auto xcoord = static_cast(dimensions.xpixels() + dimensions.padding_horizontal); 51 | const auto ycoord = static_cast(dimensions.ypixels() + dimensions.padding_vertical); 52 | xcb_create_window_aux(connection, screen->root_depth, window, this->parent, xcoord, ycoord, image->width(), 53 | image->height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, value_mask, 54 | &value_list); 55 | 56 | xcb_create_gc(connection, gc, window, 0, nullptr); 57 | logger->debug("Created child window {} at ({},{}) with parent {}", window, xcoord, ycoord, parent); 58 | } 59 | 60 | void X11Window::change_title() 61 | { 62 | const int bits_in_char = 8; 63 | xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, bits_in_char, 64 | win_name.size(), win_name.data()); 65 | } 66 | 67 | void X11Window::show() 68 | { 69 | if (visible) { 70 | return; 71 | } 72 | visible = true; 73 | xcb_map_window(connection, window); 74 | xcb_flush(connection); 75 | } 76 | 77 | void X11Window::hide() 78 | { 79 | if (!visible) { 80 | return; 81 | } 82 | visible = false; 83 | xcb_unmap_window(connection, window); 84 | xcb_flush(connection); 85 | } 86 | 87 | void X11Window::draw() 88 | { 89 | if (!xcb_image) { 90 | return; 91 | } 92 | xcb_image_put(connection, window, gc, xcb_image.get(), 0, 0, 0); 93 | } 94 | 95 | void X11Window::generate_frame() 96 | { 97 | xcb_image.reset(xcb_image_create_native(connection, image->width(), image->height(), XCB_IMAGE_FORMAT_Z_PIXMAP, 98 | screen->root_depth, nullptr, 0, nullptr)); 99 | xcb_image->data = const_cast(image->data()); 100 | send_expose_event(); 101 | } 102 | 103 | X11Window::~X11Window() 104 | { 105 | xcb_destroy_window(connection, window); 106 | xcb_free_gc(connection, gc); 107 | xcb_flush(connection); 108 | } 109 | 110 | void X11Window::send_expose_event() 111 | { 112 | const int event_size = 32; 113 | std::array buffer; 114 | auto *event = reinterpret_cast(buffer.data()); 115 | event->response_type = XCB_EXPOSE; 116 | event->window = window; 117 | xcb_send_event(connection, 0, window, XCB_EVENT_MASK_EXPOSURE, reinterpret_cast(event)); 118 | xcb_flush(connection); 119 | } 120 | -------------------------------------------------------------------------------- /src/canvas/x11/window/x11.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef X11_WINDOW_H 18 | #define X11_WINDOW_H 19 | 20 | #include "image.hpp" 21 | #include "util/ptr.hpp" 22 | #include "window.hpp" 23 | 24 | #include 25 | #include 26 | 27 | class Dimensions; 28 | 29 | class X11Window : public Window 30 | { 31 | public: 32 | X11Window(xcb_connection_t* connection, xcb_screen_t *screen, 33 | xcb_window_t window, xcb_window_t parent, std::shared_ptr image); 34 | ~X11Window() override; 35 | 36 | void draw() override; 37 | void generate_frame() override; 38 | void show() override; 39 | void hide() override; 40 | 41 | private: 42 | xcb_connection_t *connection; 43 | xcb_screen_t *screen; 44 | 45 | xcb_window_t window; 46 | xcb_window_t parent; 47 | xcb_gcontext_t gc; 48 | 49 | c_unique_ptr xcb_image; 50 | std::shared_ptr logger; 51 | std::shared_ptr image; 52 | 53 | bool visible = false; 54 | 55 | void send_expose_event(); 56 | void create(); 57 | void change_title(); 58 | }; 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /src/canvas/x11/window/x11egl.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "x11egl.hpp" 18 | #include "dimensions.hpp" 19 | #include "image.hpp" 20 | #include "util.hpp" 21 | 22 | #include 23 | 24 | X11EGLWindow::X11EGLWindow(xcb_connection_t *connection, xcb_screen_t *screen, xcb_window_t windowid, 25 | xcb_window_t parentid, const EGLUtil *egl, 26 | std::shared_ptr new_image) 27 | : connection(connection), 28 | screen(screen), 29 | windowid(windowid), 30 | parentid(parentid), 31 | image(std::move(new_image)), 32 | egl(egl) 33 | { 34 | logger = spdlog::get("x11"); 35 | create(); 36 | opengl_setup(); 37 | } 38 | 39 | X11EGLWindow::~X11EGLWindow() 40 | { 41 | egl->run_contained(egl_surface, egl_context, [this] { 42 | glDeleteTextures(1, &texture); 43 | glDeleteFramebuffers(1, &fbo); 44 | }); 45 | eglDestroySurface(egl->display, egl_surface); 46 | eglDestroyContext(egl->display, egl_context); 47 | 48 | xcb_destroy_window(connection, windowid); 49 | xcb_flush(connection); 50 | } 51 | 52 | void X11EGLWindow::create() 53 | { 54 | const uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP; 55 | struct xcb_create_window_value_list_t value_list; 56 | value_list.background_pixel = screen->black_pixel; 57 | value_list.border_pixel = screen->black_pixel; 58 | value_list.event_mask = XCB_EVENT_MASK_EXPOSURE; 59 | value_list.colormap = screen->default_colormap; 60 | 61 | const auto dimensions = image->dimensions(); 62 | const auto xcoord = static_cast(dimensions.xpixels() + dimensions.padding_horizontal); 63 | const auto ycoord = static_cast(dimensions.ypixels() + dimensions.padding_vertical); 64 | xcb_create_window_aux(connection, screen->root_depth, windowid, parentid, xcoord, ycoord, image->width(), 65 | image->height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, value_mask, 66 | &value_list); 67 | } 68 | 69 | void X11EGLWindow::opengl_setup() 70 | { 71 | egl_surface = egl->create_surface(&windowid); 72 | if (egl_surface == EGL_NO_SURFACE) { 73 | throw std::runtime_error(""); 74 | } 75 | 76 | egl_context = egl->create_context(egl_surface); 77 | if (egl_context == EGL_NO_CONTEXT) { 78 | throw std::runtime_error(""); 79 | } 80 | 81 | egl->run_contained(egl_surface, egl_context, [this] { 82 | glGenFramebuffers(1, &fbo); 83 | glGenTextures(1, &texture); 84 | }); 85 | } 86 | 87 | void X11EGLWindow::draw() 88 | { 89 | const std::scoped_lock lock{egl_mutex}; 90 | egl->run_contained(egl_surface, egl_context, [this] { 91 | glBlitFramebuffer(0, 0, image->width(), image->height(), 0, 0, image->width(), image->height(), 92 | GL_COLOR_BUFFER_BIT, GL_NEAREST); 93 | eglSwapBuffers(egl->display, egl_surface); 94 | }); 95 | } 96 | 97 | void X11EGLWindow::generate_frame() 98 | { 99 | const std::scoped_lock lock{egl_mutex}; 100 | egl->run_contained(egl_surface, egl_context, [this] { 101 | egl->get_texture_from_image(*image, texture); 102 | glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); 103 | glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0); 104 | }); 105 | 106 | send_expose_event(); 107 | } 108 | 109 | void X11EGLWindow::show() 110 | { 111 | if (visible) { 112 | return; 113 | } 114 | visible = true; 115 | xcb_map_window(connection, windowid); 116 | xcb_flush(connection); 117 | } 118 | 119 | void X11EGLWindow::hide() 120 | { 121 | if (!visible) { 122 | return; 123 | } 124 | visible = false; 125 | xcb_unmap_window(connection, windowid); 126 | xcb_flush(connection); 127 | } 128 | 129 | void X11EGLWindow::send_expose_event() 130 | { 131 | const int event_size = 32; 132 | std::array buffer; 133 | auto *event = reinterpret_cast(buffer.data()); 134 | event->response_type = XCB_EXPOSE; 135 | event->window = windowid; 136 | xcb_send_event(connection, 0, windowid, XCB_EVENT_MASK_EXPOSURE, reinterpret_cast(event)); 137 | xcb_flush(connection); 138 | } 139 | -------------------------------------------------------------------------------- /src/canvas/x11/window/x11egl.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef X11_EGL_WINDOW_H 18 | #define X11_EGL_WINDOW_H 19 | 20 | #include "window.hpp" 21 | #include "util/egl.hpp" 22 | 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | 29 | class Image; 30 | 31 | class X11EGLWindow : public Window 32 | { 33 | public: 34 | X11EGLWindow(xcb_connection_t* connection, xcb_screen_t* screen, 35 | xcb_window_t windowid, xcb_window_t parentid, const EGLUtil* egl, 36 | std::shared_ptr new_image); 37 | ~X11EGLWindow() override; 38 | 39 | void draw() override; 40 | void generate_frame() override; 41 | void show() override; 42 | void hide() override; 43 | 44 | private: 45 | xcb_connection_t* connection; 46 | xcb_screen_t* screen; 47 | xcb_window_t windowid; 48 | xcb_window_t parentid; 49 | std::shared_ptr image; 50 | const EGLUtil* egl; 51 | 52 | GLuint texture; 53 | GLuint fbo; 54 | EGLContext egl_context; 55 | EGLSurface egl_surface; 56 | 57 | std::mutex egl_mutex; 58 | std::shared_ptr logger; 59 | 60 | bool visible = false; 61 | 62 | void send_expose_event(); 63 | void create(); 64 | void change_title(); 65 | void opengl_setup(); 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/canvas/x11/x11.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef X11_CANVAS_H 18 | #define X11_CANVAS_H 19 | 20 | #include "canvas.hpp" 21 | #include "image.hpp" 22 | #include "window.hpp" 23 | #include "dimensions.hpp" 24 | #include "util/x11.hpp" 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #include 33 | #include 34 | 35 | #ifdef ENABLE_XCB_ERRORS 36 | # include 37 | #endif 38 | 39 | #ifdef ENABLE_OPENGL 40 | # include "util/egl.hpp" 41 | #endif 42 | 43 | class Flags; 44 | 45 | class X11Canvas : public Canvas 46 | { 47 | public: 48 | explicit X11Canvas(); 49 | ~X11Canvas() override; 50 | 51 | void add_image(const std::string& identifier, std::unique_ptr new_image) override; 52 | void remove_image(const std::string& identifier) override; 53 | void hide() override; 54 | void show() override; 55 | 56 | private: 57 | xcb_connection_t *connection; 58 | xcb_screen_t *screen; 59 | 60 | #ifdef ENABLE_XCB_ERRORS 61 | xcb_errors_context_t *err_ctx; 62 | #endif 63 | 64 | std::unique_ptr xutil; 65 | 66 | // map for event handler 67 | std::unordered_map> windows; 68 | 69 | // windows per image 70 | std::unordered_map>> image_windows; 72 | 73 | std::unordered_map> images; 74 | std::unordered_map draw_threads; 75 | 76 | std::thread event_handler; 77 | std::mutex windows_mutex; 78 | 79 | std::shared_ptr logger; 80 | std::shared_ptr flags; 81 | 82 | #ifdef ENABLE_OPENGL 83 | std::unique_ptr> egl; 84 | bool egl_available = true; 85 | #endif 86 | 87 | void draw(const std::string& identifier); 88 | void handle_events(); 89 | void get_tmux_window_ids(std::unordered_set& windows); 90 | void print_xcb_error(const xcb_generic_error_t* err); 91 | }; 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /src/dimensions.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "dimensions.hpp" 18 | #include "tmux.hpp" 19 | #include "terminal.hpp" 20 | 21 | #include 22 | 23 | Dimensions::Dimensions(const Terminal* terminal, uint16_t xcoord, 24 | uint16_t ycoord, int max_w, int max_h, std::string scaler): 25 | max_w(max_w), 26 | max_h(max_h), 27 | padding_horizontal(terminal->padding_horizontal), 28 | padding_vertical(terminal->padding_vertical), 29 | scaler(std::move(scaler)), 30 | terminal(terminal), 31 | orig_x(xcoord), 32 | orig_y(ycoord) 33 | { 34 | read_offsets(); 35 | } 36 | 37 | void Dimensions::read_offsets() 38 | { 39 | const auto [offset_x, offset_y] = tmux::get_offset(); 40 | x = orig_x + offset_x; 41 | y = orig_y + offset_y; 42 | } 43 | 44 | auto Dimensions::xpixels() const -> int 45 | { 46 | return x * terminal->font_width; 47 | } 48 | 49 | auto Dimensions::ypixels() const -> int 50 | { 51 | return y * terminal->font_height; 52 | } 53 | 54 | auto Dimensions::max_wpixels() const -> int 55 | { 56 | return max_w * terminal->font_width; 57 | } 58 | 59 | auto Dimensions::max_hpixels() const -> int 60 | { 61 | return max_h * terminal->font_height; 62 | } 63 | -------------------------------------------------------------------------------- /src/flags.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "flags.hpp" 18 | #include "os.hpp" 19 | #include "util.hpp" 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | namespace fs = std::filesystem; 26 | using json = nlohmann::json; 27 | 28 | // read configuration file 29 | Flags::Flags() 30 | { 31 | const auto home = os::getenv("HOME").value_or(util::temp_directory_path()); 32 | const auto config_home = os::getenv("XDG_CONFIG_HOME").value_or(fmt::format("{}/.config", home)); 33 | config_file = fmt::format("{}/ueberzugpp/config.json", config_home); 34 | if (fs::exists(config_file)) { 35 | read_config_file(); 36 | } 37 | } 38 | 39 | void Flags::read_config_file() 40 | { 41 | std::ifstream ifs(config_file); 42 | const auto data = json::parse(ifs); 43 | if (!data.contains("layer")) { 44 | return; 45 | } 46 | const auto &layer = data.at("layer"); 47 | silent = layer.value("silent", false); 48 | output = layer.value("output", ""); 49 | no_cache = layer.value("no-cache", false); 50 | no_opencv = layer.value("no-opencv", false); 51 | use_opengl = layer.value("opengl", false); 52 | } 53 | -------------------------------------------------------------------------------- /src/image.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "image.hpp" 18 | #ifdef ENABLE_OPENCV 19 | # include "image/opencv.hpp" 20 | #endif 21 | #include "dimensions.hpp" 22 | #include "flags.hpp" 23 | #include "image/libvips.hpp" 24 | #include "util.hpp" 25 | 26 | #ifdef ENABLE_OPENCV 27 | # include 28 | #endif 29 | #include 30 | #include 31 | 32 | namespace fs = std::filesystem; 33 | using njson = nlohmann::json; 34 | 35 | auto Image::load(const njson &command, const Terminal *terminal) -> std::unique_ptr 36 | { 37 | const fs::path &filename = command.at("path"); 38 | if (!fs::exists(filename)) { 39 | return nullptr; 40 | } 41 | const auto flags = Flags::instance(); 42 | const auto logger = spdlog::get("main"); 43 | std::shared_ptr dimensions; 44 | try { 45 | dimensions = get_dimensions(command, terminal); 46 | } catch (const std::exception &) { 47 | logger->error("Could not parse dimensions from command"); 48 | return nullptr; 49 | } 50 | std::string image_path = filename; 51 | bool in_cache = false; 52 | if (!flags->no_cache) { 53 | image_path = check_cache(*dimensions, filename); 54 | in_cache = image_path != filename; 55 | } 56 | 57 | #ifdef ENABLE_OPENCV 58 | if (cv::haveImageReader(image_path) && !flags->no_opencv) { 59 | try { 60 | return std::make_unique(dimensions, image_path, in_cache); 61 | } catch (const std::runtime_error &) { 62 | return nullptr; 63 | } 64 | } 65 | #endif 66 | const auto *vips_loader = vips_foreign_find_load(image_path.c_str()); 67 | if (vips_loader != nullptr) { 68 | try { 69 | return std::make_unique(dimensions, image_path, in_cache); 70 | } catch (const vips::VError &) { 71 | return nullptr; 72 | } 73 | } 74 | return nullptr; 75 | } 76 | 77 | auto Image::check_cache(const Dimensions &dimensions, const fs::path &orig_path) -> std::string 78 | { 79 | const fs::path cache_path = util::get_cache_file_save_location(orig_path); 80 | if (!fs::exists(cache_path)) { 81 | return orig_path; 82 | } 83 | 84 | vips::VImage cache_img; 85 | try { 86 | cache_img = vips::VImage::new_from_file(cache_path.c_str()); 87 | } catch (const vips::VError &) { 88 | return orig_path; 89 | } 90 | const uint32_t img_width = cache_img.width(); 91 | const uint32_t img_height = cache_img.height(); 92 | const uint32_t dim_width = dimensions.max_wpixels(); 93 | const uint32_t dim_height = dimensions.max_hpixels(); 94 | const int delta = 10; 95 | 96 | if ((dim_width >= img_width && dim_height >= img_height) && 97 | ((dim_width - img_width) <= delta || (dim_height - img_height) <= delta)) { 98 | return cache_path; 99 | } 100 | return orig_path; 101 | } 102 | 103 | auto Image::get_new_sizes(double max_width, double max_height, std::string_view scaler, int scale_factor) const 104 | -> std::pair 105 | { 106 | int img_width = width(); 107 | int img_height = height(); 108 | int new_width = img_width; 109 | int new_height = img_height; 110 | double new_scale = 0; 111 | double width_scale = 0; 112 | double height_scale = 0; 113 | double min_scale = 0; 114 | double max_scale = 0; 115 | 116 | if (scaler == "fit_contain" || scaler == "forced_cover") { 117 | // I believe these should work the same 118 | new_scale = max_height / img_height; 119 | if (img_width >= img_height) { 120 | new_scale = max_width / img_width; 121 | } 122 | new_width = static_cast(img_width * new_scale); 123 | new_height = static_cast(img_height * new_scale); 124 | new_scale = 1; 125 | } 126 | 127 | if (new_height > max_height) { 128 | if (new_width > max_width) { 129 | width_scale = max_width / new_width; 130 | height_scale = max_height / new_height; 131 | min_scale = std::min(width_scale, height_scale); 132 | max_scale = std::max(width_scale, height_scale); 133 | if (new_width * max_scale <= max_width && new_height * max_scale <= max_height) { 134 | new_scale = max_scale; 135 | } else { 136 | new_scale = min_scale; 137 | } 138 | } else { 139 | new_scale = max_height / new_height; 140 | } 141 | } else if (new_width > max_width) { 142 | new_scale = max_width / new_width; 143 | } 144 | if (new_scale != 1) { 145 | new_width = static_cast(new_width * new_scale); 146 | new_height = static_cast(new_height * new_scale); 147 | } 148 | 149 | return std::make_pair(util::round_up(new_width, scale_factor), util::round_up(new_height, scale_factor)); 150 | } 151 | 152 | auto Image::get_dimensions(const njson &json, const Terminal *terminal) -> std::shared_ptr 153 | { 154 | using std::string; 155 | int xcoord = 0; 156 | int ycoord = 0; 157 | int max_width = 0; 158 | int max_height = 0; 159 | string width_key = "max_width"; 160 | string height_key = "max_height"; 161 | const string scaler = json.value("scaler", "contain"); 162 | if (json.contains("width")) { 163 | width_key = "width"; 164 | height_key = "height"; 165 | } 166 | if (json.at(width_key).is_string()) { 167 | const string &width = json.at(width_key); 168 | const string &height = json.at(height_key); 169 | max_width = std::stoi(width); 170 | max_height = std::stoi(height); 171 | } else { 172 | max_width = json.at(width_key); 173 | max_height = json.at(height_key); 174 | } 175 | if (json.at("x").is_string()) { 176 | const string &xcoords = json.at("x"); 177 | const string &ycoords = json.at("y"); 178 | xcoord = std::stoi(xcoords); 179 | ycoord = std::stoi(ycoords); 180 | } else { 181 | xcoord = json.at("x"); 182 | ycoord = json.at("y"); 183 | } 184 | return std::make_shared(terminal, xcoord, ycoord, max_width, max_height, scaler); 185 | } 186 | -------------------------------------------------------------------------------- /src/image/libvips.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "libvips.hpp" 18 | #include "dimensions.hpp" 19 | #include "flags.hpp" 20 | #include "terminal.hpp" 21 | #include "util.hpp" 22 | 23 | #include 24 | #include 25 | 26 | #ifdef ENABLE_OPENCV 27 | # include 28 | #endif 29 | 30 | using vips::VError; 31 | using vips::VImage; 32 | 33 | LibvipsImage::LibvipsImage(std::shared_ptr new_dims, const std::string &filename, bool in_cache) 34 | : path(filename), 35 | dims(std::move(new_dims)), 36 | max_width(dims->max_wpixels()), 37 | max_height(dims->max_hpixels()), 38 | in_cache(in_cache) 39 | { 40 | image = VImage::new_from_file(path.c_str()).colourspace(VIPS_INTERPRETATION_sRGB); 41 | flags = Flags::instance(); 42 | logger = spdlog::get("vips"); 43 | logger->info("loading file {}", filename); 44 | 45 | try { 46 | // animated images should have both n-pages and delay 47 | npages = image.get_int("n-pages"); 48 | std::ignore = image.get_array_int("delay"); 49 | is_anim = true; 50 | logger->info("file is an animated image"); 51 | auto *opts = VImage::option()->set("n", -1); 52 | backup = VImage::new_from_file(filename.c_str(), opts).colourspace(VIPS_INTERPRETATION_sRGB); 53 | orig_height = backup.height() / npages; 54 | image = backup.crop(0, 0, backup.width(), orig_height); 55 | } catch (const VError &err) { 56 | logger->debug("Failed to process image animation"); 57 | } 58 | 59 | if (!is_anim) { 60 | image = image.autorot(); 61 | } 62 | process_image(); 63 | } 64 | 65 | auto LibvipsImage::dimensions() const -> const Dimensions & 66 | { 67 | return *dims; 68 | } 69 | 70 | auto LibvipsImage::filename() const -> std::string 71 | { 72 | return path.string(); 73 | } 74 | 75 | auto LibvipsImage::width() const -> int 76 | { 77 | return image.width(); 78 | } 79 | 80 | auto LibvipsImage::height() const -> int 81 | { 82 | return image.height(); 83 | } 84 | 85 | auto LibvipsImage::size() const -> size_t 86 | { 87 | return _size; 88 | } 89 | 90 | auto LibvipsImage::data() const -> const unsigned char * 91 | { 92 | return _data.get(); 93 | } 94 | 95 | auto LibvipsImage::channels() const -> int 96 | { 97 | return image.bands(); 98 | } 99 | 100 | auto LibvipsImage::is_animated() const -> bool 101 | { 102 | return is_anim; 103 | } 104 | 105 | auto LibvipsImage::next_frame() -> void 106 | { 107 | if (!is_anim) { 108 | return; 109 | } 110 | top += orig_height; 111 | if (top == backup.height()) { 112 | top = 0; 113 | } 114 | image = backup.crop(0, top, backup.width(), orig_height); 115 | process_image(); 116 | } 117 | 118 | auto LibvipsImage::frame_delay() const -> int 119 | { 120 | if (!is_anim) { 121 | return -1; 122 | } 123 | try { 124 | const auto delays = backup.get_array_int("delay"); 125 | const int ms_per_sec = 1000; 126 | if (delays.at(0) == 0) { 127 | #ifdef ENABLE_OPENCV 128 | const cv::VideoCapture video(path); 129 | if (video.isOpened()) { 130 | return static_cast((1.0 / video.get(cv::CAP_PROP_FPS)) * ms_per_sec); 131 | } 132 | #endif 133 | return static_cast((1.0 / npages) * ms_per_sec); 134 | } 135 | return delays.at(0); 136 | } catch (const VError &err) { 137 | return -1; 138 | } 139 | } 140 | 141 | auto LibvipsImage::resize_image() -> void 142 | { 143 | if (in_cache) { 144 | return; 145 | } 146 | const auto [new_width, new_height] = get_new_sizes(max_width, max_height, dims->scaler, flags->scale_factor); 147 | if (new_width <= 0 && new_height <= 0) { 148 | // ensure width and height are pair 149 | if (flags->needs_scaling) { 150 | const auto curw = width(); 151 | const auto curh = height(); 152 | if ((curw % 2) != 0 || (curh % 2) != 0) { 153 | auto *opts = VImage::option() 154 | ->set("height", util::round_up(curh, flags->scale_factor)) 155 | ->set("size", VIPS_SIZE_FORCE); 156 | image = image.thumbnail_image(util::round_up(curw, flags->scale_factor), opts); 157 | } 158 | } 159 | return; 160 | } 161 | 162 | logger->debug("Resizing image"); 163 | 164 | auto *opts = VImage::option()->set("height", new_height)->set("size", VIPS_SIZE_FORCE); 165 | image = image.thumbnail_image(new_width, opts); 166 | 167 | if (is_anim || flags->no_cache) { 168 | return; 169 | } 170 | 171 | const auto save_location = util::get_cache_file_save_location(path); 172 | try { 173 | image.write_to_file(save_location.c_str()); 174 | logger->debug("Saved resized image"); 175 | } catch (const VError &err) { 176 | logger->debug("Could not save resized image"); 177 | } 178 | } 179 | 180 | auto LibvipsImage::process_image() -> void 181 | { 182 | resize_image(); 183 | if (flags->origin_center) { 184 | const double img_width = static_cast(width()) / dims->terminal->font_width; 185 | const double img_height = static_cast(height()) / dims->terminal->font_height; 186 | dims->x -= std::floor(img_width / 2); 187 | dims->y -= std::floor(img_height / 2); 188 | } 189 | 190 | const std::unordered_set bgra_trifecta = {"x11", "chafa", "wayland"}; 191 | 192 | #ifdef ENABLE_OPENGL 193 | if (flags->use_opengl) { 194 | image = image.flipver(); 195 | } 196 | #endif 197 | 198 | if (bgra_trifecta.contains(flags->output)) { 199 | // alpha channel required 200 | if (!image.has_alpha()) { 201 | const int alpha_value = 255; 202 | image = image.bandjoin(alpha_value); 203 | } 204 | // convert from RGB to BGR 205 | auto bands = image.bandsplit(); 206 | std::swap(bands[0], bands[2]); 207 | image = VImage::bandjoin(bands); 208 | } else if (flags->output == "sixel") { 209 | // sixel expects RGB888 210 | if (image.has_alpha()) { 211 | image = image.flatten(); 212 | } 213 | } 214 | _size = VIPS_IMAGE_SIZEOF_IMAGE(image.get_image()); 215 | _data.reset(static_cast(image.write_to_memory(&_size))); 216 | } 217 | -------------------------------------------------------------------------------- /src/image/libvips.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef LIBVIPS_IMAGE_H 18 | #define LIBVIPS_IMAGE_H 19 | 20 | #include "image.hpp" 21 | #include "util/ptr.hpp" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | class LibvipsImage : public Image 29 | { 30 | public: 31 | LibvipsImage(std::shared_ptr new_dims, const std::string &filename, bool in_cache); 32 | 33 | [[nodiscard]] auto dimensions() const -> const Dimensions & override; 34 | [[nodiscard]] auto width() const -> int override; 35 | [[nodiscard]] auto height() const -> int override; 36 | [[nodiscard]] auto size() const -> size_t override; 37 | [[nodiscard]] auto data() const -> const unsigned char * override; 38 | [[nodiscard]] auto channels() const -> int override; 39 | 40 | void next_frame() override; 41 | [[nodiscard]] auto frame_delay() const -> int override; 42 | [[nodiscard]] auto is_animated() const -> bool override; 43 | [[nodiscard]] auto filename() const -> std::string override; 44 | 45 | private: 46 | vips::VImage image; 47 | vips::VImage backup; 48 | 49 | c_unique_ptr _data; 50 | std::filesystem::path path; 51 | std::shared_ptr dims; 52 | 53 | std::shared_ptr flags; 54 | std::shared_ptr logger; 55 | 56 | uint32_t max_width; 57 | uint32_t max_height; 58 | size_t _size = 0; 59 | 60 | // for animated pictures 61 | int top = 0; 62 | int orig_height; 63 | int npages = 0; 64 | bool is_anim = false; 65 | bool in_cache; 66 | 67 | void process_image(); 68 | void resize_image(); 69 | }; 70 | 71 | #endif 72 | -------------------------------------------------------------------------------- /src/image/opencv.hpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #ifndef OPENCV_IMAGE_H 18 | #define OPENCV_IMAGE_H 19 | 20 | #include "image.hpp" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace fs = std::filesystem; 28 | 29 | class OpencvImage : public Image 30 | { 31 | public: 32 | OpencvImage(std::shared_ptr new_dims, const std::string &filename, bool in_cache); 33 | ~OpencvImage() override = default; 34 | 35 | [[nodiscard]] auto dimensions() const -> const Dimensions & override; 36 | [[nodiscard]] auto width() const -> int override; 37 | [[nodiscard]] auto height() const -> int override; 38 | [[nodiscard]] auto size() const -> size_t override; 39 | [[nodiscard]] auto data() const -> const unsigned char * override; 40 | [[nodiscard]] auto channels() const -> int override; 41 | 42 | [[nodiscard]] auto filename() const -> std::string override; 43 | 44 | private: 45 | cv::Mat image; 46 | cv::UMat uimage; 47 | 48 | fs::path path; 49 | std::shared_ptr dims; 50 | 51 | uint64_t _size = 0; 52 | uint32_t max_width; 53 | uint32_t max_height; 54 | bool in_cache; 55 | bool opencl_available = false; 56 | 57 | std::shared_ptr logger; 58 | std::shared_ptr flags; 59 | 60 | void process_image(); 61 | void resize_image(); 62 | void resize_image_helper(cv::InputOutputArray &mat, int new_width, int new_height); 63 | 64 | void rotate_image(); 65 | void wayland_processing(); 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "application.hpp" 26 | #include "flags.hpp" 27 | #include "tmux.hpp" 28 | #include "util.hpp" 29 | 30 | void signal_handler(const int signal) 31 | { 32 | Application::stop_flag = true; 33 | 34 | const auto logger = spdlog::get("main"); 35 | if (!logger) { 36 | return; 37 | } 38 | switch (signal) { 39 | case SIGINT: 40 | logger->error("SIGINT received, exiting..."); 41 | break; 42 | case SIGTERM: 43 | logger->error("SIGTERM received, exiting..."); 44 | break; 45 | case SIGHUP: 46 | logger->error("SIGHUP received, exiting..."); 47 | break; 48 | default: 49 | logger->error("UNKNOWN({}) signal received, exiting...", signal); 50 | break; 51 | } 52 | } 53 | 54 | auto main(int argc, char *argv[]) -> int 55 | { 56 | // handle signals 57 | struct sigaction sga; 58 | sga.sa_handler = signal_handler; 59 | sigemptyset(&sga.sa_mask); 60 | sga.sa_flags = 0; 61 | sigaction(SIGINT, &sga, nullptr); 62 | sigaction(SIGTERM, &sga, nullptr); 63 | sigaction(SIGHUP, &sga, nullptr); 64 | sigaction(SIGCHLD, nullptr, nullptr); 65 | 66 | spdlog::cfg::load_env_levels(); 67 | 68 | std::shared_ptr flags; 69 | try { 70 | flags = Flags::instance(); 71 | } catch (const std::exception &e) { 72 | std::cerr << "Could not parse config file: " << e.what() << std::endl; 73 | return 1; 74 | } 75 | 76 | CLI::App program("Display images in the terminal", "ueberzug"); 77 | program.add_flag("-V,--version", flags->print_version, "Print version information."); 78 | 79 | auto *layer_command = program.add_subcommand("layer", "Display images on the terminal."); 80 | layer_command->add_flag("-s,--silent", flags->silent, "Print stderr to /dev/null."); 81 | layer_command 82 | ->add_flag("--use-escape-codes", flags->use_escape_codes, "Use escape codes to get terminal capabilities.") 83 | ->default_val(false); 84 | layer_command->add_option("--pid-file", flags->pid_file, "Output file where to write the daemon PID."); 85 | layer_command->add_flag("--no-stdin", flags->no_stdin, "Do not listen on stdin for commands.")->needs("--pid-file"); 86 | layer_command->add_flag("--no-cache", flags->no_cache, "Disable caching of resized images."); 87 | layer_command->add_flag("--no-opencv", flags->no_opencv, "Do not use OpenCV, use Libvips instead."); 88 | layer_command->add_option("-o,--output", flags->output, "Image output method") 89 | ->check(CLI::IsMember({"x11", "wayland", "sixel", "kitty", "iterm2", "chafa"})); 90 | layer_command->add_flag("--origin-center", flags->origin_center, "Location of the origin wrt the image"); 91 | layer_command->add_option("-p,--parser", nullptr, "**UNUSED**, only present for backwards compatibility."); 92 | layer_command->add_option("-l,--loader", nullptr, "**UNUSED**, only present for backwards compatibility."); 93 | 94 | auto *cmd_comand = program.add_subcommand("cmd", "Send a command to a running ueberzugpp instance."); 95 | cmd_comand->add_option("-s,--socket", flags->cmd_socket, "UNIX socket of running instance"); 96 | cmd_comand->add_option("-i,--identifier", flags->cmd_id, "Preview identifier"); 97 | cmd_comand->add_option("-a,--action", flags->cmd_action, "Action to send"); 98 | cmd_comand->add_option("-f,--file", flags->cmd_file_path, "Path of image file"); 99 | cmd_comand->add_option("-x,--xpos", flags->cmd_x, "X position of preview"); 100 | cmd_comand->add_option("-y,--ypos", flags->cmd_y, "Y position of preview"); 101 | cmd_comand->add_option("--max-width", flags->cmd_max_width, "Max width of preview"); 102 | cmd_comand->add_option("--max-height", flags->cmd_max_height, "Max height of preview"); 103 | 104 | auto *tmux_command = program.add_subcommand("tmux", "Handle tmux hooks. Used internaly."); 105 | tmux_command->allow_extras(); 106 | 107 | auto *query_win_command = 108 | program.add_subcommand("query_windows", "**UNUSED**, only present for backwards compatibility."); 109 | query_win_command->allow_extras(); 110 | 111 | CLI11_PARSE(program, argc, argv); 112 | 113 | if (query_win_command->parsed()) { 114 | return 0; 115 | } 116 | 117 | if (flags->print_version) { 118 | Application::print_version(); 119 | return 0; 120 | } 121 | 122 | if (!layer_command->parsed() && !tmux_command->parsed() && !cmd_comand->parsed()) { 123 | program.exit(CLI::CallForHelp()); 124 | return 1; 125 | } 126 | 127 | if (layer_command->parsed()) { 128 | Application application(argv[0]); 129 | application.command_loop(); 130 | } 131 | 132 | if (tmux_command->parsed()) { 133 | try { 134 | const auto positionals = tmux_command->remaining(); 135 | tmux::handle_hook(positionals.at(0), std::stoi(positionals.at(1))); 136 | } catch (const std::out_of_range &oor) { 137 | } 138 | } 139 | 140 | if (cmd_comand->parsed()) { 141 | util::send_command(*flags); 142 | } 143 | 144 | return 0; 145 | } 146 | -------------------------------------------------------------------------------- /src/os.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "os.hpp" 18 | #include "util/ptr.hpp" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | auto os::exec(const std::string &cmd) -> std::string 30 | { 31 | const int bufsize = 128; 32 | std::array buffer{}; 33 | std::string result; 34 | const c_unique_ptr pipe{popen(cmd.c_str(), "r")}; 35 | if (!pipe) { 36 | throw std::system_error(errno, std::generic_category()); 37 | } 38 | while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { 39 | result.append(buffer.data()); 40 | } 41 | if (!result.empty()) { 42 | result.erase(result.length() - 1); 43 | } 44 | return result; 45 | } 46 | 47 | auto os::read_data_from_fd(int filde, char sep) -> std::string 48 | { 49 | using std::errc; 50 | std::string response; 51 | char readch = 0; 52 | 53 | while (true) { 54 | const auto status = read(filde, &readch, 1); 55 | if (status == -1) { 56 | throw std::system_error(errno, std::system_category()); 57 | } 58 | if (status == 0 || readch == sep) { 59 | if (response.empty()) { 60 | throw std::system_error(EIO, std::system_category()); 61 | } 62 | break; 63 | } 64 | response.push_back(readch); 65 | } 66 | return response; 67 | } 68 | 69 | auto os::read_data_from_stdin(char sep) -> std::string 70 | { 71 | return read_data_from_fd(STDIN_FILENO, sep); 72 | } 73 | 74 | auto os::wait_for_data_on_fd(int filde, int waitms) -> bool 75 | { 76 | struct pollfd fds; 77 | fds.fd = filde; 78 | fds.events = POLLIN; 79 | 80 | poll(&fds, 1, waitms); 81 | 82 | if ((fds.revents & (POLLERR | POLLNVAL | POLLHUP)) != 0) { 83 | throw std::system_error(EIO, std::generic_category()); 84 | } 85 | 86 | return (fds.revents & POLLIN) != 0; 87 | } 88 | 89 | auto os::wait_for_data_on_stdin(int waitms) -> bool 90 | { 91 | return wait_for_data_on_fd(STDIN_FILENO, waitms); 92 | } 93 | 94 | auto os::getenv(const std::string &var) -> std::optional 95 | { 96 | const char *env_p = std::getenv(var.c_str()); // NOLINT 97 | if (env_p == nullptr) { 98 | return {}; 99 | } 100 | return env_p; 101 | } 102 | 103 | auto os::get_pid() -> int 104 | { 105 | return getpid(); 106 | } 107 | 108 | auto os::get_ppid() -> int 109 | { 110 | return getppid(); 111 | } 112 | 113 | void os::daemonize() 114 | { 115 | const int pid = fork(); 116 | if (pid < 0) { 117 | std::exit(EXIT_FAILURE); // NOLINT 118 | } 119 | if (pid > 0) { 120 | std::exit(EXIT_SUCCESS); // NOLINT 121 | } 122 | const int status = setsid(); 123 | if (status < 0) { 124 | std::exit(EXIT_FAILURE); // NOLINT 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/process/apple.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "os.hpp" 18 | #include "process.hpp" 19 | #include "tmux.hpp" 20 | #include "util.hpp" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | Process::Process(int pid) 27 | : pid(pid) 28 | { 29 | struct proc_bsdshortinfo sproc; 30 | struct proc_bsdinfo proc; 31 | 32 | int status = proc_pidinfo(pid, PROC_PIDT_SHORTBSDINFO, 0, &sproc, PROC_PIDT_SHORTBSDINFO_SIZE); 33 | if (status == PROC_PIDT_SHORTBSDINFO_SIZE) { 34 | ppid = static_cast(sproc.pbsi_ppid); 35 | } 36 | 37 | status = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &proc, PROC_PIDTBSDINFO_SIZE); 38 | if (status == PROC_PIDTBSDINFO_SIZE) { 39 | tty_nr = static_cast(proc.e_tdev); 40 | minor_dev = minor(tty_nr); 41 | pty_path = fmt::format("/dev/ttys{:0>3}", minor_dev); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/process/linux.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "process.hpp" 18 | 19 | #include 20 | #include 21 | #include 22 | #if defined(__FreeBSD__) || defined(__NetBSD__) 23 | # include 24 | #else 25 | # include 26 | #endif 27 | 28 | constexpr auto max_size = std::numeric_limits::max(); 29 | 30 | Process::Process(int pid) 31 | : pid(pid) 32 | { 33 | const auto stat = fmt::format("/proc/{}/stat", pid); 34 | std::ifstream ifs(stat); 35 | ifs.ignore(max_size, ')'); // skip pid and executable name 36 | ifs >> state >> ppid >> process_group_id >> session_id >> tty_nr; 37 | minor_dev = minor(tty_nr); // NOLINT 38 | pty_path = fmt::format("/dev/pts/{}", minor_dev); 39 | } 40 | -------------------------------------------------------------------------------- /src/tmux.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "tmux.hpp" 18 | #include "os.hpp" 19 | #include "util.hpp" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | constexpr auto session_hooks = std::to_array( 27 | {"session-window-changed", "client-detached", "window-layout-changed"}); 28 | 29 | constexpr auto global_hooks = std::to_array( 30 | {"client-session-changed"}); 31 | 32 | auto tmux::get_session_id() -> std::string 33 | { 34 | const auto cmd = fmt::format("tmux display -p -F '#{{session_id}}' -t {}", tmux::get_pane()); 35 | return os::exec(cmd); 36 | } 37 | 38 | auto tmux::is_used() -> bool 39 | { 40 | return !tmux::get_pane().empty(); 41 | } 42 | 43 | auto tmux::is_window_focused() -> bool 44 | { 45 | const auto cmd = fmt::format("tmux display -p -F '#{{session_attached}},#{{window_active}},#{{pane_in_mode}}' -t {}", tmux::get_pane()); 46 | return os::exec(cmd) == "1,1,0"; 47 | } 48 | 49 | auto tmux::get_pane() -> std::string 50 | { 51 | return os::getenv("TMUX_PANE").value_or(""); 52 | } 53 | 54 | auto tmux::get_client_pids() -> std::optional> 55 | { 56 | if (!tmux::is_used()) { 57 | return {}; 58 | } 59 | if (!tmux::is_window_focused()) { 60 | return {}; 61 | } 62 | 63 | std::vector pids; 64 | const auto cmd = fmt::format("tmux list-clients -F '#{{client_pid}}' -t {}", tmux::get_pane()); 65 | const auto output = os::exec(cmd); 66 | 67 | for (const auto &line : util::str_split(output, "\n")) { 68 | pids.push_back(std::stoi(line)); 69 | } 70 | 71 | return pids; 72 | } 73 | 74 | auto tmux::get_offset() -> std::pair 75 | { 76 | if (!tmux::is_used()) { 77 | return std::make_pair(0, 0); 78 | } 79 | const auto [p_x, p_y] = tmux::get_pane_offset(); 80 | const auto s_y = tmux::get_statusbar_offset(); 81 | return std::make_pair(p_x, p_y + s_y); 82 | } 83 | 84 | auto tmux::get_pane_offset() -> std::pair 85 | { 86 | const auto cmd = fmt::format(R"(tmux display -p -F '#{{pane_top}},#{{pane_left}}, 87 | #{{pane_bottom}},#{{pane_right}}, 88 | #{{window_height}},#{{window_width}}' -t {})", 89 | tmux::get_pane()); 90 | const auto output = util::str_split(os::exec(cmd), ","); 91 | return std::make_pair(std::stoi(output.at(1)), std::stoi(output.at(0))); 92 | } 93 | 94 | auto tmux::get_statusbar_offset() -> int 95 | { 96 | const std::string cmd = "tmux display -p '#{status},#{status-position}'"; 97 | const auto output = util::str_split(os::exec(cmd), ","); 98 | if (output.at(1) != "top" || output.at(0) == "off") { 99 | return 0; 100 | } 101 | if (output.at(0) == "on") { 102 | return 1; 103 | } 104 | return std::stoi(output.at(0)); 105 | } 106 | 107 | void tmux::handle_hook(const std::string_view hook, int pid) 108 | { 109 | const auto msg = fmt::format(R"({{"action":"tmux","hook":"{}"}})", hook); 110 | const auto endpoint = util::get_socket_path(pid); 111 | util::send_socket_message(msg, endpoint); 112 | } 113 | 114 | void tmux::register_hooks() 115 | { 116 | if (!tmux::is_used()) { 117 | return; 118 | } 119 | for (const auto &hook : session_hooks) { 120 | std::string cmd = fmt::format(R"(tmux set-hook -t {0} {1} "run-shell 'ueberzugpp tmux {1} {2}'")", tmux::get_pane(), hook, os::get_pid()); 121 | os::exec(cmd); 122 | } 123 | for (const auto &hook : global_hooks) { 124 | std::string cmd = fmt::format(R"(tmux set-hook -g {0} "run-shell 'ueberzugpp tmux {0} {1}'")", hook, os::get_pid()); 125 | os::exec(cmd); 126 | } 127 | } 128 | 129 | void tmux::unregister_hooks() 130 | { 131 | if (!tmux::is_used()) { 132 | return; 133 | } 134 | for (const auto &hook : session_hooks) { 135 | const auto cmd = fmt::format("tmux set-hook -u -t {} {}", tmux::get_pane(), hook); 136 | os::exec(cmd); 137 | } 138 | for (const auto &hook : global_hooks) { 139 | const auto cmd = fmt::format("tmux set-hook -gu {}", hook); 140 | os::exec(cmd); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/util/dbus.cpp: -------------------------------------------------------------------------------- 1 | #include "util/dbus.hpp" 2 | #include 3 | 4 | DbusUtil::DbusUtil(const std::string &address) 5 | : connection(dbus_connection_open(address.c_str(), nullptr)) 6 | { 7 | if (connection == nullptr) { 8 | throw std::invalid_argument("Could not connect to dbus address"); 9 | } 10 | } 11 | 12 | DbusUtil::~DbusUtil() 13 | { 14 | dbus_connection_unref(connection); 15 | } 16 | -------------------------------------------------------------------------------- /src/util/socket.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "util/socket.hpp" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "os.hpp" 30 | #include "util.hpp" 31 | 32 | namespace fs = std::filesystem; 33 | 34 | UnixSocket::UnixSocket(const std::string_view endpoint) 35 | : fd(socket(AF_UNIX, SOCK_STREAM, 0)) 36 | { 37 | if (fd == -1) { 38 | throw std::system_error(errno, std::generic_category()); 39 | } 40 | connect_to_endpoint(endpoint); 41 | } 42 | 43 | UnixSocket::UnixSocket() 44 | : fd(socket(AF_UNIX, SOCK_STREAM, 0)) 45 | { 46 | if (fd == -1) { 47 | throw std::system_error(errno, std::generic_category()); 48 | } 49 | const int bufsize = 8192; 50 | buffer.reserve(bufsize); 51 | } 52 | 53 | void UnixSocket::connect_to_endpoint(const std::string_view endpoint) 54 | { 55 | if (!fs::exists(endpoint)) { 56 | connected = false; 57 | return; 58 | } 59 | struct sockaddr_un sock; 60 | std::memset(&sock, 0, sizeof(sockaddr_un)); 61 | sock.sun_family = AF_UNIX; 62 | endpoint.copy(sock.sun_path, endpoint.size()); 63 | 64 | int res = connect(fd, reinterpret_cast(&sock), sizeof(struct sockaddr_un)); 65 | if (res == -1) { 66 | throw std::system_error(errno, std::generic_category()); 67 | } 68 | } 69 | 70 | void UnixSocket::bind_to_endpoint(const std::string_view endpoint) const 71 | { 72 | struct sockaddr_un sock; 73 | std::memset(&sock, 0, sizeof(sockaddr_un)); 74 | sock.sun_family = AF_UNIX; 75 | endpoint.copy(sock.sun_path, endpoint.size()); 76 | 77 | int res = bind(fd, reinterpret_cast(&sock), sizeof(struct sockaddr_un)); 78 | if (res == -1) { 79 | throw std::system_error(errno, std::generic_category()); 80 | } 81 | res = listen(fd, SOMAXCONN); 82 | if (res == -1) { 83 | throw std::system_error(errno, std::generic_category()); 84 | } 85 | } 86 | 87 | auto UnixSocket::wait_for_connections(int waitms) const -> int 88 | { 89 | const auto in_event = os::wait_for_data_on_fd(fd, waitms); 90 | if (in_event) { 91 | return accept(fd, nullptr, nullptr); 92 | } 93 | return -1; 94 | } 95 | 96 | auto UnixSocket::read_data_from_connection(int filde) -> std::vector 97 | { 98 | // a single connection could send multiples commands at once 99 | // each command should end with a '\n' character 100 | const int read_buffer_size = 4096; 101 | std::array read_buffer; 102 | while (true) { 103 | const auto status = recv(filde, read_buffer.data(), read_buffer_size, 0); 104 | if (status <= 0) { 105 | break; 106 | } 107 | buffer.append(read_buffer.data(), status); 108 | } 109 | auto cmds = util::str_split(buffer, "\n"); 110 | buffer.clear(); 111 | close(filde); 112 | return cmds; 113 | } 114 | 115 | void UnixSocket::write(const void *data, std::size_t len) const 116 | { 117 | if (!connected) { 118 | return; 119 | } 120 | const auto *runner = static_cast(data); 121 | while (len != 0) { 122 | const auto status = send(fd, runner, len, MSG_NOSIGNAL); 123 | if (status == -1) { 124 | throw std::system_error(errno, std::generic_category()); 125 | } 126 | len -= status; 127 | runner += status; 128 | } 129 | } 130 | 131 | void UnixSocket::read(void *data, std::size_t len) const 132 | { 133 | if (!connected) { 134 | return; 135 | } 136 | auto *runner = static_cast(data); 137 | while (len != 0) { 138 | const auto status = recv(fd, runner, len, 0); 139 | if (status == 0) { 140 | return; // no data 141 | } 142 | if (status == -1) { 143 | throw std::system_error(errno, std::generic_category()); 144 | } 145 | len -= status; 146 | runner += status; 147 | } 148 | } 149 | 150 | auto UnixSocket::read_until_empty() const -> std::string 151 | { 152 | std::string result; 153 | const int read_buffer_size = 4096; 154 | std::array read_buffer; 155 | result.reserve(read_buffer_size); 156 | while (true) { 157 | const auto status = recv(fd, read_buffer.data(), read_buffer_size, 0); 158 | if (status <= 0) { 159 | break; 160 | } 161 | result.append(read_buffer.data(), status); 162 | } 163 | return result; 164 | } 165 | 166 | UnixSocket::~UnixSocket() 167 | { 168 | close(fd); 169 | } 170 | -------------------------------------------------------------------------------- /src/util/x11.cpp: -------------------------------------------------------------------------------- 1 | // Display images inside a terminal 2 | // Copyright (C) 2023 JustKidding 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // This program is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU General Public License 15 | // along with this program. If not, see . 16 | 17 | #include "util/x11.hpp" 18 | #include "flags.hpp" 19 | #include "os.hpp" 20 | #include "util.hpp" 21 | #include "util/ptr.hpp" 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | X11Util::X11Util() 33 | : connection(xcb_connect(nullptr, nullptr)) 34 | { 35 | const auto flags = Flags::instance(); 36 | const auto xdg_session = os::getenv("XDG_SESSION_TYPE").value_or(""); 37 | if (xcb_connection_has_error(connection) == 0) { 38 | screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data; 39 | if (xdg_session != "wayland" || flags->output == "x11") { 40 | connected = true; 41 | } 42 | } 43 | } 44 | 45 | X11Util::X11Util(xcb_connection_t *connection) 46 | : connection(connection), 47 | screen(xcb_setup_roots_iterator(xcb_get_setup(connection)).data), 48 | owns_connection(false) 49 | { 50 | } 51 | 52 | X11Util::~X11Util() 53 | { 54 | if (owns_connection) { 55 | xcb_disconnect(connection); 56 | } 57 | } 58 | 59 | auto X11Util::get_server_window_ids() const -> std::vector 60 | { 61 | const int num_clients = 256; 62 | std::vector windows; 63 | std::stack cookies_st; 64 | windows.reserve(num_clients); 65 | 66 | cookies_st.push(xcb_query_tree_unchecked(connection, screen->root)); 67 | 68 | while (!cookies_st.empty()) { 69 | const auto cookie = cookies_st.top(); 70 | cookies_st.pop(); 71 | 72 | const auto reply = unique_C_ptr{xcb_query_tree_reply(connection, cookie, nullptr)}; 73 | if (!reply) { 74 | continue; 75 | } 76 | 77 | const auto num_children = xcb_query_tree_children_length(reply.get()); 78 | if (num_children == 0) { 79 | continue; 80 | } 81 | const auto *children = xcb_query_tree_children(reply.get()); 82 | for (int i = 0; i < num_children; ++i) { 83 | const auto child = children[i]; 84 | const bool is_complete_window = window_has_properties(child, {XCB_ATOM_WM_CLASS, XCB_ATOM_WM_NAME}); 85 | if (is_complete_window) { 86 | windows.push_back(child); 87 | } 88 | cookies_st.push(xcb_query_tree_unchecked(connection, child)); 89 | } 90 | } 91 | return windows; 92 | } 93 | 94 | auto X11Util::get_pid_window_map() const -> std::unordered_map 95 | { 96 | const auto windows = get_server_window_ids(); 97 | std::unordered_map res; 98 | std::vector cookies; 99 | res.reserve(windows.size()); 100 | cookies.reserve(windows.size()); 101 | 102 | struct xcb_res_client_id_spec_t spec; 103 | spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID; 104 | 105 | // bulk request pids 106 | for (const auto window : windows) { 107 | spec.client = window; 108 | cookies.push_back(xcb_res_query_client_ids_unchecked(connection, 1, &spec)); 109 | } 110 | 111 | // process replies 112 | auto win_iter = windows.cbegin(); 113 | for (const auto cookie : cookies) { 114 | const auto reply = 115 | unique_C_ptr{xcb_res_query_client_ids_reply(connection, cookie, nullptr)}; 116 | if (!reply) { 117 | continue; 118 | } 119 | const auto iter = xcb_res_query_client_ids_ids_iterator(reply.get()); 120 | const auto pid = *xcb_res_client_id_value_value(iter.data); 121 | res.insert_or_assign(pid, *win_iter); 122 | std::advance(win_iter, 1); 123 | } 124 | return res; 125 | } 126 | 127 | auto X11Util::window_has_properties(xcb_window_t window, std::initializer_list properties) const -> bool 128 | { 129 | std::vector cookies; 130 | cookies.reserve(properties.size()); 131 | for (const auto prop : properties) { 132 | cookies.push_back(xcb_get_property_unchecked(connection, 0, window, prop, XCB_ATOM_ANY, 0, 4)); 133 | } 134 | return ranges::any_of(cookies, [this](xcb_get_property_cookie_t cookie) -> bool { 135 | const auto reply = unique_C_ptr{xcb_get_property_reply(connection, cookie, nullptr)}; 136 | return reply && xcb_get_property_value_length(reply.get()) != 0; 137 | }); 138 | } 139 | 140 | auto X11Util::get_window_dimensions(xcb_window_t window) const -> std::pair 141 | { 142 | const auto cookie = xcb_get_geometry_unchecked(connection, window); 143 | const auto reply = unique_C_ptr{xcb_get_geometry_reply(connection, cookie, nullptr)}; 144 | if (!reply) { 145 | return std::make_pair(0, 0); 146 | } 147 | return std::make_pair(reply->width, reply->height); 148 | } 149 | 150 | auto X11Util::get_parent_window(int pid) const -> xcb_window_t 151 | { 152 | const auto wid = os::getenv("WINDOWID"); 153 | if (wid.has_value()) { 154 | try { 155 | return std::stoi(wid.value()); 156 | } catch (const std::out_of_range &oor) { 157 | return 0; 158 | } 159 | } 160 | 161 | const auto pid_window_map = get_pid_window_map(); 162 | const auto tree = util::get_process_tree(pid); 163 | for (const auto spid : tree) { 164 | const auto win = pid_window_map.find(spid); 165 | if (win != pid_window_map.end()) { 166 | return win->second; 167 | } 168 | } 169 | 170 | return 0; 171 | } 172 | --------------------------------------------------------------------------------