init
This commit is contained in:
280
CMakeLists.txt
Normal file
280
CMakeLists.txt
Normal file
@ -0,0 +1,280 @@
|
|||||||
|
# Display images inside a terminal Copyright (C) 2023 JustKidding
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under
|
||||||
|
# the terms of the GNU General Public License as published by the Free Software
|
||||||
|
# Foundation, either version 3 of the License, or (at your option) any later
|
||||||
|
# version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||||
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License along with
|
||||||
|
# this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.21...3.28 FATAL_ERROR)
|
||||||
|
|
||||||
|
set(UEBERZUGPP_VERSION 2.9.8)
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
set(CMAKE_BUILD_TYPE
|
||||||
|
Debug
|
||||||
|
CACHE STRING "Build type.")
|
||||||
|
|
||||||
|
project(
|
||||||
|
ueberzugpp
|
||||||
|
LANGUAGES CXX C
|
||||||
|
VERSION ${UEBERZUGPP_VERSION})
|
||||||
|
add_executable(ueberzug)
|
||||||
|
|
||||||
|
option(ENABLE_X11 "Enable X11 canvas." ON)
|
||||||
|
option(ENABLE_XCB_ERRORS "Enable useful logging of XCB errors." OFF)
|
||||||
|
option(ENABLE_WAYLAND "Enable wayland canvas" OFF)
|
||||||
|
option(ENABLE_DBUS "Enable dbus support" OFF)
|
||||||
|
option(ENABLE_OPENCV "Enable OpenCV image processing." ON)
|
||||||
|
option(ENABLE_TURBOBASE64 "Enable Turbo-Base64 for base64 encoding." OFF)
|
||||||
|
option(ENABLE_OPENGL "Enable canvas rendering with OpenGL." OFF)
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
include(CheckCXXSymbolExists)
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
find_package(TBB REQUIRED)
|
||||||
|
|
||||||
|
# check if <execution> is available
|
||||||
|
set(CMAKE_REQUIRED_LIBRARIES TBB::tbb)
|
||||||
|
check_cxx_symbol_exists(std::execution::par_unseq execution
|
||||||
|
HAVE_STD_EXECUTION_H)
|
||||||
|
if(HAVE_STD_EXECUTION_H)
|
||||||
|
target_compile_definitions(ueberzug PRIVATE HAVE_STD_EXECUTION_H)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(CLI11 QUIET)
|
||||||
|
if(NOT CLI11_FOUND)
|
||||||
|
if(FETCHCONTENT_FULLY_DISCONNECTED)
|
||||||
|
add_subdirectory("${CMAKE_SOURCE_DIR}/third_party/CLI11")
|
||||||
|
else()
|
||||||
|
FetchContent_Declare(
|
||||||
|
cli11
|
||||||
|
URL https://github.com/CLIUtils/CLI11/archive/refs/tags/v2.4.2.tar.gz)
|
||||||
|
list(APPEND FC_LIBS cli11)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(nlohmann_json QUIET)
|
||||||
|
if(NOT nlohmann_json_FOUND)
|
||||||
|
FetchContent_Declare(
|
||||||
|
nlohmann_json
|
||||||
|
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
|
||||||
|
list(APPEND FC_LIBS nlohmann_json)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(fmt QUIET)
|
||||||
|
if(NOT fmt_FOUND)
|
||||||
|
FetchContent_Declare(
|
||||||
|
fmt URL https://github.com/fmtlib/fmt/archive/refs/tags/10.2.1.tar.gz)
|
||||||
|
list(APPEND FC_LIBS fmt)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(spdlog QUIET)
|
||||||
|
if(NOT spdlog_FOUND)
|
||||||
|
set(SPDLOG_FMT_EXTERNAL ON)
|
||||||
|
FetchContent_Declare(
|
||||||
|
spdlog
|
||||||
|
URL https://github.com/gabime/spdlog/archive/refs/tags/v1.14.1.tar.gz)
|
||||||
|
list(APPEND FC_LIBS spdlog)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(FC_LIBS)
|
||||||
|
FetchContent_MakeAvailable(${FC_LIBS})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(range-v3 QUIET)
|
||||||
|
if(NOT range-v3_FOUND)
|
||||||
|
FetchContent_Declare(
|
||||||
|
range-v3
|
||||||
|
URL https://github.com/ericniebler/range-v3/archive/refs/tags/0.12.0.tar.gz)
|
||||||
|
FetchContent_Populate(range-v3)
|
||||||
|
add_subdirectory(${range-v3_SOURCE_DIR} ${range-v3_BINARY_DIR}
|
||||||
|
EXCLUDE_FROM_ALL)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_OPENGL)
|
||||||
|
target_compile_definitions(ueberzug PRIVATE ENABLE_OPENGL)
|
||||||
|
find_package(OpenGL REQUIRED)
|
||||||
|
list(APPEND UEBERZUG_SOURCES "src/util/egl.cpp")
|
||||||
|
list(APPEND UEBERZUG_LIBRARIES OpenGL::OpenGL OpenGL::EGL)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_X11)
|
||||||
|
target_compile_definitions(ueberzug PRIVATE ENABLE_X11)
|
||||||
|
pkg_check_modules(XCB REQUIRED IMPORTED_TARGET xcb)
|
||||||
|
pkg_check_modules(XCBIMAGE REQUIRED IMPORTED_TARGET xcb-image)
|
||||||
|
pkg_check_modules(XCBRES REQUIRED IMPORTED_TARGET xcb-res)
|
||||||
|
list(APPEND UEBERZUG_SOURCES "src/util/x11.cpp" "src/canvas/x11/x11.cpp"
|
||||||
|
"src/canvas/x11/window/x11.cpp")
|
||||||
|
list(APPEND UEBERZUG_LIBRARIES PkgConfig::XCB PkgConfig::XCBIMAGE
|
||||||
|
PkgConfig::XCBRES)
|
||||||
|
|
||||||
|
if(ENABLE_OPENGL)
|
||||||
|
list(APPEND UEBERZUG_SOURCES "src/canvas/x11/window/x11egl.cpp")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_XCB_ERRORS)
|
||||||
|
target_compile_definitions(ueberzug PRIVATE ENABLE_XCB_ERRORS)
|
||||||
|
pkg_check_modules(XCBERRORS REQUIRED IMPORTED_TARGET xcb-errors)
|
||||||
|
list(APPEND UEBERZUG_LIBRARIES PkgConfig::XCBERRORS)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_WAYLAND)
|
||||||
|
target_compile_definitions(ueberzug PRIVATE ENABLE_WAYLAND)
|
||||||
|
find_package(ECM REQUIRED NO_MODULE)
|
||||||
|
list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
|
||||||
|
|
||||||
|
find_library(LIBRT rt REQUIRED)
|
||||||
|
if(ENABLE_OPENGL)
|
||||||
|
find_package(Wayland REQUIRED COMPONENTS Client Egl)
|
||||||
|
list(APPEND UEBERZUG_LIBRARIES Wayland::Egl)
|
||||||
|
list(APPEND UEBERZUG_SOURCES "src/canvas/wayland/window/waylandegl.cpp")
|
||||||
|
else()
|
||||||
|
find_package(Wayland REQUIRED COMPONENTS Client)
|
||||||
|
endif()
|
||||||
|
find_package(WaylandProtocols REQUIRED)
|
||||||
|
find_package(WaylandScanner REQUIRED)
|
||||||
|
|
||||||
|
ecm_add_wayland_client_protocol(
|
||||||
|
UEBERZUG_SOURCES PROTOCOL
|
||||||
|
"${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml" BASENAME
|
||||||
|
"xdg-shell")
|
||||||
|
|
||||||
|
list(
|
||||||
|
APPEND
|
||||||
|
UEBERZUG_SOURCES
|
||||||
|
"src/canvas/wayland/wayland.cpp"
|
||||||
|
"src/canvas/wayland/config.cpp"
|
||||||
|
"src/canvas/wayland/window/shm.cpp"
|
||||||
|
"src/canvas/wayland/window/waylandshm.cpp"
|
||||||
|
"src/canvas/wayland/config/sway.cpp"
|
||||||
|
"src/canvas/wayland/config/hyprland.cpp"
|
||||||
|
"src/canvas/wayland/config/wayfire.cpp"
|
||||||
|
"src/canvas/wayland/config/dummy.cpp")
|
||||||
|
|
||||||
|
list(APPEND UEBERZUG_LIBRARIES Wayland::Client ${LIBRT})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_OPENCV)
|
||||||
|
target_compile_definitions(ueberzug PRIVATE ENABLE_OPENCV)
|
||||||
|
find_package(OpenCV REQUIRED COMPONENTS core imgproc imgcodecs videoio)
|
||||||
|
|
||||||
|
list(APPEND UEBERZUG_SOURCES "src/image/opencv.cpp")
|
||||||
|
list(APPEND UEBERZUG_LIBRARIES opencv_core opencv_imgproc opencv_imgcodecs
|
||||||
|
opencv_videoio)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_TURBOBASE64)
|
||||||
|
target_compile_definitions(ueberzug PRIVATE ENABLE_TURBOBASE64)
|
||||||
|
find_package(turbobase64 QUIET)
|
||||||
|
if(NOT turbobase64_FOUND)
|
||||||
|
FetchContent_Declare(
|
||||||
|
turbobase64
|
||||||
|
URL https://github.com/powturbo/Turbo-Base64/archive/refs/tags/2023.08.tar.gz
|
||||||
|
)
|
||||||
|
FetchContent_Populate(turbobase64)
|
||||||
|
add_subdirectory(${turbobase64_SOURCE_DIR} ${turbobase64_BINARY_DIR}
|
||||||
|
EXCLUDE_FROM_ALL)
|
||||||
|
list(APPEND UEBERZUG_LIBRARIES base64)
|
||||||
|
else()
|
||||||
|
target_compile_definitions(ueberzug PRIVATE WITH_SYSTEM_TURBOBASE64)
|
||||||
|
list(APPEND UEBERZUG_LIBRARIES turbo::base64)
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(ENABLE_DBUS)
|
||||||
|
pkg_check_modules(DBUS REQUIRED IMPORTED_TARGET dbus-1)
|
||||||
|
list(APPEND UEBERZUG_LIBRARIES PkgConfig::DBUS)
|
||||||
|
list(APPEND UEBERZUG_SOURCES "src/util/dbus.cpp")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(PROJECT_WARNINGS_CXX -Wall -Wextra -Wpedantic -Werror)
|
||||||
|
|
||||||
|
target_compile_options(
|
||||||
|
ueberzug PRIVATE $<$<CONFIG:Debug>:
|
||||||
|
$<$<COMPILE_LANGUAGE:CXX>:${PROJECT_WARNINGS_CXX}> >)
|
||||||
|
|
||||||
|
target_compile_definitions(ueberzug PRIVATE $<$<CONFIG:Debug>: DEBUG >)
|
||||||
|
|
||||||
|
pkg_check_modules(VIPS REQUIRED IMPORTED_TARGET vips-cpp)
|
||||||
|
pkg_check_modules(SIXEL REQUIRED IMPORTED_TARGET libsixel)
|
||||||
|
pkg_check_modules(CHAFA REQUIRED IMPORTED_TARGET chafa>=1.6)
|
||||||
|
|
||||||
|
if(CMAKE_HOST_SYSTEM_VERSION MATCHES "^.*microsoft.*$")
|
||||||
|
target_compile_definitions(ueberzug PRIVATE WSL)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(APPLE)
|
||||||
|
list(APPEND UEBERZUG_SOURCES src/process/apple.cpp)
|
||||||
|
else()
|
||||||
|
list(APPEND UEBERZUG_SOURCES src/process/linux.cpp)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
configure_file("include/version.hpp.in" version.hpp)
|
||||||
|
configure_file("docs/ueberzugpp.1.in" ueberzugpp.1)
|
||||||
|
|
||||||
|
list(
|
||||||
|
APPEND
|
||||||
|
UEBERZUG_SOURCES
|
||||||
|
"src/main.cpp"
|
||||||
|
"src/application.cpp"
|
||||||
|
"src/os.cpp"
|
||||||
|
"src/tmux.cpp"
|
||||||
|
"src/terminal.cpp"
|
||||||
|
"src/dimensions.cpp"
|
||||||
|
"src/flags.cpp"
|
||||||
|
"src/util/util.cpp"
|
||||||
|
"src/util/socket.cpp"
|
||||||
|
"src/canvas.cpp"
|
||||||
|
"src/canvas/chafa.cpp"
|
||||||
|
"src/canvas/sixel.cpp"
|
||||||
|
"src/canvas/kitty/kitty.cpp"
|
||||||
|
"src/canvas/kitty/chunk.cpp"
|
||||||
|
"src/canvas/iterm2/iterm2.cpp"
|
||||||
|
"src/canvas/iterm2/chunk.cpp"
|
||||||
|
"src/image.cpp"
|
||||||
|
"src/image/libvips.cpp")
|
||||||
|
|
||||||
|
list(
|
||||||
|
APPEND
|
||||||
|
UEBERZUG_LIBRARIES
|
||||||
|
nlohmann_json::nlohmann_json
|
||||||
|
CLI11::CLI11
|
||||||
|
Threads::Threads
|
||||||
|
fmt::fmt
|
||||||
|
spdlog::spdlog
|
||||||
|
range-v3
|
||||||
|
OpenSSL::Crypto
|
||||||
|
TBB::tbb
|
||||||
|
PkgConfig::VIPS
|
||||||
|
PkgConfig::SIXEL
|
||||||
|
PkgConfig::CHAFA)
|
||||||
|
|
||||||
|
target_include_directories(ueberzug PRIVATE "${CMAKE_SOURCE_DIR}/include"
|
||||||
|
"${PROJECT_BINARY_DIR}")
|
||||||
|
target_sources(ueberzug PRIVATE ${UEBERZUG_SOURCES})
|
||||||
|
target_link_libraries(ueberzug PRIVATE ${UEBERZUG_LIBRARIES})
|
||||||
|
file(CREATE_LINK ueberzug "${PROJECT_BINARY_DIR}/ueberzugpp" SYMBOLIC)
|
||||||
|
|
||||||
|
install(TARGETS ueberzug RUNTIME)
|
||||||
|
install(FILES "${PROJECT_BINARY_DIR}/ueberzugpp" TYPE BIN)
|
||||||
|
install(FILES "${PROJECT_BINARY_DIR}/ueberzugpp.1"
|
||||||
|
DESTINATION "${CMAKE_INSTALL_MANDIR}/man1")
|
||||||
|
install(
|
||||||
|
FILES "${PROJECT_BINARY_DIR}/ueberzugpp.1"
|
||||||
|
DESTINATION "${CMAKE_INSTALL_MANDIR}/man1"
|
||||||
|
RENAME ueberzug.1)
|
||||||
53
CODE_OF_CONDUCT.md
Normal file
53
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Code of Merit
|
||||||
|
|
||||||
|
1. The project creators, lead developers, core team, constitute
|
||||||
|
the managing members of the project and have final say in every decision
|
||||||
|
of the project, technical or otherwise, including overruling previous decisions.
|
||||||
|
There are no limitations to this decisional power.
|
||||||
|
|
||||||
|
2. Contributions are an expected result of your membership on the project.
|
||||||
|
Don't expect others to do your work or help you with your work forever.
|
||||||
|
|
||||||
|
3. All members have the same opportunities to seek any challenge they want
|
||||||
|
within the project.
|
||||||
|
|
||||||
|
4. Authority or position in the project will be proportional
|
||||||
|
to the accrued contribution. Seniority must be earned.
|
||||||
|
|
||||||
|
5. Software is evolutive: the better implementations must supersede lesser
|
||||||
|
implementations. Technical advantage is the primary evaluation metric.
|
||||||
|
|
||||||
|
6. This is a space for technical prowess; topics outside of the project
|
||||||
|
will not be tolerated.
|
||||||
|
|
||||||
|
7. Non technical conflicts will be discussed in a separate space. Disruption
|
||||||
|
of the project will not be allowed.
|
||||||
|
|
||||||
|
8. Individual characteristics, including but not limited to,
|
||||||
|
body, sex, sexual preference, race, language, religion, nationality,
|
||||||
|
or political preferences are irrelevant in the scope of the project and
|
||||||
|
will not be taken into account concerning your value or that of your contribution
|
||||||
|
to the project.
|
||||||
|
|
||||||
|
9. Discuss or debate the idea, not the person.
|
||||||
|
|
||||||
|
10. There is no room for ambiguity: Ambiguity will be met with questioning;
|
||||||
|
further ambiguity will be met with silence. It is the responsibility
|
||||||
|
of the originator to provide requested context.
|
||||||
|
|
||||||
|
11. If something is illegal outside the scope of the project, it is illegal
|
||||||
|
in the scope of the project. This Code of Merit does not take precedence over
|
||||||
|
governing law.
|
||||||
|
|
||||||
|
12. This Code of Merit governs the technical procedures of the project not the
|
||||||
|
activities outside of it.
|
||||||
|
|
||||||
|
13. Participation on the project equates to agreement of this Code of Merit.
|
||||||
|
|
||||||
|
14. No objectives beyond the stated objectives of this project are relevant
|
||||||
|
to the project. Any intent to deviate the project from its original purpose
|
||||||
|
of existence will constitute grounds for remedial action which may include
|
||||||
|
expulsion from the project.
|
||||||
|
|
||||||
|
This document is adapted from the Code of Merit, version 1.0.
|
||||||
|
See: https://codeofmerit.org/.
|
||||||
674
LICENSE
Normal file
674
LICENSE
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
247
README.md
Normal file
247
README.md
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
# pantsnet frozen version with install script
|
||||||
|
|
||||||
|
## Überzug++
|
||||||
|
|
||||||
|
Überzug++ is a command line utility written in C++ which allows to draw images on terminals by using X11/wayland child
|
||||||
|
windows, sixels, kitty and iterm2..
|
||||||
|
|
||||||
|
This project intends to be a drop-in replacement for the now defunct [ueberzug](https://github.com/seebye/ueberzug)
|
||||||
|
project. If some tool doesn't work,
|
||||||
|
feel free to open an issue.
|
||||||
|
|
||||||
|
Advantages over w3mimgdisplay and ueberzug:
|
||||||
|
|
||||||
|
- support for wayland: sway, hyprland and wayfire
|
||||||
|
- support for MacOS
|
||||||
|
- no race conditions as a new window is created to display images
|
||||||
|
- expose events will be processed, so images will be redrawn on switch workspaces
|
||||||
|
- tmux support on X11, sway and hyprland
|
||||||
|
- terminals without the WINDOWID environment variable are supported
|
||||||
|
- chars are used as position - and size unit
|
||||||
|
- No memory leak (usage of smart pointers)
|
||||||
|
- A lot of image formats supported (through opencv and libvips).
|
||||||
|
- GIF and animated WEBP support on X11, Sixel, Sway and hyprland
|
||||||
|
- Fast image downscaling (through opencv and opencl)
|
||||||
|
- Cache resized images for faster viewing
|
||||||
|
|
||||||
|
# Applications that support Überzug++
|
||||||
|
|
||||||
|
- [ytfzf](https://github.com/pystardust/ytfzf)
|
||||||
|
- [lobster](https://github.com/justchokingaround/lobster)
|
||||||
|
- [vifm](https://github.com/vifm/vifm)
|
||||||
|
- [rnvimr](https://github.com/kevinhwang91/rnvimr)
|
||||||
|
- [image.nvim](https://github.com/3rd/image.nvim)
|
||||||
|
- [yazi](https://github.com/sxyazi/yazi)
|
||||||
|
- [twitchez](https://github.com/WANDEX/twitchez)
|
||||||
|
- ÜberzugPP is a drop in replacement for Ueberzug, so applications that worked with ueberzug should work out of the box
|
||||||
|
with this project.
|
||||||
|
|
||||||
|
# Integration scripts
|
||||||
|
|
||||||
|
- [lf](https://github.com/jstkdng/ueberzugpp/blob/master/scripts/lf/lfub)
|
||||||
|
- [fzf](https://github.com/jstkdng/ueberzugpp/blob/master/scripts/fzfub)
|
||||||
|
|
||||||
|
# Install
|
||||||
|
|
||||||
|
<a href="https://repology.org/project/ueberzugpp/versions"><img src="https://repology.org/badge/vertical-allrepos/ueberzugpp.svg" /></a>
|
||||||
|
|
||||||
|
### Homebrew (MacOS/Linux)
|
||||||
|
|
||||||
|
`brew install jstkdng/programs/ueberzugpp`
|
||||||
|
|
||||||
|
### Debian/Ubuntu/Fedora
|
||||||
|
|
||||||
|
Packages for x86_64, aarch64 and ppc64le are available in the following repository.
|
||||||
|
|
||||||
|
https://software.opensuse.org/download.html?project=home%3Ajustkidding&package=ueberzugpp
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
1. Ueberzugpp provides two commands, `layer` and `tmux`. `layer` is used to send
|
||||||
|
commands to ueberzugpp, `tmux` is used internally.
|
||||||
|
|
||||||
|
- Layer accepts the following options
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ ueberzug layer -h
|
||||||
|
Display images on the terminal.
|
||||||
|
Usage: ueberzug layer [OPTIONS]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-h,--help Print this help message and exit
|
||||||
|
-s,--silent Print stderr to /dev/null.
|
||||||
|
--use-escape-codes [0] Use escape codes to get terminal capabilities.
|
||||||
|
--pid-file TEXT Output file where to write the daemon PID.
|
||||||
|
--no-stdin Needs: --pid-file
|
||||||
|
Do not listen on stdin for commands.
|
||||||
|
--no-cache Disable caching of resized images.
|
||||||
|
--no-opencv Do not use OpenCV, use Libvips instead.
|
||||||
|
-o,--output TEXT:{x11,wayland,sixel,kitty,iterm2,chafa}
|
||||||
|
Image output method
|
||||||
|
-p,--parser **UNUSED**, only present for backwards compatibility.
|
||||||
|
-l,--loader **UNUSED**, only present for backwards compatibility.
|
||||||
|
```
|
||||||
|
|
||||||
|
2. You can also configure ueberzugpp with a json file. The file should be located
|
||||||
|
on `$XDG_CONFIG_HOME/ueberzugpp/config.json`, in case XDG_CONFIG_HOME isn't set,
|
||||||
|
ueberzugpp will look for the configuration at `~/.config/ueberzugpp/config.json`
|
||||||
|
|
||||||
|
Application flags have precedence over the configuration file.
|
||||||
|
The configuration file should have this format.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"layer": {
|
||||||
|
"silent": true,
|
||||||
|
"use-escape-codes": false,
|
||||||
|
"no-stdin": false,
|
||||||
|
"output": "sixel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The most helpful is the `output` variable as that can be used to force
|
||||||
|
ueberzugpp to output images with a particular method.
|
||||||
|
|
||||||
|
3. You can configure ueberzug++ directory for temporary files (logs, sockets)
|
||||||
|
with `${UEBERZUGPP_TMPDIR}` environment variable (by default it is system temporary directory)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export UEBERZUGPP_TMPDIR="${TMPDIR}/ueberzugpp"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. By default, commands are sent to ueberzug++ through stdin, this is enough in
|
||||||
|
some cases. In some terminals and application combinations (e.g. ranger + wezterm + zellij)
|
||||||
|
using stdin to send commands doesn't work properly or ueberzug++ could fail to
|
||||||
|
start altogether. In those cases, the user may send commands to ueberzug++ through
|
||||||
|
a unix socket. By default, ueberzug++ will listen to commands on `${UEBERZUGPP_TMPDIR}/ueberzugpp-${PID}.socket`.
|
||||||
|
|
||||||
|
New software is encouraged to use sockets instead of stdin as they cover more cases.
|
||||||
|
|
||||||
|
5. You can then feed Ueberzug with json objects to display an image or make it disappear.
|
||||||
|
|
||||||
|
- json object to display the image:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"action":"add","identifier":"preview","max_height":0,"max_width":0,"path":"/path/image.ext","x":0,"y":0}
|
||||||
|
```
|
||||||
|
|
||||||
|
The number values are COLUMNS and LINES of your terminal window, in TMUX it's relative to the size of the panels.
|
||||||
|
|
||||||
|
- Don't display the image anymore:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{"action":"remove","identifier":"preview"}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Build from source
|
||||||
|
|
||||||
|
This project uses C++20 features so you must use a recent compiler. GCC 10.1 is
|
||||||
|
the minimum supported version.
|
||||||
|
|
||||||
|
## Required dependencies
|
||||||
|
|
||||||
|
Must be installed in order to build.
|
||||||
|
|
||||||
|
- cmake ≥ 3.22
|
||||||
|
- libvips
|
||||||
|
- libsixel
|
||||||
|
- chafa ≥ 1.6
|
||||||
|
- openssl
|
||||||
|
- tbb
|
||||||
|
|
||||||
|
### Install dependencies on Ubuntu
|
||||||
|
|
||||||
|
```
|
||||||
|
apt-get install libssl-dev libvips-dev libsixel-dev libchafa-dev libtbb-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Downloadable dependencies
|
||||||
|
|
||||||
|
Required for building, if they are not installed, they will be downloaded
|
||||||
|
and included in the binary.
|
||||||
|
|
||||||
|
- nlohmann-json
|
||||||
|
- cli11
|
||||||
|
- spdlog
|
||||||
|
- fmt
|
||||||
|
- range-v3
|
||||||
|
|
||||||
|
## Optional dependencies
|
||||||
|
|
||||||
|
Not required for building, can be disabled/enabled using flags.
|
||||||
|
|
||||||
|
- opencv
|
||||||
|
- xcb-util-image
|
||||||
|
- turbo-base64
|
||||||
|
- wayland (libwayland)
|
||||||
|
- wayland-protocols
|
||||||
|
- extra-cmake-modules
|
||||||
|
|
||||||
|
## Build instructions
|
||||||
|
|
||||||
|
1. Download and extract the latest release.
|
||||||
|
|
||||||
|
2. Choose feature flags
|
||||||
|
|
||||||
|
The following feature flags are available:
|
||||||
|
|
||||||
|
ENABLE_OPENCV (ON by default)
|
||||||
|
|
||||||
|
ENABLE_X11 (ON by default)
|
||||||
|
|
||||||
|
ENABLE_TURBOBASE64 (OFF by default)
|
||||||
|
|
||||||
|
ENABLE_WAYLAND (OFF by default)
|
||||||
|
|
||||||
|
You may use any of them when building the project, for example:
|
||||||
|
|
||||||
|
- Compile with default options
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/jstkdng/ueberzugpp.git
|
||||||
|
cd ueberzugpp
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||||
|
cmake --build .
|
||||||
|
```
|
||||||
|
|
||||||
|
- Disable X11 and OpenCV support
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/jstkdng/ueberzugpp.git
|
||||||
|
cd ueberzugpp
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_X11=OFF -DENABLE_OPENCV=OFF ..
|
||||||
|
cmake --build .
|
||||||
|
```
|
||||||
|
|
||||||
|
- Enable support for Turbo-Base64
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/jstkdng/ueberzugpp.git
|
||||||
|
cd ueberzugpp
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_TURBOBASE64=ON ..
|
||||||
|
cmake --build .
|
||||||
|
```
|
||||||
|
|
||||||
|
after running these commands the resulting binary is ready to be used.
|
||||||
|
|
||||||
|
- Install the resulting build directory to the default installation path (Optional)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake --install build
|
||||||
|
```
|
||||||
|
|
||||||
|
# Donate
|
||||||
|
|
||||||
|
If you like my work you can send some monero my way.
|
||||||
|
|
||||||
|
XMR Address: `8BRt2qYXjyR9Bb2CXtjVWSYNCepqgcZkheoMWTXTNmwLLU3ZEscuxtYFGaytSMNn1FETLdbdhXimCTTLSkN5r5j7SEBLMho`
|
||||||
|
|
||||||
|
# Thanks
|
||||||
|
|
||||||
|
Thank you jetbrains for providing licenses for this project.
|
||||||
|
|
||||||
|
[<img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg">](https://jb.gg/OpenSourceSupport)
|
||||||
5
debian/changelog
vendored
Normal file
5
debian/changelog
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
ueberzugpp (2.8.6) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Initial release.
|
||||||
|
|
||||||
|
-- JustKidding <jk@vin.ovh> Wed, 31 May 2023 07:35:31 -0400
|
||||||
5
debian/changelog.in
vendored
Normal file
5
debian/changelog.in
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
ueberzugpp (@@VERSION@@) unstable; urgency=medium
|
||||||
|
|
||||||
|
* Initial release.
|
||||||
|
|
||||||
|
-- JustKidding <jk@vin.ovh> @@DATETIME@@
|
||||||
37
debian/control
vendored
Normal file
37
debian/control
vendored
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
Source: ueberzugpp
|
||||||
|
Maintainer: JustKidding <jk@vin.ovh>
|
||||||
|
Homepage: https://github.com/jstkdng/ueberzugpp
|
||||||
|
Section: misc
|
||||||
|
Priority: optional
|
||||||
|
Standards-Version: 4.5.1
|
||||||
|
Conflicts: ueberzug
|
||||||
|
Build-Depends:
|
||||||
|
debhelper (>= 13),
|
||||||
|
debhelper-compat (= 13),
|
||||||
|
cmake (>= 3.18),
|
||||||
|
libchafa-dev,
|
||||||
|
libvips-dev,
|
||||||
|
nlohmann-json3-dev,
|
||||||
|
libspdlog-dev,
|
||||||
|
libfmt-dev,
|
||||||
|
libopencv-videoio-dev,
|
||||||
|
libopencv-dev,
|
||||||
|
libopencv-imgproc-dev,
|
||||||
|
libopencv-imgcodecs-dev,
|
||||||
|
libopencv-core-dev,
|
||||||
|
libsixel-dev,
|
||||||
|
libssl-dev,
|
||||||
|
libxcb-image0-dev,
|
||||||
|
libxcb-res0-dev,
|
||||||
|
wayland-protocols,
|
||||||
|
libwayland-dev,
|
||||||
|
librange-v3-dev,
|
||||||
|
extra-cmake-modules
|
||||||
|
|
||||||
|
Package: ueberzugpp
|
||||||
|
Architecture: any
|
||||||
|
Depends: ${shlibs:Depends}, ${misc:Depends}
|
||||||
|
Description: Display images inside the terminal.
|
||||||
|
Überzug++ is a command line utility written in C++ which
|
||||||
|
allows to draw images on terminals by using X11/wayland
|
||||||
|
child windows, sixels, kitty and iterm2.
|
||||||
36
debian/copyright
vendored
Normal file
36
debian/copyright
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: ueberzugpp
|
||||||
|
Upstream-Contact: JustKidding <jk@vin.ovh>
|
||||||
|
Source: https://github.com/jstkdng/ueberzugpp
|
||||||
|
|
||||||
|
Files: *
|
||||||
|
Copyright: JustKidding <jk@vin.ovh>
|
||||||
|
License: GPL-3+ with OpenSSL exception
|
||||||
|
Display images inside a terminal
|
||||||
|
Copyright (C) 2023 JustKidding
|
||||||
|
.
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
.
|
||||||
|
In addition, as a special exception, the author of this program gives
|
||||||
|
permission to link the code of its release with the OpenSSL project's
|
||||||
|
"OpenSSL" library (or with modified versions of it that use the same
|
||||||
|
license as the "OpenSSL" library), and distribute the linked executables.
|
||||||
|
You must obey the GNU General Public License in all respects for all of
|
||||||
|
the code used other than "OpenSSL". If you modify this file, you may
|
||||||
|
extend this exception to your version of the file, but you are not
|
||||||
|
obligated to do so. If you do not wish to do so, delete this exception
|
||||||
|
statement from your version.
|
||||||
|
.
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
.
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
Comment:
|
||||||
|
On Debian systems, the full text of the GNU General Public License
|
||||||
|
version 3 can be found in the file '/usr/share/common-licenses/GPL-3'.
|
||||||
10
debian/rules
vendored
Executable file
10
debian/rules
vendored
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/make -f
|
||||||
|
# See debhelper(7) (uncomment to enable)
|
||||||
|
export DH_VERBOSE := 1
|
||||||
|
%:
|
||||||
|
dh $@ --buildsystem=cmake
|
||||||
|
|
||||||
|
override_dh_auto_configure:
|
||||||
|
dh_auto_configure -- \
|
||||||
|
-DENABLE_WAYLAND=ON \
|
||||||
|
-DFETCHCONTENT_FULLY_DISCONNECTED=ON
|
||||||
1
debian/source/format
vendored
Normal file
1
debian/source/format
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
3.0 (native)
|
||||||
169
docs/ueberzugpp.1.in
Normal file
169
docs/ueberzugpp.1.in
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
.TH UEBERZUGPP 1 "2023 May" "Ueberzugpp @ueberzugpp_VERSION@"
|
||||||
|
|
||||||
|
.SH NAME
|
||||||
|
ueberzugpp \- display images in a terminal
|
||||||
|
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.SY ueberzugpp
|
||||||
|
.RI [ options ]
|
||||||
|
|
||||||
|
.SH DESCRIPTION
|
||||||
|
.PP
|
||||||
|
.B ueberzugpp
|
||||||
|
is a program meant to display images in a terminal in a with an IPC.
|
||||||
|
|
||||||
|
.SH OPTIONS
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR \-h ", " \-\-help
|
||||||
|
Show help text.
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR \-\-version
|
||||||
|
Show version
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR \-\-use\-escape\-codes
|
||||||
|
Use escape codes to get terminal capabilities
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR \-\-no\-stdin
|
||||||
|
Do not listen on stdin for commands
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR \-\-no\-cache
|
||||||
|
Disable caching of resized images
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR \-\-no\-opencv
|
||||||
|
Do not use OpenCV, use Libvips instead
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR \-o ", " \-\-output
|
||||||
|
Image output method, valid values for this include:
|
||||||
|
.PP
|
||||||
|
.RS
|
||||||
|
.I x11 " (May not be available if disabled in compilation)"
|
||||||
|
.br
|
||||||
|
.I sixel
|
||||||
|
.br
|
||||||
|
.I kitty
|
||||||
|
.br
|
||||||
|
.I iterm2
|
||||||
|
.br
|
||||||
|
.I wayland " (May not be available if disabled in compilation)"
|
||||||
|
.br
|
||||||
|
.I chafa
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR \-p ", " \-\-parser
|
||||||
|
.B UNUSED ", "
|
||||||
|
only present for backwards compatibility
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.BR \-l ", " \-\-loader
|
||||||
|
.B UNUSED ", "
|
||||||
|
only present for backwards compatibility
|
||||||
|
|
||||||
|
.SH STDIN
|
||||||
|
|
||||||
|
.PP
|
||||||
|
Ueberzugpp reads commands through stdin. Or through the unix socket located at /tmp/ueberzug_${USER}.sock
|
||||||
|
.PP
|
||||||
|
Commands should be in JSON form, as described in the JSON IPC section
|
||||||
|
|
||||||
|
.SH JSON IPC
|
||||||
|
|
||||||
|
.PP
|
||||||
|
There are two actions,
|
||||||
|
.I add
|
||||||
|
and
|
||||||
|
.I remove
|
||||||
|
.PP
|
||||||
|
|
||||||
|
.SS
|
||||||
|
.B add
|
||||||
|
action json schema
|
||||||
|
.PP
|
||||||
|
Requried Keys
|
||||||
|
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
|
.B action " (string)"
|
||||||
|
should be
|
||||||
|
.I add
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B path " (string)"
|
||||||
|
the path to the image to use
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B identifier " (string)"
|
||||||
|
an identifier for the image, so that it can be removed with the remove action
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B One of, width/height, or max_width/max_height
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B width " (integer)"
|
||||||
|
width of the image
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B max_width " (integer)"
|
||||||
|
maximum width of the image
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B height " (integer)"
|
||||||
|
height of the image
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B max_height " (integer)"
|
||||||
|
maximum height of the image
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B x " (integer)"
|
||||||
|
the column position in the terminal
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B y " (integer)"
|
||||||
|
the row position in the terminal
|
||||||
|
|
||||||
|
.PP
|
||||||
|
Optional keys
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B scaler " (string)"
|
||||||
|
can be fit_contain or forced_cover.
|
||||||
|
.br
|
||||||
|
Both base the scale on whichever is larger, the width, or height of the image
|
||||||
|
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.SS
|
||||||
|
.B remove
|
||||||
|
action json schema
|
||||||
|
.PP
|
||||||
|
Requried Keys
|
||||||
|
|
||||||
|
.RS
|
||||||
|
.TP
|
||||||
|
.B action " (string)"
|
||||||
|
should be remove
|
||||||
|
|
||||||
|
.TP
|
||||||
|
.B identifier " (string)"
|
||||||
|
The identifier of the image to remove
|
||||||
|
|
||||||
|
.RE
|
||||||
|
|
||||||
|
.SH EXAMPLE
|
||||||
|
|
||||||
|
.PP
|
||||||
|
Create a fifo file named fifo, and have an image in the current folder named image.png for this example to work
|
||||||
|
|
||||||
|
.PP
|
||||||
|
ueberzugpp layer -o sixel < fifo &
|
||||||
|
|
||||||
|
.PP
|
||||||
|
echo '{"path": "./image.png", "action": "add", "identifier": "image", "x": 0, "y": 0, "width": 20, "height": 20}' > fifo
|
||||||
66
include/application.hpp
Normal file
66
include/application.hpp
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef APPLICATION_H
|
||||||
|
#define APPLICATION_H
|
||||||
|
|
||||||
|
#include "canvas.hpp"
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "terminal.hpp"
|
||||||
|
#include "util/ptr.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
class Application
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Application(const char *executable);
|
||||||
|
~Application();
|
||||||
|
|
||||||
|
void execute(std::string_view cmd);
|
||||||
|
void command_loop();
|
||||||
|
void handle_tmux_hook(std::string_view hook);
|
||||||
|
|
||||||
|
inline static std::atomic<bool> stop_flag = false; // NOLINT
|
||||||
|
inline static const int parent_pid = os::get_ppid();
|
||||||
|
|
||||||
|
static void print_version();
|
||||||
|
static void print_header();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Terminal> terminal;
|
||||||
|
std::unique_ptr<Canvas> canvas;
|
||||||
|
|
||||||
|
std::shared_ptr<Flags> flags;
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
|
||||||
|
cn_unique_ptr<std::FILE, std::fclose> f_stderr;
|
||||||
|
std::thread socket_thread;
|
||||||
|
|
||||||
|
void setup_logger();
|
||||||
|
void set_silent();
|
||||||
|
void socket_loop();
|
||||||
|
void daemonize();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
38
include/canvas.hpp
Normal file
38
include/canvas.hpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef CANVAS_H
|
||||||
|
#define CANVAS_H
|
||||||
|
|
||||||
|
#include "image.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class Canvas
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static auto create() -> std::unique_ptr<Canvas>;
|
||||||
|
virtual ~Canvas() = default;
|
||||||
|
|
||||||
|
virtual void add_image(const std::string &identifier, std::unique_ptr<Image> new_image) = 0;
|
||||||
|
virtual void remove_image(const std::string &identifier) = 0;
|
||||||
|
|
||||||
|
virtual void show() {}
|
||||||
|
virtual void hide() {}
|
||||||
|
virtual void toggle() {}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
51
include/dimensions.hpp
Normal file
51
include/dimensions.hpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef DIMENSIONS_H
|
||||||
|
#define DIMENSIONS_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "terminal.hpp"
|
||||||
|
|
||||||
|
class Dimensions
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Dimensions(const Terminal *terminal, uint16_t xcoord, uint16_t ycoord, int max_w, int max_h, std::string scaler);
|
||||||
|
|
||||||
|
[[nodiscard]] auto xpixels() const -> int;
|
||||||
|
[[nodiscard]] auto ypixels() const -> int;
|
||||||
|
[[nodiscard]] auto max_wpixels() const -> int;
|
||||||
|
[[nodiscard]] auto max_hpixels() const -> int;
|
||||||
|
|
||||||
|
uint16_t x;
|
||||||
|
uint16_t y;
|
||||||
|
uint16_t max_w;
|
||||||
|
uint16_t max_h;
|
||||||
|
uint16_t padding_horizontal;
|
||||||
|
uint16_t padding_vertical;
|
||||||
|
std::string scaler;
|
||||||
|
const Terminal *terminal;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint16_t orig_x;
|
||||||
|
uint16_t orig_y;
|
||||||
|
|
||||||
|
void read_offsets();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
68
include/flags.hpp
Normal file
68
include/flags.hpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef FLAGS_H
|
||||||
|
#define FLAGS_H
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
// singleton
|
||||||
|
class Flags
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static auto instance() -> std::shared_ptr<Flags>
|
||||||
|
{
|
||||||
|
static std::shared_ptr<Flags> instance{new Flags};
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
Flags(const Flags &) = delete;
|
||||||
|
Flags(Flags &) = delete;
|
||||||
|
auto operator=(const Flags &) -> Flags & = delete;
|
||||||
|
auto operator=(Flags &) -> Flags & = delete;
|
||||||
|
|
||||||
|
bool no_stdin = false;
|
||||||
|
bool silent = false;
|
||||||
|
bool use_escape_codes = false;
|
||||||
|
bool print_version = false;
|
||||||
|
bool no_cache = false;
|
||||||
|
bool no_opencv = false;
|
||||||
|
bool use_opengl = false;
|
||||||
|
std::string output;
|
||||||
|
std::string pid_file;
|
||||||
|
bool origin_center = false;
|
||||||
|
int32_t scale_factor = 1;
|
||||||
|
bool needs_scaling = false;
|
||||||
|
|
||||||
|
std::string cmd_id;
|
||||||
|
std::string cmd_action;
|
||||||
|
std::string cmd_socket;
|
||||||
|
std::string cmd_x;
|
||||||
|
std::string cmd_y;
|
||||||
|
std::string cmd_max_width;
|
||||||
|
std::string cmd_max_height;
|
||||||
|
std::string cmd_file_path;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Flags();
|
||||||
|
std::filesystem::path config_file;
|
||||||
|
|
||||||
|
void read_config_file();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
54
include/image.hpp
Normal file
54
include/image.hpp
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef IMAGE_H
|
||||||
|
#define IMAGE_H
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "terminal.hpp"
|
||||||
|
|
||||||
|
class Image
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static auto load(const nlohmann::json &command, const Terminal *terminal) -> std::unique_ptr<Image>;
|
||||||
|
static auto check_cache(const Dimensions &dimensions, const std::filesystem::path &orig_path) -> std::string;
|
||||||
|
static auto get_dimensions(const nlohmann::json &json, const Terminal *terminal) -> std::shared_ptr<Dimensions>;
|
||||||
|
|
||||||
|
virtual ~Image() = default;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual auto dimensions() const -> const Dimensions & = 0;
|
||||||
|
[[nodiscard]] virtual auto width() const -> int = 0;
|
||||||
|
[[nodiscard]] virtual auto height() const -> int = 0;
|
||||||
|
[[nodiscard]] virtual auto size() const -> size_t = 0;
|
||||||
|
[[nodiscard]] virtual auto data() const -> const unsigned char * = 0;
|
||||||
|
[[nodiscard]] virtual auto channels() const -> int = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual auto frame_delay() const -> int { return -1; }
|
||||||
|
[[nodiscard]] virtual auto is_animated() const -> bool { return false; }
|
||||||
|
[[nodiscard]] virtual auto filename() const -> std::string = 0;
|
||||||
|
virtual auto next_frame() -> void {}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
[[nodiscard]] auto get_new_sizes(double max_width, double max_height, std::string_view scaler,
|
||||||
|
int scale_factor = 0) const -> std::pair<int, int>;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
41
include/os.hpp
Normal file
41
include/os.hpp
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef NAMESPACE_OS_H
|
||||||
|
#define NAMESPACE_OS_H
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace os
|
||||||
|
{
|
||||||
|
|
||||||
|
auto exec(const std::string &cmd) -> std::string;
|
||||||
|
auto getenv(const std::string &var) -> std::optional<std::string>;
|
||||||
|
auto read_data_from_fd(int filde, char sep = '\n') -> std::string;
|
||||||
|
auto read_data_from_stdin(char sep = '\n') -> std::string;
|
||||||
|
auto wait_for_data_on_fd(int filde, int waitms) -> bool;
|
||||||
|
auto wait_for_data_on_stdin(int waitms) -> bool;
|
||||||
|
|
||||||
|
auto get_pid() -> int;
|
||||||
|
auto get_ppid() -> int;
|
||||||
|
|
||||||
|
void get_process_info(int pid);
|
||||||
|
void daemonize();
|
||||||
|
|
||||||
|
} // namespace os
|
||||||
|
|
||||||
|
#endif
|
||||||
38
include/process.hpp
Normal file
38
include/process.hpp
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef PROCESS_INFO_H
|
||||||
|
#define PROCESS_INFO_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Process
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit Process(int pid);
|
||||||
|
~Process() = default;
|
||||||
|
|
||||||
|
int pid;
|
||||||
|
char state;
|
||||||
|
int ppid;
|
||||||
|
int process_group_id;
|
||||||
|
int session_id;
|
||||||
|
int tty_nr;
|
||||||
|
int minor_dev;
|
||||||
|
std::string pty_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
89
include/terminal.hpp
Normal file
89
include/terminal.hpp
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef TERMINAL_H
|
||||||
|
#define TERMINAL_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <spdlog/fwd.h>
|
||||||
|
#include <termios.h>
|
||||||
|
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
|
||||||
|
class Terminal
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Terminal();
|
||||||
|
~Terminal();
|
||||||
|
|
||||||
|
uint16_t font_width;
|
||||||
|
uint16_t font_height;
|
||||||
|
uint16_t padding_horizontal;
|
||||||
|
uint16_t padding_vertical;
|
||||||
|
uint16_t rows;
|
||||||
|
uint16_t cols;
|
||||||
|
int pid = os::get_pid();
|
||||||
|
int terminal_pid;
|
||||||
|
unsigned int x11_wid;
|
||||||
|
std::string term;
|
||||||
|
std::string term_program;
|
||||||
|
std::string detected_output;
|
||||||
|
|
||||||
|
private:
|
||||||
|
auto get_terminal_size() -> void;
|
||||||
|
static auto guess_padding(uint16_t chars, double pixels) -> double;
|
||||||
|
static auto guess_font_size(uint16_t chars, float pixels, float padding) -> float;
|
||||||
|
static auto read_raw_str(std::string_view esc) -> std::string;
|
||||||
|
|
||||||
|
void init_termios();
|
||||||
|
void reset_termios() const;
|
||||||
|
|
||||||
|
void check_sixel_support();
|
||||||
|
void check_kitty_support();
|
||||||
|
void check_iterm2_support();
|
||||||
|
|
||||||
|
void get_terminal_size_escape_code();
|
||||||
|
void get_terminal_size_xtsm();
|
||||||
|
void get_fallback_x11_terminal_sizes();
|
||||||
|
void get_fallback_wayland_terminal_sizes();
|
||||||
|
|
||||||
|
void open_first_pty();
|
||||||
|
void set_detected_output();
|
||||||
|
|
||||||
|
int pty_fd;
|
||||||
|
int xpixel = 0;
|
||||||
|
int ypixel = 0;
|
||||||
|
uint16_t fallback_xpixel = 0;
|
||||||
|
uint16_t fallback_ypixel = 0;
|
||||||
|
|
||||||
|
bool supports_sixel = false;
|
||||||
|
bool supports_kitty = false;
|
||||||
|
bool supports_x11 = false;
|
||||||
|
bool supports_iterm2 = false;
|
||||||
|
bool supports_wayland = false;
|
||||||
|
|
||||||
|
std::shared_ptr<Flags> flags;
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
|
||||||
|
struct termios old_term;
|
||||||
|
struct termios new_term;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
46
include/tmux.hpp
Normal file
46
include/tmux.hpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef NAMESPACE_TMUX_H
|
||||||
|
#define NAMESPACE_TMUX_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <optional>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace tmux
|
||||||
|
{
|
||||||
|
auto get_session_id() -> std::string;
|
||||||
|
auto get_pane() -> std::string;
|
||||||
|
|
||||||
|
auto is_used() -> bool;
|
||||||
|
auto is_window_focused() -> bool;
|
||||||
|
|
||||||
|
auto get_client_pids() -> std::optional<std::vector<int>>;
|
||||||
|
|
||||||
|
auto get_offset() -> std::pair<int, int>;
|
||||||
|
|
||||||
|
auto get_pane_offset() -> std::pair<int, int>;
|
||||||
|
|
||||||
|
auto get_statusbar_offset() -> int;
|
||||||
|
|
||||||
|
void handle_hook(std::string_view hook, int pid);
|
||||||
|
void register_hooks();
|
||||||
|
void unregister_hooks();
|
||||||
|
} // namespace tmux
|
||||||
|
|
||||||
|
#endif
|
||||||
67
include/util.hpp
Normal file
67
include/util.hpp
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef NAMESPACE_UTIL_H
|
||||||
|
#define NAMESPACE_UTIL_H
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
|
#include <optional>
|
||||||
|
#include <random>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "process.hpp"
|
||||||
|
|
||||||
|
namespace util
|
||||||
|
{
|
||||||
|
auto str_split(std::string_view str, std::string_view delim) -> std::vector<std::string>;
|
||||||
|
auto get_process_tree(int pid) -> std::vector<int>;
|
||||||
|
auto get_process_tree_v2(int pid) -> std::vector<Process>;
|
||||||
|
auto get_b2_hash_ssl(std::string_view str) -> std::string;
|
||||||
|
auto get_cache_path() -> std::string;
|
||||||
|
auto get_cache_file_save_location(const std::filesystem::path &path) -> std::string;
|
||||||
|
auto get_log_filename() -> std::string;
|
||||||
|
auto get_socket_path(int pid = os::get_pid()) -> std::string;
|
||||||
|
void send_socket_message(std::string_view msg, std::string_view endpoint);
|
||||||
|
auto base64_encode(const unsigned char *input, size_t length) -> std::string;
|
||||||
|
void base64_encode_v2(const unsigned char *input, size_t length, unsigned char *out);
|
||||||
|
void move_cursor(int row, int col);
|
||||||
|
void save_cursor_position();
|
||||||
|
void restore_cursor_position();
|
||||||
|
void benchmark(const std::function<void(void)> &func);
|
||||||
|
void send_command(const Flags &flags);
|
||||||
|
void clear_terminal_area(int xcoord, int ycoord, int width, int height);
|
||||||
|
auto generate_random_string(std::size_t length) -> std::string;
|
||||||
|
auto round_up(int num_to_round, int multiple) -> int;
|
||||||
|
auto temp_directory_path() -> std::filesystem::path;
|
||||||
|
|
||||||
|
auto read_exif_rotation(const std::filesystem::path &path) -> std::optional<std::uint16_t>;
|
||||||
|
template <typename T>
|
||||||
|
auto generate_random_number(T min, T max = std::numeric_limits<T>::max()) -> T
|
||||||
|
{
|
||||||
|
std::random_device dev;
|
||||||
|
std::mt19937 rng(dev());
|
||||||
|
std::uniform_int_distribution<T> dist(min, max);
|
||||||
|
return dist(rng);
|
||||||
|
}
|
||||||
|
} // namespace util
|
||||||
|
|
||||||
|
#endif
|
||||||
33
include/util/dbus.hpp
Normal file
33
include/util/dbus.hpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef UEBERZUGPP_DBUS_HPP
|
||||||
|
#define UEBERZUGPP_DBUS_HPP
|
||||||
|
|
||||||
|
#include <dbus/dbus.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class DbusUtil
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit DbusUtil(const std::string &address);
|
||||||
|
~DbusUtil();
|
||||||
|
|
||||||
|
private:
|
||||||
|
DBusConnection *connection = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // UEBERZUGPP_DBUS_HPP
|
||||||
54
include/util/egl.hpp
Normal file
54
include/util/egl.hpp
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef UTIL_EGL_H
|
||||||
|
#define UTIL_EGL_H
|
||||||
|
|
||||||
|
#include "image.hpp"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <spdlog/fwd.h>
|
||||||
|
|
||||||
|
#include <EGL/egl.h>
|
||||||
|
#define GL_GLEXT_PROTOTYPES
|
||||||
|
#include <GL/gl.h>
|
||||||
|
|
||||||
|
template <class T, class V>
|
||||||
|
class EGLUtil
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EGLUtil(EGLenum platform, T *native_display, const EGLAttrib *attrib = nullptr);
|
||||||
|
~EGLUtil();
|
||||||
|
|
||||||
|
void get_texture_from_image(const Image &image, GLuint texture) const;
|
||||||
|
void run_contained(EGLSurface surface, EGLContext context, const std::function<void()> &func) const;
|
||||||
|
void make_current(EGLSurface surface, EGLContext context) const;
|
||||||
|
void restore() const;
|
||||||
|
[[nodiscard]] auto create_surface(V *native_window) const -> EGLSurface;
|
||||||
|
[[nodiscard]] auto create_context(EGLSurface surface) const -> EGLContext;
|
||||||
|
|
||||||
|
EGLDisplay display;
|
||||||
|
|
||||||
|
private:
|
||||||
|
EGLConfig config;
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
|
||||||
|
[[nodiscard]] auto error_to_string() const -> std::string;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
53
include/util/ptr.hpp
Normal file
53
include/util/ptr.hpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef UTIL_PTR_H
|
||||||
|
#define UTIL_PTR_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
template <auto fn>
|
||||||
|
struct deleter_from_fn {
|
||||||
|
template <typename T>
|
||||||
|
constexpr void operator()(T *arg) const
|
||||||
|
{
|
||||||
|
fn(const_cast<std::remove_const_t<T> *>(arg));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <auto fn>
|
||||||
|
struct deleter_from_fn_null {
|
||||||
|
template <typename T>
|
||||||
|
constexpr void operator()(T *arg) const
|
||||||
|
{
|
||||||
|
if (arg != nullptr) {
|
||||||
|
fn(const_cast<std::remove_const_t<T> *>(arg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// custom unique pointer
|
||||||
|
template <typename T, auto fn>
|
||||||
|
using c_unique_ptr = std::unique_ptr<T, deleter_from_fn<fn>>;
|
||||||
|
|
||||||
|
// custom unique pointer that checks for null before deleting
|
||||||
|
template <typename T, auto fn>
|
||||||
|
using cn_unique_ptr = std::unique_ptr<T, deleter_from_fn_null<fn>>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using unique_C_ptr = std::unique_ptr<T, deleter_from_fn<std::free>>;
|
||||||
|
|
||||||
|
#endif
|
||||||
46
include/util/socket.hpp
Normal file
46
include/util/socket.hpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef UTIL_SOCKET_H
|
||||||
|
#define UTIL_SOCKET_H
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class UnixSocket
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
UnixSocket();
|
||||||
|
explicit UnixSocket(std::string_view endpoint);
|
||||||
|
~UnixSocket();
|
||||||
|
|
||||||
|
void connect_to_endpoint(std::string_view endpoint);
|
||||||
|
void bind_to_endpoint(std::string_view endpoint) const;
|
||||||
|
[[nodiscard]] auto wait_for_connections(int waitms) const -> int;
|
||||||
|
[[nodiscard]] auto read_data_from_connection(int filde) -> std::vector<std::string>;
|
||||||
|
void write(const void *data, std::size_t len) const;
|
||||||
|
void read(void *data, std::size_t len) const;
|
||||||
|
[[nodiscard]] auto read_until_empty() const -> std::string;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int fd;
|
||||||
|
bool connected = true;
|
||||||
|
std::string buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
49
include/util/x11.hpp
Normal file
49
include/util/x11.hpp
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef X11_UTIL_H
|
||||||
|
#define X11_UTIL_H
|
||||||
|
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
|
class X11Util
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
X11Util();
|
||||||
|
explicit X11Util(xcb_connection_t *connection);
|
||||||
|
~X11Util();
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_server_window_ids() const -> std::vector<xcb_window_t>;
|
||||||
|
[[nodiscard]] auto get_pid_window_map() const -> std::unordered_map<uint32_t, xcb_window_t>;
|
||||||
|
[[nodiscard]] auto get_window_dimensions(xcb_window_t window) const -> std::pair<uint16_t, uint16_t>;
|
||||||
|
[[nodiscard]] auto get_parent_window(int pid) const -> xcb_window_t;
|
||||||
|
[[nodiscard]] auto window_has_properties(xcb_window_t window, std::initializer_list<xcb_atom_t> properties) const
|
||||||
|
-> bool;
|
||||||
|
|
||||||
|
bool connected = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
xcb_connection_t *connection;
|
||||||
|
xcb_screen_t *screen = nullptr;
|
||||||
|
bool owns_connection = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
19
include/version.hpp.in
Normal file
19
include/version.hpp.in
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#define ueberzugpp_VERSION_MAJOR @ueberzugpp_VERSION_MAJOR@
|
||||||
|
#define ueberzugpp_VERSION_MINOR @ueberzugpp_VERSION_MINOR@
|
||||||
|
#define ueberzugpp_VERSION_PATCH @ueberzugpp_VERSION_PATCH@
|
||||||
36
include/window.hpp
Normal file
36
include/window.hpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef WINDOW_H
|
||||||
|
#define WINDOW_H
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class Window
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~Window() = default;
|
||||||
|
virtual void draw() = 0;
|
||||||
|
virtual void generate_frame() = 0;
|
||||||
|
virtual void show() {};
|
||||||
|
virtual void hide() {};
|
||||||
|
};
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
concept WindowType = std::is_base_of<Window, T>::value;
|
||||||
|
|
||||||
|
#endif
|
||||||
18
install
Executable file
18
install
Executable file
@ -0,0 +1,18 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# install script for debian-like systems
|
||||||
|
|
||||||
|
echo "just installing dependencies..."
|
||||||
|
sudo apt install libxcb-* libopencv-dev make cmake
|
||||||
|
|
||||||
|
echo "building"
|
||||||
|
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
cmake -DCMAKE_BUILD_TYPE=Release ..
|
||||||
|
cmake --build .
|
||||||
|
|
||||||
|
echo "done building, installing"
|
||||||
|
sudo install -Dm755 ueberzug /usr/local/bin/ueberzug
|
||||||
|
sudo ln -sf /usr/local/bin/ueberzug /usr/local/bin/ueberzugpp
|
||||||
|
|
||||||
|
echo "done"
|
||||||
1
plugins/wayfire/.gitignore
vendored
Normal file
1
plugins/wayfire/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.cache/
|
||||||
66
plugins/wayfire/CMakeLists.txt
Normal file
66
plugins/wayfire/CMakeLists.txt
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# Display images inside a terminal
|
||||||
|
# Copyright (C) 2023 JustKidding
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.18)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 17)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF)
|
||||||
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type.")
|
||||||
|
|
||||||
|
cmake_policy(SET CMP0065 NEW)
|
||||||
|
set(CMAKE_ENABLE_EXPORTS ON)
|
||||||
|
|
||||||
|
project(wayfire-ueberzugpp LANGUAGES CXX C VERSION 0.0.1)
|
||||||
|
add_library(ueberzugpp SHARED)
|
||||||
|
|
||||||
|
list(APPEND PLUGIN_SOURCES "ueberzugpp.cpp")
|
||||||
|
|
||||||
|
find_package(PkgConfig REQUIRED)
|
||||||
|
|
||||||
|
find_package(ECM REQUIRED NO_MODULE)
|
||||||
|
list(APPEND CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
|
||||||
|
find_package(WaylandProtocols REQUIRED)
|
||||||
|
find_package(WaylandScanner REQUIRED)
|
||||||
|
|
||||||
|
ecm_add_wayland_server_protocol(PLUGIN_SOURCES
|
||||||
|
PROTOCOL "${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml"
|
||||||
|
BASENAME "xdg-shell")
|
||||||
|
file(CREATE_LINK
|
||||||
|
"wayland-xdg-shell-server-protocol.h"
|
||||||
|
"${PROJECT_BINARY_DIR}/xdg-shell-protocol.h" SYMBOLIC)
|
||||||
|
|
||||||
|
pkg_check_modules(WAYFIRE REQUIRED IMPORTED_TARGET wayfire)
|
||||||
|
pkg_check_modules(WLROOTS REQUIRED IMPORTED_TARGET wlroots)
|
||||||
|
pkg_check_modules(WFCONFIG REQUIRED IMPORTED_TARGET wf-config)
|
||||||
|
|
||||||
|
add_compile_definitions(WLR_USE_UNSTABLE WAYFIRE_PLUGIN)
|
||||||
|
target_compile_options(ueberzugpp PRIVATE
|
||||||
|
$<$<CONFIG:Debug>:
|
||||||
|
-Wall -Wextra -Wpedantic -Werror
|
||||||
|
>
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(ueberzugpp PRIVATE "${PROJECT_BINARY_DIR}")
|
||||||
|
target_sources(ueberzugpp PRIVATE ${PLUGIN_SOURCES})
|
||||||
|
target_link_libraries(ueberzugpp
|
||||||
|
PkgConfig::WAYFIRE
|
||||||
|
PkgConfig::WLROOTS
|
||||||
|
PkgConfig::WFCONFIG)
|
||||||
150
plugins/wayfire/ueberzugpp.cpp
Normal file
150
plugins/wayfire/ueberzugpp.cpp
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <wayfire/core.hpp>
|
||||||
|
#include <wayfire/scene-render.hpp>
|
||||||
|
#include <wayfire/util.hpp>
|
||||||
|
#include <wayfire/view.hpp>
|
||||||
|
#include <wayfire/output.hpp>
|
||||||
|
#include <wayfire/plugin.hpp>
|
||||||
|
#include <wayfire/scene-operations.hpp>
|
||||||
|
#include <wayfire/plugins/ipc/ipc-method-repository.hpp>
|
||||||
|
#include <wayfire/plugins/common/shared-core-data.hpp>
|
||||||
|
#include <wayfire/unstable/wlr-view-events.hpp>
|
||||||
|
#include <wayfire/unstable/wlr-surface-node.hpp>
|
||||||
|
#include <wayfire/unstable/translation-node.hpp>
|
||||||
|
|
||||||
|
namespace wf
|
||||||
|
{
|
||||||
|
class ueberzugpp_surface_t
|
||||||
|
{
|
||||||
|
std::weak_ptr<wf::view_interface_t> terminal;
|
||||||
|
std::shared_ptr<scene::wlr_surface_node_t> surface;
|
||||||
|
std::shared_ptr<scene::translation_node_t> translation;
|
||||||
|
wlr_xdg_toplevel *toplevel;
|
||||||
|
|
||||||
|
wf::wl_listener_wrapper on_toplevel_destroy;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ueberzugpp_surface_t(const std::weak_ptr<wf::view_interface_t>& _terminal,
|
||||||
|
wlr_xdg_toplevel *_toplevel):
|
||||||
|
terminal(_terminal),
|
||||||
|
toplevel(_toplevel)
|
||||||
|
{
|
||||||
|
// We create a custom surface node for the ueberzugpp window and wrap it in a translation node, so that
|
||||||
|
// we can control its position.
|
||||||
|
// We add the translation node as a subsurface of the terminal.
|
||||||
|
surface = std::make_shared<scene::wlr_surface_node_t>(_toplevel->base->surface, true);
|
||||||
|
translation = std::make_shared<scene::translation_node_t>();
|
||||||
|
|
||||||
|
translation->set_children_list({surface});
|
||||||
|
|
||||||
|
auto term = _terminal.lock();
|
||||||
|
wf::scene::add_front(term->get_surface_root_node(), translation);
|
||||||
|
|
||||||
|
// Finally, wait for the toplevel to be destroyed
|
||||||
|
on_toplevel_destroy.set_callback([this] (auto)
|
||||||
|
{
|
||||||
|
destroy_callback();
|
||||||
|
});
|
||||||
|
on_toplevel_destroy.connect(&toplevel->base->events.destroy);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::function<void()> destroy_callback;
|
||||||
|
|
||||||
|
~ueberzugpp_surface_t()
|
||||||
|
{
|
||||||
|
wf::scene::remove_child(translation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_offset(int xcoord, int ycoord)
|
||||||
|
{
|
||||||
|
translation->set_offset({xcoord, ycoord});
|
||||||
|
const auto term = terminal.lock();
|
||||||
|
if (!term) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
term->damage();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class ueberzugpp_mapper : public wf::plugin_interface_t
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void init() override
|
||||||
|
{
|
||||||
|
ipc_repo->register_method("ueberzugpp/set_offset", set_offset);
|
||||||
|
wf::get_core().connect(&on_new_xdg_surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
void fini() override
|
||||||
|
{
|
||||||
|
ipc_repo->unregister_method("ueberzugpp/set_offset");
|
||||||
|
}
|
||||||
|
|
||||||
|
ipc::method_callback set_offset = [this] (nlohmann::json json)
|
||||||
|
{
|
||||||
|
// NOLINTBEGIN
|
||||||
|
WFJSON_EXPECT_FIELD(json, "app-id", string);
|
||||||
|
WFJSON_EXPECT_FIELD(json, "x", number_integer);
|
||||||
|
WFJSON_EXPECT_FIELD(json, "y", number_integer);
|
||||||
|
// NOLINTEND
|
||||||
|
|
||||||
|
const std::string app_id = json["app-id"];
|
||||||
|
const int xcoord = json["x"];
|
||||||
|
const int ycoord = json["y"];
|
||||||
|
|
||||||
|
const auto search = surfaces.find(app_id);
|
||||||
|
if (search == surfaces.end())
|
||||||
|
{
|
||||||
|
return ipc::json_error("Unknown ueberzugpp window with appid " + app_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
search->second->set_offset(xcoord, ycoord);
|
||||||
|
return ipc::json_ok();
|
||||||
|
};
|
||||||
|
|
||||||
|
shared_data::ref_ptr_t<ipc::method_repository_t> ipc_repo;
|
||||||
|
|
||||||
|
// When a new xdg_toplevel is created, we need to check whether it is an ueberzugpp window by looking at
|
||||||
|
// its app-id. If it is indeed an ueberzugpp window, we take over the toplevel (by setting
|
||||||
|
// use_default_implementation=false) and create our own ueberzugpp_surface. In addition, we directly map
|
||||||
|
// the surface to the currently focused view, if it exists.
|
||||||
|
wf::signal::connection_t<new_xdg_surface_signal> on_new_xdg_surface = [this] (new_xdg_surface_signal *event)
|
||||||
|
{
|
||||||
|
if (!event->use_default_implementation) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (event->surface->role != WLR_XDG_SURFACE_ROLE_TOPLEVEL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto* toplevel_title = event->surface->toplevel->title;
|
||||||
|
const std::string appid = toplevel_title != nullptr ? toplevel_title : "null";
|
||||||
|
const std::string search_for = "ueberzugpp_";
|
||||||
|
if (appid.find(search_for) != std::string::npos)
|
||||||
|
{
|
||||||
|
auto terminal = wf::get_core().get_active_output()->get_active_view();
|
||||||
|
if (!terminal)
|
||||||
|
{
|
||||||
|
LOGE("Which window to map ueberzugpp to????");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event->use_default_implementation = false;
|
||||||
|
const auto [iter, was_inserted] = surfaces.insert_or_assign(appid,
|
||||||
|
std::make_unique<ueberzugpp_surface_t>(terminal->weak_from_this(), event->surface->toplevel));
|
||||||
|
iter->second->destroy_callback = [this, appid]
|
||||||
|
{
|
||||||
|
surfaces.erase(appid);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<ueberzugpp_surface_t>> surfaces;
|
||||||
|
};
|
||||||
|
} // end namespace wf
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE
|
||||||
|
DECLARE_WAYFIRE_PLUGIN(wf::ueberzugpp_mapper);
|
||||||
45
scripts/fifo/fzf-fifo
Executable file
45
scripts/fifo/fzf-fifo
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This is a simple script that allows you to have image preview
|
||||||
|
# in fzf, using ueberzugpp with fifo. For this script to work
|
||||||
|
# you must add it to path, because it requires to call itself
|
||||||
|
# during the preview process in fzf (line 42 -> $0).
|
||||||
|
# Example usage:
|
||||||
|
# ls | fzf-fifo
|
||||||
|
# find $HOME/pix/ -type f -iname "*.jpg" | fzf-fifo
|
||||||
|
|
||||||
|
FIFO="/tmp/fzf_preview_fifo"
|
||||||
|
[ -p "$FIFO" ] || mkfifo "$FIFO"
|
||||||
|
|
||||||
|
start_ueberzugpp() {
|
||||||
|
ueberzugpp layer --silent <"$FIFO" &
|
||||||
|
exec 3>"${FIFO}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
exec 3>&-
|
||||||
|
}
|
||||||
|
trap cleanup HUP INT QUIT TERM EXIT
|
||||||
|
|
||||||
|
preview_image() {
|
||||||
|
echo '{"path": "'"$1"'", "action": "add", ''"identifier": "fzfpreview", '\
|
||||||
|
'"x": "'"$FZF_PREVIEW_LEFT"'", "y": "'"$FZF_PREVIEW_TOP"'", '\
|
||||||
|
'"width": "'"$FZF_PREVIEW_COLUMNS"'", "height": "'"$FZF_PREVIEW_LINES"'"}' \
|
||||||
|
>"$FIFO"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
-W)
|
||||||
|
shift
|
||||||
|
preview_image "$@"
|
||||||
|
exit
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
main() {
|
||||||
|
start_ueberzugpp
|
||||||
|
selected_image=$(fzf --preview "$0 -W {}" --preview-window=up:60%:wrap)
|
||||||
|
[ -n "$selected_image" ] && echo "Selected image: $selected_image"
|
||||||
|
rm "$FIFO"
|
||||||
|
}
|
||||||
|
main
|
||||||
31
scripts/fifo/img-fifo
Executable file
31
scripts/fifo/img-fifo
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This is a simple script that allows you to use ueberzugpp to
|
||||||
|
# preview images in the terminal by providing the x and y
|
||||||
|
# coordinates of the image, the width and height of the image,
|
||||||
|
# and the path to the image file, with $1, $2, $3, $4 and $5
|
||||||
|
# as arguments, respectively.
|
||||||
|
# Example usage:
|
||||||
|
# ./img-fifo 0 0 30 30 image.jpg
|
||||||
|
# Use Ctrl+C to exit.
|
||||||
|
|
||||||
|
FIFO="/tmp/preview_fifo"
|
||||||
|
[ -p "$FIFO" ] || mkfifo "$FIFO"
|
||||||
|
|
||||||
|
start_ueberzugpp() {
|
||||||
|
ueberzugpp layer --silent <"$FIFO" &
|
||||||
|
exec 3>"${FIFO}"
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
rm -f "$FIFO"
|
||||||
|
}
|
||||||
|
trap cleanup HUP INT QUIT TERM EXIT
|
||||||
|
|
||||||
|
preview_image() {
|
||||||
|
echo '{"path": "'"$5"'", "action": "add", "identifier": "img-fifo", "x": "'"$1"'", "y": "'"$2"'", "width": "'"$3"'", "height": "'"$4"'"}' >"$FIFO"
|
||||||
|
}
|
||||||
|
|
||||||
|
start_ueberzugpp
|
||||||
|
preview_image "$1" "$2" "$3" "$4" "$5"
|
||||||
|
wait
|
||||||
25
scripts/fzfub
Executable file
25
scripts/fzfub
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
case "$(uname -a)" in
|
||||||
|
*Darwin*) UEBERZUG_TMP_DIR="$TMPDIR" ;;
|
||||||
|
*) UEBERZUG_TMP_DIR="/tmp" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
ueberzugpp cmd -s "$SOCKET" -a exit
|
||||||
|
}
|
||||||
|
trap cleanup HUP INT QUIT TERM EXIT
|
||||||
|
|
||||||
|
UB_PID_FILE="$UEBERZUG_TMP_DIR/.$(uuidgen)"
|
||||||
|
ueberzugpp layer --no-stdin --silent --use-escape-codes --pid-file "$UB_PID_FILE"
|
||||||
|
UB_PID=$(cat "$UB_PID_FILE")
|
||||||
|
|
||||||
|
export SOCKET="$UEBERZUG_TMP_DIR"/ueberzugpp-"$UB_PID".socket
|
||||||
|
|
||||||
|
# run fzf with preview
|
||||||
|
fzf --reverse --preview="ueberzugpp cmd -s $SOCKET -i fzfpreview -a add \
|
||||||
|
-x \$FZF_PREVIEW_LEFT -y \$FZF_PREVIEW_TOP \
|
||||||
|
--max-width \$FZF_PREVIEW_COLUMNS --max-height \$FZF_PREVIEW_LINES \
|
||||||
|
-f {}"
|
||||||
|
|
||||||
|
ueberzugpp cmd -s "$SOCKET" -a exit
|
||||||
30
scripts/img
Executable file
30
scripts/img
Executable file
@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# This is a simple script that allows you to use ueberzugpp to
|
||||||
|
# preview images in the terminal by providing the x and y
|
||||||
|
# coordinates of the image, the width and height of the image,
|
||||||
|
# and the path to the image file, with $1, $2, $3, $4 and $5
|
||||||
|
# as arguments, respectively.
|
||||||
|
# Example usage:
|
||||||
|
# ./img 0 0 30 30 image.jpg
|
||||||
|
|
||||||
|
UB_PID=0
|
||||||
|
UB_SOCKET=""
|
||||||
|
|
||||||
|
case "$(uname -a)" in
|
||||||
|
*Darwin*) UEBERZUG_TMP_DIR="$TMPDIR" ;;
|
||||||
|
*) UEBERZUG_TMP_DIR="/tmp" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
ueberzugpp cmd -s "$UB_SOCKET" -a exit
|
||||||
|
}
|
||||||
|
trap cleanup HUP INT QUIT TERM EXIT
|
||||||
|
|
||||||
|
UB_PID_FILE="$UEBERZUG_TMP_DIR/.$(uuidgen)"
|
||||||
|
ueberzugpp layer --no-stdin --silent --use-escape-codes --pid-file "$UB_PID_FILE"
|
||||||
|
UB_PID="$(cat "$UB_PID_FILE")"
|
||||||
|
export UB_SOCKET="$UEBERZUG_TMP_DIR"/ueberzugpp-"$UB_PID".socket
|
||||||
|
ueberzugpp cmd -s "$UB_SOCKET" -a add -i PREVIEW -x "$1" -y "$2" --max-width "$3" --max-height "$4" -f "$5"
|
||||||
|
sleep 2
|
||||||
3
scripts/lf/cleaner
Executable file
3
scripts/lf/cleaner
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
ueberzugpp cmd -s $UB_SOCKET -a remove -i PREVIEW
|
||||||
34
scripts/lf/lfub
Executable file
34
scripts/lf/lfub
Executable file
@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This is a wrapper script for lf that allows it to create image previews with
|
||||||
|
# ueberzug. This works in concert with the lf configuration file and the
|
||||||
|
# lf-cleaner script.
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
UB_PID=0
|
||||||
|
UB_SOCKET=""
|
||||||
|
|
||||||
|
case "$(uname -a)" in
|
||||||
|
*Darwin*) UEBERZUG_TMP_DIR="$TMPDIR" ;;
|
||||||
|
*) UEBERZUG_TMP_DIR="/tmp" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
exec 3>&-
|
||||||
|
ueberzugpp cmd -s "$UB_SOCKET" -a exit
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -n "$SSH_CLIENT" ] || [ -n "$SSH_TTY" ]; then
|
||||||
|
lf "$@"
|
||||||
|
else
|
||||||
|
[ ! -d "$HOME/.cache/lf" ] && mkdir -p "$HOME/.cache/lf"
|
||||||
|
UB_PID_FILE="$UEBERZUG_TMP_DIR/.$(uuidgen)"
|
||||||
|
ueberzugpp layer --silent --no-stdin --use-escape-codes --pid-file "$UB_PID_FILE"
|
||||||
|
UB_PID=$(cat "$UB_PID_FILE")
|
||||||
|
rm "$UB_PID_FILE"
|
||||||
|
UB_SOCKET="$UEBERZUG_TMP_DIR/ueberzugpp-${UB_PID}.socket"
|
||||||
|
export UB_PID UB_SOCKET
|
||||||
|
trap cleanup HUP INT QUIT TERM EXIT
|
||||||
|
lf "$@" 3>&-
|
||||||
|
fi
|
||||||
65
scripts/lf/preview
Executable file
65
scripts/lf/preview
Executable file
@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
image() {
|
||||||
|
FILE_PATH="$1"
|
||||||
|
X=$4
|
||||||
|
Y=$5
|
||||||
|
MW=$(($2 - 1))
|
||||||
|
MH=$3
|
||||||
|
ueberzugpp cmd -s "$UB_SOCKET" -a add -i PREVIEW -x "$X" -y "$Y" --max-width "$MW" --max-height "$MH" -f "$FILE_PATH"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
batorcat() {
|
||||||
|
file="$1"
|
||||||
|
shift
|
||||||
|
if command -v bat >/dev/null 2>&1; then
|
||||||
|
bat --color=always --style=plain --pager=never "$file" "$@"
|
||||||
|
else
|
||||||
|
cat "$file"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
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}'))"
|
||||||
|
|
||||||
|
case "$(printf "%s\n" "$(readlink -f "$1")" | tr '[:upper:]' '[:lower:]')" in
|
||||||
|
*.tgz | *.tar.gz) tar tzf "$1" ;;
|
||||||
|
*.tar.bz2 | *.tbz2) tar tjf "$1" ;;
|
||||||
|
*.tar.txz | *.txz) xz --list "$1" ;;
|
||||||
|
*.tar) tar tf "$1" ;;
|
||||||
|
*.zip | *.jar | *.war | *.ear | *.oxt) unzip -l "$1" ;;
|
||||||
|
*.rar) unrar l "$1" ;;
|
||||||
|
*.7z) 7z l "$1" ;;
|
||||||
|
*.[1-8]) man "$1" | col -b ;;
|
||||||
|
*.o) nm "$1" ;;
|
||||||
|
*.torrent) transmission-show "$1" ;;
|
||||||
|
*.iso) iso-info --no-header -l "$1" ;;
|
||||||
|
*.odt | *.ods | *.odp | *.sxw) odt2txt "$1" ;;
|
||||||
|
*.doc) catdoc "$1" ;;
|
||||||
|
*.docx) docx2txt "$1" - ;;
|
||||||
|
*.xls | *.xlsx)
|
||||||
|
ssconvert --export-type=Gnumeric_stf:stf_csv "$1" "fd://1" | batorcat --language=csv
|
||||||
|
;;
|
||||||
|
*.wav | *.mp3 | *.flac | *.m4a | *.wma | *.ape | *.ac3 | *.og[agx] | *.spx | *.opus | *.as[fx] | *.mka)
|
||||||
|
exiftool "$1"
|
||||||
|
;;
|
||||||
|
*.pdf)
|
||||||
|
[ ! -f "${CACHE}.jpg" ] && pdftoppm -jpeg -f 1 -singlefile "$1" "$CACHE"
|
||||||
|
image "${CACHE}.jpg" "$2" "$3" "$4" "$5"
|
||||||
|
;;
|
||||||
|
*.avi | *.mp4 | *.wmv | *.dat | *.3gp | *.ogv | *.mkv | *.mpg | *.mpeg | *.vob | *.fl[icv] | *.m2v | *.mov | *.webm | *.ts | *.mts | *.m4v | *.r[am] | *.qt | *.divx)
|
||||||
|
[ ! -f "${CACHE}.jpg" ] && ffmpegthumbnailer -i "$1" -o "${CACHE}.jpg" -s 0 -q 5
|
||||||
|
image "${CACHE}.jpg" "$2" "$3" "$4" "$5"
|
||||||
|
;;
|
||||||
|
*.bmp | *.jpg | *.jpeg | *.png | *.xpm | *.webp | *.gif | *.jfif)
|
||||||
|
image "$1" "$2" "$3" "$4" "$5"
|
||||||
|
;;
|
||||||
|
*.svg)
|
||||||
|
[ ! -f "${CACHE}.jpg" ] && convert "$1" "${CACHE}.jpg"
|
||||||
|
image "${CACHE}.jpg" "$2" "$3" "$4" "$5"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
batorcat "$1"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
exit 0
|
||||||
32
scripts/sockets.py
Normal file
32
scripts/sockets.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import socket
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
cmd1 = """
|
||||||
|
{"action":"add","identifier":"preview","max_height":21,"max_width":118,"path":"/tmp/a.png","x":10,"y":15}
|
||||||
|
"""
|
||||||
|
|
||||||
|
cmd2 = """
|
||||||
|
{"action":"add","identifier":"preview","max_height":47,"max_width":118,"path":"/tmp/b.png","x":10,"y":15}
|
||||||
|
"""
|
||||||
|
|
||||||
|
cmd_exit = """
|
||||||
|
{"action":"remove","identifier":"preview"}
|
||||||
|
"""
|
||||||
|
|
||||||
|
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||||
|
connected = False
|
||||||
|
while not connected:
|
||||||
|
try:
|
||||||
|
s.connect(f"/tmp/ueberzugpp-30468.socket")
|
||||||
|
connected = True
|
||||||
|
except Exception:
|
||||||
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
s.sendall(str.encode(cmd1 + "\n"))
|
||||||
|
time.sleep(2)
|
||||||
|
s.sendall(str.encode(cmd_exit + "\n"))
|
||||||
|
s.sendall(str.encode(cmd2 + "\n"))
|
||||||
|
time.sleep(2)
|
||||||
|
s.sendall(str.encode(cmd_exit + "\n"))
|
||||||
|
s.close()
|
||||||
281
src/application.cpp
Normal file
281
src/application.cpp
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "application.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "tmux.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
#include "util/socket.hpp"
|
||||||
|
#include "version.hpp"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <spdlog/sinks/basic_file_sink.h>
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
|
using njson = nlohmann::json;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
Application::Application(const char *executable)
|
||||||
|
{
|
||||||
|
flags = Flags::instance();
|
||||||
|
print_header();
|
||||||
|
setup_logger();
|
||||||
|
set_silent();
|
||||||
|
terminal = std::make_unique<Terminal>();
|
||||||
|
if (flags->no_stdin) {
|
||||||
|
daemonize();
|
||||||
|
}
|
||||||
|
canvas = Canvas::create();
|
||||||
|
const auto cache_path = util::get_cache_path();
|
||||||
|
if (!fs::exists(cache_path) && !flags->no_cache) {
|
||||||
|
fs::create_directories(cache_path);
|
||||||
|
}
|
||||||
|
tmux::register_hooks();
|
||||||
|
socket_thread = std::thread([this] {
|
||||||
|
const auto sock_path = util::get_socket_path();
|
||||||
|
logger->info("Listening for commands on socket {}", sock_path);
|
||||||
|
socket_loop();
|
||||||
|
});
|
||||||
|
if (flags->no_cache) {
|
||||||
|
logger->info("Image caching is disabled");
|
||||||
|
}
|
||||||
|
if (VIPS_INIT(executable)) {
|
||||||
|
vips_error_exit(nullptr);
|
||||||
|
}
|
||||||
|
vips_cache_set_max(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Application::~Application()
|
||||||
|
{
|
||||||
|
if (socket_thread.joinable()) {
|
||||||
|
socket_thread.join();
|
||||||
|
}
|
||||||
|
logger->info("Exiting ueberzugpp");
|
||||||
|
canvas.reset();
|
||||||
|
vips_shutdown();
|
||||||
|
tmux::unregister_hooks();
|
||||||
|
fs::remove(util::get_socket_path());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::execute(const std::string_view cmd)
|
||||||
|
{
|
||||||
|
if (!canvas) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
njson json;
|
||||||
|
try {
|
||||||
|
json = njson::parse(cmd);
|
||||||
|
} catch (const njson::parse_error &e) {
|
||||||
|
logger->error("Command received is not valid json");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto json_str = json.dump();
|
||||||
|
logger->info("Command received: {}", json_str);
|
||||||
|
|
||||||
|
const std::string &action = json.at("action");
|
||||||
|
if (action == "tmux") {
|
||||||
|
const std::string &hook = json.at("hook");
|
||||||
|
handle_tmux_hook(hook);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string &identifier = json.at("identifier");
|
||||||
|
if (action == "add") {
|
||||||
|
if (!json.at("path").is_string()) {
|
||||||
|
logger->error("Path received is not valid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto image = Image::load(json, terminal.get());
|
||||||
|
if (!image) {
|
||||||
|
logger->error("Unable to load image file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
canvas->add_image(identifier, std::move(image));
|
||||||
|
} else if (action == "remove") {
|
||||||
|
canvas->remove_image(identifier);
|
||||||
|
} else {
|
||||||
|
logger->warn("Command not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::handle_tmux_hook(const std::string_view hook)
|
||||||
|
{
|
||||||
|
const std::unordered_map<std::string_view, std::function<void()>> hook_fns{
|
||||||
|
{"client-session-changed",
|
||||||
|
[this] {
|
||||||
|
if (tmux::is_window_focused()) {
|
||||||
|
canvas->show();
|
||||||
|
} else {
|
||||||
|
canvas->hide();
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{"session-window-changed",
|
||||||
|
[this] {
|
||||||
|
if (tmux::is_window_focused()) {
|
||||||
|
canvas->show();
|
||||||
|
} else {
|
||||||
|
canvas->hide();
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
{"client-detached", [this] { canvas->hide(); }},
|
||||||
|
{"window-layout-changed",
|
||||||
|
[this] {
|
||||||
|
if (tmux::is_window_focused()) {
|
||||||
|
canvas->hide();
|
||||||
|
}
|
||||||
|
}},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
hook_fns.at(hook)();
|
||||||
|
} catch (const std::out_of_range &oor) {
|
||||||
|
logger->warn("TMUX hook not recognized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::setup_logger()
|
||||||
|
{
|
||||||
|
const auto log_tmp = util::get_log_filename();
|
||||||
|
const auto log_path = util::temp_directory_path() / log_tmp;
|
||||||
|
try {
|
||||||
|
spdlog::flush_on(spdlog::level::debug);
|
||||||
|
const auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(log_path);
|
||||||
|
|
||||||
|
const auto main_logger = std::make_shared<spdlog::logger>("main", sink);
|
||||||
|
const auto terminal_logger = std::make_shared<spdlog::logger>("terminal", sink);
|
||||||
|
const auto cv_logger = std::make_shared<spdlog::logger>("opencv", sink);
|
||||||
|
const auto vips_logger = std::make_shared<spdlog::logger>("vips", sink);
|
||||||
|
const auto x11_logger = std::make_shared<spdlog::logger>("X11", sink);
|
||||||
|
const auto sixel_logger = std::make_shared<spdlog::logger>("sixel", sink);
|
||||||
|
const auto kitty_logger = std::make_shared<spdlog::logger>("kitty", sink);
|
||||||
|
const auto iterm2_logger = std::make_shared<spdlog::logger>("iterm2", sink);
|
||||||
|
const auto chafa_logger = std::make_shared<spdlog::logger>("chafa", sink);
|
||||||
|
const auto wayland_logger = std::make_shared<spdlog::logger>("wayland", sink);
|
||||||
|
const auto opengl_logger = std::make_shared<spdlog::logger>("opengl", sink);
|
||||||
|
|
||||||
|
spdlog::initialize_logger(main_logger);
|
||||||
|
spdlog::initialize_logger(terminal_logger);
|
||||||
|
spdlog::initialize_logger(cv_logger);
|
||||||
|
spdlog::initialize_logger(vips_logger);
|
||||||
|
spdlog::initialize_logger(x11_logger);
|
||||||
|
spdlog::initialize_logger(sixel_logger);
|
||||||
|
spdlog::initialize_logger(kitty_logger);
|
||||||
|
spdlog::initialize_logger(iterm2_logger);
|
||||||
|
spdlog::initialize_logger(chafa_logger);
|
||||||
|
spdlog::initialize_logger(wayland_logger);
|
||||||
|
spdlog::initialize_logger(opengl_logger);
|
||||||
|
|
||||||
|
logger = spdlog::get("main");
|
||||||
|
} catch (const spdlog::spdlog_ex &ex) {
|
||||||
|
std::cout << "Log init failed: " << ex.what() << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::command_loop()
|
||||||
|
{
|
||||||
|
if (flags->no_stdin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (!stop_flag) {
|
||||||
|
try {
|
||||||
|
const auto in_event = os::wait_for_data_on_stdin(100);
|
||||||
|
if (!in_event) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto cmd = os::read_data_from_stdin();
|
||||||
|
execute(cmd);
|
||||||
|
} catch (const std::system_error &err) {
|
||||||
|
stop_flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::socket_loop()
|
||||||
|
{
|
||||||
|
UnixSocket socket;
|
||||||
|
socket.bind_to_endpoint(util::get_socket_path());
|
||||||
|
|
||||||
|
const int waitms = 100;
|
||||||
|
int conn = -1;
|
||||||
|
while (!stop_flag) {
|
||||||
|
try {
|
||||||
|
conn = socket.wait_for_connections(waitms);
|
||||||
|
} catch (const std::system_error &err) {
|
||||||
|
stop_flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conn == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto data = socket.read_data_from_connection(conn);
|
||||||
|
for (const auto &cmd : data) {
|
||||||
|
if (cmd == "EXIT") {
|
||||||
|
stop_flag = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
execute(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::print_header()
|
||||||
|
{
|
||||||
|
const auto log_tmp = util::get_log_filename();
|
||||||
|
const auto log_path = util::temp_directory_path() / log_tmp;
|
||||||
|
const auto art = fmt::format(R"(
|
||||||
|
_ _ _
|
||||||
|
| | | | | | _ _
|
||||||
|
| | | | ___| |__ ___ _ __ _____ _ __ _ _| |_ _| |_
|
||||||
|
| | | |/ _ \ '_ \ / _ \ '__|_ / | | |/ _` |_ _|_ _|
|
||||||
|
| |_| | __/ |_) | __/ | / /| |_| | (_| | |_| |_|
|
||||||
|
\___/ \___|_.__/ \___|_| /___|\__,_|\__, |
|
||||||
|
__/ |
|
||||||
|
|___/ v{}.{}.{})",
|
||||||
|
ueberzugpp_VERSION_MAJOR, ueberzugpp_VERSION_MINOR, ueberzugpp_VERSION_PATCH);
|
||||||
|
std::ofstream ofs(log_path, std::ios::out | std::ios::app);
|
||||||
|
ofs << art << '\n' << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::set_silent()
|
||||||
|
{
|
||||||
|
if (!flags->silent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
f_stderr.reset(std::freopen("/dev/null", "w", stderr));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::print_version()
|
||||||
|
{
|
||||||
|
const auto ver_str = fmt::format("ueberzugpp {}.{}.{}", ueberzugpp_VERSION_MAJOR, ueberzugpp_VERSION_MINOR,
|
||||||
|
ueberzugpp_VERSION_PATCH);
|
||||||
|
std::cout << ver_str << '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::daemonize()
|
||||||
|
{
|
||||||
|
os::daemonize();
|
||||||
|
std::ofstream ofs(flags->pid_file);
|
||||||
|
ofs << os::get_pid() << std::flush;
|
||||||
|
}
|
||||||
64
src/canvas.cpp
Normal file
64
src/canvas.cpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "canvas/chafa.hpp"
|
||||||
|
#include "canvas/iterm2/iterm2.hpp"
|
||||||
|
#include "canvas/kitty/kitty.hpp"
|
||||||
|
#include "canvas/sixel.hpp"
|
||||||
|
#include "canvas/stdout.hpp"
|
||||||
|
#ifdef ENABLE_X11
|
||||||
|
# include "canvas/x11/x11.hpp"
|
||||||
|
#endif
|
||||||
|
#ifdef ENABLE_WAYLAND
|
||||||
|
# include "canvas/wayland/wayland.hpp"
|
||||||
|
#endif
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
auto Canvas::create() -> std::unique_ptr<Canvas>
|
||||||
|
{
|
||||||
|
const auto flags = Flags::instance();
|
||||||
|
const auto logger = spdlog::get("main");
|
||||||
|
|
||||||
|
#ifdef ENABLE_WAYLAND
|
||||||
|
if (flags->output == "wayland") {
|
||||||
|
return std::make_unique<WaylandCanvas>();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
logger->debug("Wayland support not compiled in the binary");
|
||||||
|
#endif
|
||||||
|
#ifdef ENABLE_X11
|
||||||
|
if (flags->output == "x11") {
|
||||||
|
return std::make_unique<X11Canvas>();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
logger->debug("X11 support not compiled in the binary");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (flags->output == "kitty") {
|
||||||
|
return std::make_unique<StdoutCanvas<Kitty>>(flags->output);
|
||||||
|
}
|
||||||
|
if (flags->output == "iterm2") {
|
||||||
|
return std::make_unique<StdoutCanvas<Iterm2>>(flags->output);
|
||||||
|
}
|
||||||
|
if (flags->output == "sixel") {
|
||||||
|
return std::make_unique<StdoutCanvas<Sixel>>(flags->output);
|
||||||
|
}
|
||||||
|
if (flags->output == "chafa") {
|
||||||
|
return std::make_unique<StdoutCanvas<Chafa>>(flags->output);
|
||||||
|
}
|
||||||
|
throw std::runtime_error(fmt::format("output backend not supported (backend is {})", flags->output));
|
||||||
|
}
|
||||||
104
src/canvas/chafa.cpp
Normal file
104
src/canvas/chafa.cpp
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "chafa.hpp"
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "terminal.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
#include "util/ptr.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <range/v3/all.hpp>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
void gstring_delete(GString *str)
|
||||||
|
{
|
||||||
|
g_string_free(str, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Chafa::Chafa(std::unique_ptr<Image> new_image, std::mutex *stdout_mutex)
|
||||||
|
: symbol_map(chafa_symbol_map_new()),
|
||||||
|
config(chafa_canvas_config_new()),
|
||||||
|
image(std::move(new_image)),
|
||||||
|
stdout_mutex(stdout_mutex)
|
||||||
|
{
|
||||||
|
const auto envp = c_unique_ptr<gchar *, g_strfreev>{g_get_environ()};
|
||||||
|
term_info = chafa_term_db_detect(chafa_term_db_get_default(), envp.get());
|
||||||
|
|
||||||
|
const auto dims = image->dimensions();
|
||||||
|
x = dims.x + 1;
|
||||||
|
y = dims.y + 1;
|
||||||
|
horizontal_cells = std::ceil(static_cast<double>(image->width()) / dims.terminal->font_width);
|
||||||
|
vertical_cells = std::ceil(static_cast<double>(image->height()) / dims.terminal->font_height);
|
||||||
|
|
||||||
|
chafa_symbol_map_add_by_tags(symbol_map, CHAFA_SYMBOL_TAG_BLOCK);
|
||||||
|
chafa_symbol_map_add_by_tags(symbol_map, CHAFA_SYMBOL_TAG_BORDER);
|
||||||
|
chafa_symbol_map_add_by_tags(symbol_map, CHAFA_SYMBOL_TAG_SPACE);
|
||||||
|
chafa_symbol_map_remove_by_tags(symbol_map, CHAFA_SYMBOL_TAG_WIDE);
|
||||||
|
chafa_canvas_config_set_symbol_map(config, symbol_map);
|
||||||
|
chafa_canvas_config_set_pixel_mode(config, CHAFA_PIXEL_MODE_SYMBOLS);
|
||||||
|
chafa_canvas_config_set_geometry(config, horizontal_cells, vertical_cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
Chafa::~Chafa()
|
||||||
|
{
|
||||||
|
chafa_canvas_unref(canvas);
|
||||||
|
chafa_canvas_config_unref(config);
|
||||||
|
chafa_symbol_map_unref(symbol_map);
|
||||||
|
chafa_term_info_unref(term_info);
|
||||||
|
|
||||||
|
const std::scoped_lock lock{*stdout_mutex};
|
||||||
|
util::clear_terminal_area(x, y, horizontal_cells, vertical_cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Chafa::draw()
|
||||||
|
{
|
||||||
|
canvas = chafa_canvas_new(config);
|
||||||
|
chafa_canvas_draw_all_pixels(canvas, CHAFA_PIXEL_BGRA8_UNASSOCIATED, image->data(), image->width(), image->height(),
|
||||||
|
image->width() * 4);
|
||||||
|
|
||||||
|
#ifdef CHAFA_VERSION_1_14
|
||||||
|
GString **lines = nullptr;
|
||||||
|
gint lines_length = 0;
|
||||||
|
|
||||||
|
chafa_canvas_print_rows(canvas, term_info, &lines, &lines_length);
|
||||||
|
auto ycoord = y;
|
||||||
|
const std::scoped_lock lock{*stdout_mutex};
|
||||||
|
util::save_cursor_position();
|
||||||
|
for (int i = 0; i < lines_length; ++i) {
|
||||||
|
const auto line = c_unique_ptr<GString, gstring_delete>{lines[i]};
|
||||||
|
util::move_cursor(ycoord++, x);
|
||||||
|
std::cout << line->str;
|
||||||
|
}
|
||||||
|
g_free(lines);
|
||||||
|
#else
|
||||||
|
const auto result = c_unique_ptr<GString, gstring_delete>{chafa_canvas_print(canvas, term_info)};
|
||||||
|
auto ycoord = y;
|
||||||
|
const auto lines = util::str_split(result->str, "\n");
|
||||||
|
|
||||||
|
const std::scoped_lock lock{*stdout_mutex};
|
||||||
|
util::save_cursor_position();
|
||||||
|
ranges::for_each(lines, [this, &ycoord](const std::string &line) {
|
||||||
|
util::move_cursor(ycoord++, x);
|
||||||
|
std::cout << line;
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
std::cout << std::flush;
|
||||||
|
util::restore_cursor_position();
|
||||||
|
}
|
||||||
52
src/canvas/chafa.hpp
Normal file
52
src/canvas/chafa.hpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef CHAFA_WINDOW_H
|
||||||
|
#define CHAFA_WINDOW_H
|
||||||
|
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <chafa.h>
|
||||||
|
|
||||||
|
class Chafa : public Window
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Chafa(std::unique_ptr<Image> new_image, std::mutex *stdout_mutex);
|
||||||
|
~Chafa() override;
|
||||||
|
|
||||||
|
void draw() override;
|
||||||
|
void generate_frame() override{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
ChafaTermInfo *term_info = nullptr;
|
||||||
|
ChafaSymbolMap *symbol_map = nullptr;
|
||||||
|
ChafaCanvasConfig *config = nullptr;
|
||||||
|
ChafaCanvas *canvas = nullptr;
|
||||||
|
|
||||||
|
std::unique_ptr<Image> image;
|
||||||
|
std::mutex *stdout_mutex;
|
||||||
|
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int horizontal_cells = 0;
|
||||||
|
int vertical_cells = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
56
src/canvas/iterm2/chunk.cpp
Normal file
56
src/canvas/iterm2/chunk.cpp
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "chunk.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
Iterm2Chunk::Iterm2Chunk(uint64_t size)
|
||||||
|
{
|
||||||
|
auto bufsize = 4*((size+2)/3);
|
||||||
|
buffer.resize(size, 0);
|
||||||
|
result.resize(bufsize + 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Iterm2Chunk::set_size(uint64_t size)
|
||||||
|
{
|
||||||
|
this->size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Iterm2Chunk::get_size() const -> uint64_t
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Iterm2Chunk::get_buffer() -> char*
|
||||||
|
{
|
||||||
|
return buffer.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Iterm2Chunk::get_result() -> char*
|
||||||
|
{
|
||||||
|
return result.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Iterm2Chunk::process_chunk(std::unique_ptr<Iterm2Chunk>& chunk)
|
||||||
|
{
|
||||||
|
util::base64_encode_v2(reinterpret_cast<unsigned char*>(chunk->get_buffer()),
|
||||||
|
chunk->get_size(), reinterpret_cast<unsigned char*>(chunk->get_result()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Iterm2Chunk::operator()(std::unique_ptr<Iterm2Chunk>& chunk) const
|
||||||
|
{
|
||||||
|
process_chunk(chunk);
|
||||||
|
}
|
||||||
45
src/canvas/iterm2/chunk.hpp
Normal file
45
src/canvas/iterm2/chunk.hpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef ITERM2_CHUNK_H
|
||||||
|
#define ITERM2_CHUNK_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class Iterm2Chunk
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Iterm2Chunk() = default;
|
||||||
|
explicit Iterm2Chunk(uint64_t size);
|
||||||
|
|
||||||
|
void operator()(std::unique_ptr<Iterm2Chunk>& chunk) const;
|
||||||
|
void static process_chunk(std::unique_ptr<Iterm2Chunk>& chunk);
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_size() const -> uint64_t;
|
||||||
|
void set_size(uint64_t size);
|
||||||
|
auto get_buffer() -> char*;
|
||||||
|
auto get_result() -> char*;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint64_t size;
|
||||||
|
std::vector<char> buffer;
|
||||||
|
std::vector<char> result;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
108
src/canvas/iterm2/iterm2.cpp
Normal file
108
src/canvas/iterm2/iterm2.cpp
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "iterm2.hpp"
|
||||||
|
#include "chunk.hpp"
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "terminal.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#ifdef HAVE_STD_EXECUTION_H
|
||||||
|
# include <execution>
|
||||||
|
#else
|
||||||
|
# include <oneapi/tbb.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <range/v3/all.hpp>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
Iterm2::Iterm2(std::unique_ptr<Image> new_image, std::mutex *stdout_mutex)
|
||||||
|
: image(std::move(new_image)),
|
||||||
|
stdout_mutex(stdout_mutex)
|
||||||
|
{
|
||||||
|
const auto dims = image->dimensions();
|
||||||
|
x = dims.x + 1;
|
||||||
|
y = dims.y + 1;
|
||||||
|
horizontal_cells = std::ceil(static_cast<double>(image->width()) / dims.terminal->font_width);
|
||||||
|
vertical_cells = std::ceil(static_cast<double>(image->height()) / dims.terminal->font_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterm2::~Iterm2()
|
||||||
|
{
|
||||||
|
const std::scoped_lock lock{*stdout_mutex};
|
||||||
|
util::clear_terminal_area(x, y, horizontal_cells, vertical_cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Iterm2::draw()
|
||||||
|
{
|
||||||
|
str.append("\033]1337;File=inline=1;");
|
||||||
|
const auto filename = image->filename();
|
||||||
|
const auto num_bytes = fs::file_size(filename);
|
||||||
|
const auto encoded_filename =
|
||||||
|
util::base64_encode(reinterpret_cast<const unsigned char *>(filename.c_str()), filename.size());
|
||||||
|
str.append(fmt::format("size={};name={};width={}px;height={}px:", num_bytes, encoded_filename, image->width(),
|
||||||
|
image->height()));
|
||||||
|
|
||||||
|
const int chunk_size = 1023;
|
||||||
|
const auto chunks = process_chunks(filename, chunk_size, num_bytes);
|
||||||
|
const int num_chunks = std::ceil(static_cast<double>(num_bytes) / chunk_size);
|
||||||
|
const uint64_t bytes_per_chunk = 4 * ((chunk_size + 2) / 3) + 100;
|
||||||
|
str.reserve((num_chunks + 2) * bytes_per_chunk);
|
||||||
|
|
||||||
|
ranges::for_each(chunks, [this](const std::unique_ptr<Iterm2Chunk> &chunk) { str.append(chunk->get_result()); });
|
||||||
|
str.append("\a");
|
||||||
|
|
||||||
|
const std::scoped_lock lock{*stdout_mutex};
|
||||||
|
util::save_cursor_position();
|
||||||
|
util::move_cursor(y, x);
|
||||||
|
std::cout << str << std::flush;
|
||||||
|
util::restore_cursor_position();
|
||||||
|
str.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Iterm2::process_chunks(const std::string &filename, int chunk_size, size_t num_bytes)
|
||||||
|
-> std::vector<std::unique_ptr<Iterm2Chunk>>
|
||||||
|
{
|
||||||
|
const int num_chunks = std::ceil(static_cast<double>(num_bytes) / chunk_size);
|
||||||
|
std::vector<std::unique_ptr<Iterm2Chunk>> chunks;
|
||||||
|
chunks.reserve(num_chunks + 2);
|
||||||
|
|
||||||
|
std::ifstream ifs(filename);
|
||||||
|
while (ifs.good()) {
|
||||||
|
auto chunk = std::make_unique<Iterm2Chunk>(chunk_size);
|
||||||
|
ifs.read(chunk->get_buffer(), chunk_size);
|
||||||
|
chunk->set_size(ifs.gcount());
|
||||||
|
chunks.push_back(std::move(chunk));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HAVE_STD_EXECUTION_H
|
||||||
|
std::for_each(std::execution::par_unseq, chunks.begin(), chunks.end(), Iterm2Chunk::process_chunk);
|
||||||
|
#else
|
||||||
|
oneapi::tbb::parallel_for_each(chunks.begin(), chunks.end(), Iterm2Chunk());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
51
src/canvas/iterm2/iterm2.hpp
Normal file
51
src/canvas/iterm2/iterm2.hpp
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef ITERM2_CANVAS_H
|
||||||
|
#define ITERM2_CANVAS_H
|
||||||
|
|
||||||
|
#include "chunk.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class Iterm2 : public Window
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Iterm2(std::unique_ptr<Image> new_image, std::mutex *stdout_mutex);
|
||||||
|
~Iterm2() override;
|
||||||
|
|
||||||
|
void draw() override;
|
||||||
|
void generate_frame() override{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Image> image;
|
||||||
|
std::mutex *stdout_mutex;
|
||||||
|
std::string str;
|
||||||
|
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int horizontal_cells = 0;
|
||||||
|
int vertical_cells = 0;
|
||||||
|
|
||||||
|
static auto process_chunks(const std::string &filename, int chunk_size, size_t num_bytes)
|
||||||
|
-> std::vector<std::unique_ptr<Iterm2Chunk>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
52
src/canvas/kitty/chunk.cpp
Normal file
52
src/canvas/kitty/chunk.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "chunk.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
KittyChunk::KittyChunk(const unsigned char* ptr, uint64_t size):
|
||||||
|
ptr(ptr),
|
||||||
|
size(size)
|
||||||
|
{
|
||||||
|
uint64_t bufsize = 4*((size+2)/3);
|
||||||
|
result.resize(bufsize + 1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KittyChunk::process_chunk(KittyChunk& chunk)
|
||||||
|
{
|
||||||
|
util::base64_encode_v2(chunk.get_ptr(), chunk.get_size(),
|
||||||
|
reinterpret_cast<unsigned char*>(chunk.get_result()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void KittyChunk::operator()(KittyChunk& chunk) const
|
||||||
|
{
|
||||||
|
process_chunk(chunk);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto KittyChunk::get_result() -> char*
|
||||||
|
{
|
||||||
|
return result.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto KittyChunk::get_ptr() const -> const unsigned char*
|
||||||
|
{
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto KittyChunk::get_size() const -> uint64_t
|
||||||
|
{
|
||||||
|
return size;
|
||||||
|
}
|
||||||
42
src/canvas/kitty/chunk.hpp
Normal file
42
src/canvas/kitty/chunk.hpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef KITTY_CHUNK_H
|
||||||
|
#define KITTY_CHUNK_H
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
class KittyChunk
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
KittyChunk() = default;
|
||||||
|
KittyChunk(const unsigned char* ptr, uint64_t size);
|
||||||
|
|
||||||
|
void operator()(KittyChunk& chunk) const;
|
||||||
|
|
||||||
|
auto get_result() -> char*;
|
||||||
|
[[nodiscard]] auto get_ptr() const -> const unsigned char*;
|
||||||
|
[[nodiscard]] auto get_size() const -> uint64_t;
|
||||||
|
|
||||||
|
static void process_chunk(KittyChunk& chunk);
|
||||||
|
private:
|
||||||
|
const unsigned char* ptr;
|
||||||
|
uint64_t size;
|
||||||
|
std::vector<char> result;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
106
src/canvas/kitty/kitty.cpp
Normal file
106
src/canvas/kitty/kitty.cpp
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "kitty.hpp"
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#ifdef HAVE_STD_EXECUTION_H
|
||||||
|
# include <execution>
|
||||||
|
#else
|
||||||
|
# include <oneapi/tbb.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Kitty::Kitty(std::unique_ptr<Image> new_image, std::mutex *stdout_mutex)
|
||||||
|
: image(std::move(new_image)),
|
||||||
|
stdout_mutex(stdout_mutex),
|
||||||
|
id(util::generate_random_number<uint32_t>(1))
|
||||||
|
{
|
||||||
|
const auto dims = image->dimensions();
|
||||||
|
x = dims.x + 1;
|
||||||
|
y = dims.y + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Kitty::~Kitty()
|
||||||
|
{
|
||||||
|
const std::scoped_lock lock{*stdout_mutex};
|
||||||
|
std::cout << fmt::format("\033_Ga=d,d=i,i={}\033\\", id) << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kitty::draw()
|
||||||
|
{
|
||||||
|
generate_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Kitty::generate_frame()
|
||||||
|
{
|
||||||
|
const int bits_per_channel = 8;
|
||||||
|
auto chunks = process_chunks();
|
||||||
|
str.append(fmt::format("\033_Ga=T,m=1,i={},q=2,f={},s={},v={};{}\033\\", id, image->channels() * bits_per_channel,
|
||||||
|
image->width(), image->height(), chunks.front().get_result()));
|
||||||
|
|
||||||
|
for (auto chunk = std::next(std::begin(chunks)); chunk != std::prev(std::end(chunks)); std::advance(chunk, 1)) {
|
||||||
|
str.append("\033_Gm=1,q=2;");
|
||||||
|
str.append(chunk->get_result());
|
||||||
|
str.append("\033\\");
|
||||||
|
}
|
||||||
|
|
||||||
|
str.append("\033_Gm=0,q=2;");
|
||||||
|
str.append(chunks.back().get_result());
|
||||||
|
str.append("\033\\");
|
||||||
|
|
||||||
|
const std::scoped_lock lock{*stdout_mutex};
|
||||||
|
util::save_cursor_position();
|
||||||
|
util::move_cursor(y, x);
|
||||||
|
std::cout << str << std::flush;
|
||||||
|
util::restore_cursor_position();
|
||||||
|
str.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Kitty::process_chunks() -> std::vector<KittyChunk>
|
||||||
|
{
|
||||||
|
const uint64_t chunk_size = 3068;
|
||||||
|
uint64_t num_chunks = image->size() / chunk_size;
|
||||||
|
uint64_t last_chunk_size = image->size() % chunk_size;
|
||||||
|
if (last_chunk_size == 0) {
|
||||||
|
last_chunk_size = chunk_size;
|
||||||
|
num_chunks--;
|
||||||
|
}
|
||||||
|
const uint64_t bytes_per_chunk = 4 * ((chunk_size + 2) / 3) + 100;
|
||||||
|
str.reserve((num_chunks + 2) * bytes_per_chunk);
|
||||||
|
|
||||||
|
std::vector<KittyChunk> chunks;
|
||||||
|
chunks.reserve(num_chunks + 2);
|
||||||
|
const auto *ptr = image->data();
|
||||||
|
|
||||||
|
uint64_t idx = 0;
|
||||||
|
for (; idx < num_chunks; idx++) {
|
||||||
|
chunks.emplace_back(ptr + idx * chunk_size, chunk_size);
|
||||||
|
}
|
||||||
|
chunks.emplace_back(ptr + idx * chunk_size, last_chunk_size);
|
||||||
|
|
||||||
|
#ifdef HAVE_STD_EXECUTION_H
|
||||||
|
std::for_each(std::execution::par_unseq, std::begin(chunks), std::end(chunks), KittyChunk::process_chunk);
|
||||||
|
#else
|
||||||
|
oneapi::tbb::parallel_for_each(std::begin(chunks), std::end(chunks), KittyChunk());
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return chunks;
|
||||||
|
}
|
||||||
48
src/canvas/kitty/kitty.hpp
Normal file
48
src/canvas/kitty/kitty.hpp
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef KITTY_WINDOW_H
|
||||||
|
#define KITTY_WINDOW_H
|
||||||
|
|
||||||
|
#include "chunk.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class Kitty : public Window
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Kitty(std::unique_ptr<Image> new_image, std::mutex *stdout_mutex);
|
||||||
|
~Kitty() override;
|
||||||
|
|
||||||
|
void draw() override;
|
||||||
|
void generate_frame() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string str;
|
||||||
|
std::unique_ptr<Image> image;
|
||||||
|
std::mutex *stdout_mutex;
|
||||||
|
uint32_t id;
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
|
||||||
|
auto process_chunks() -> std::vector<KittyChunk>;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
96
src/canvas/sixel.cpp
Normal file
96
src/canvas/sixel.cpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "sixel.hpp"
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "terminal.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
Sixel::Sixel(std::unique_ptr<Image> new_image, std::mutex *stdout_mutex)
|
||||||
|
: image(std::move(new_image)),
|
||||||
|
stdout_mutex(stdout_mutex)
|
||||||
|
{
|
||||||
|
const auto dims = image->dimensions();
|
||||||
|
x = dims.x + 1;
|
||||||
|
y = dims.y + 1;
|
||||||
|
horizontal_cells = std::ceil(static_cast<double>(image->width()) / dims.terminal->font_width);
|
||||||
|
vertical_cells = std::ceil(static_cast<double>(image->height()) / dims.terminal->font_height);
|
||||||
|
|
||||||
|
const auto draw_callback = [](char *data, int size, void *priv) -> int {
|
||||||
|
auto *str = static_cast<std::string *>(priv);
|
||||||
|
str->append(data, size);
|
||||||
|
return size;
|
||||||
|
};
|
||||||
|
sixel_output_new(&output, draw_callback, &str, nullptr);
|
||||||
|
|
||||||
|
const auto file_size = fs::file_size(image->filename());
|
||||||
|
constexpr auto reserve_ratio = 50;
|
||||||
|
str.reserve(file_size * reserve_ratio);
|
||||||
|
|
||||||
|
// create dither and palette from image
|
||||||
|
sixel_dither_new(&dither, -1, nullptr);
|
||||||
|
sixel_dither_initialize(dither, const_cast<unsigned char *>(image->data()), image->width(), image->height(),
|
||||||
|
SIXEL_PIXELFORMAT_RGB888, SIXEL_LARGE_LUM, SIXEL_REP_CENTER_BOX, SIXEL_QUALITY_HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sixel::~Sixel()
|
||||||
|
{
|
||||||
|
can_draw.store(false);
|
||||||
|
if (draw_thread.joinable()) {
|
||||||
|
draw_thread.join();
|
||||||
|
}
|
||||||
|
sixel_dither_destroy(dither);
|
||||||
|
sixel_output_destroy(output);
|
||||||
|
|
||||||
|
const std::scoped_lock lock{*stdout_mutex};
|
||||||
|
util::clear_terminal_area(x, y, horizontal_cells, vertical_cells);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sixel::draw()
|
||||||
|
{
|
||||||
|
if (!image->is_animated()) {
|
||||||
|
generate_frame();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// start drawing loop
|
||||||
|
draw_thread = std::thread([this] {
|
||||||
|
while (can_draw.load()) {
|
||||||
|
generate_frame();
|
||||||
|
image->next_frame();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(image->frame_delay()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sixel::generate_frame()
|
||||||
|
{
|
||||||
|
// output sixel content to stream
|
||||||
|
sixel_encode(const_cast<unsigned char *>(image->data()), image->width(), image->height(), 3 /*unused*/, dither,
|
||||||
|
output);
|
||||||
|
|
||||||
|
const std::scoped_lock lock{*stdout_mutex};
|
||||||
|
util::save_cursor_position();
|
||||||
|
util::move_cursor(y, x);
|
||||||
|
std::cout << str << std::flush;
|
||||||
|
util::restore_cursor_position();
|
||||||
|
str.clear();
|
||||||
|
}
|
||||||
57
src/canvas/sixel.hpp
Normal file
57
src/canvas/sixel.hpp
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef SIXEL_WINDOW_H
|
||||||
|
#define SIXEL_WINDOW_H
|
||||||
|
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <sixel.h>
|
||||||
|
|
||||||
|
class Sixel : public Window
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Sixel(std::unique_ptr<Image> new_image, std::mutex *stdout_mutex);
|
||||||
|
~Sixel() override;
|
||||||
|
|
||||||
|
void draw() override;
|
||||||
|
void generate_frame() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<Image> image;
|
||||||
|
std::mutex *stdout_mutex;
|
||||||
|
|
||||||
|
std::string str;
|
||||||
|
std::thread draw_thread;
|
||||||
|
std::atomic<bool> can_draw{true};
|
||||||
|
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
int horizontal_cells = 0;
|
||||||
|
int vertical_cells = 0;
|
||||||
|
|
||||||
|
sixel_dither_t *dither = nullptr;
|
||||||
|
sixel_output_t *output = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
64
src/canvas/stdout.hpp
Normal file
64
src/canvas/stdout.hpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef STDOUT_CANVAS_H
|
||||||
|
#define STDOUT_CANVAS_H
|
||||||
|
|
||||||
|
#include "canvas.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
template <WindowType T>
|
||||||
|
class StdoutCanvas : public Canvas
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit StdoutCanvas(const std::string &output)
|
||||||
|
{
|
||||||
|
logger = spdlog::get(output);
|
||||||
|
logger->info("Canvas created");
|
||||||
|
}
|
||||||
|
|
||||||
|
~StdoutCanvas() override = default;
|
||||||
|
|
||||||
|
void add_image(const std::string &identifier, std::unique_ptr<Image> new_image) override
|
||||||
|
{
|
||||||
|
logger->info("Displaying image with id {}", identifier);
|
||||||
|
images.erase(identifier);
|
||||||
|
const auto [entry, success] =
|
||||||
|
images.emplace(identifier, std::make_unique<T>(std::move(new_image), &stdout_mutex));
|
||||||
|
entry->second->draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_image(const std::string &identifier) override
|
||||||
|
{
|
||||||
|
logger->info("Removing image with id {}", identifier);
|
||||||
|
images.erase(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::mutex stdout_mutex;
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
std::unordered_map<std::string, std::unique_ptr<T>> images;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
43
src/canvas/wayland/config.cpp
Normal file
43
src/canvas/wayland/config.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "config.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "config/sway.hpp"
|
||||||
|
#include "config/hyprland.hpp"
|
||||||
|
#include "config/wayfire.hpp"
|
||||||
|
#include "config/dummy.hpp"
|
||||||
|
|
||||||
|
auto WaylandConfig::get() -> std::unique_ptr<WaylandConfig>
|
||||||
|
{
|
||||||
|
const auto sway_sock = os::getenv("SWAYSOCK");
|
||||||
|
if (sway_sock.has_value()) {
|
||||||
|
return std::make_unique<SwaySocket>(sway_sock.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hypr_sig = os::getenv("HYPRLAND_INSTANCE_SIGNATURE");
|
||||||
|
if (hypr_sig.has_value()) {
|
||||||
|
return std::make_unique<HyprlandSocket>(hypr_sig.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto wayfire_sock = os::getenv("WAYFIRE_SOCKET");
|
||||||
|
if (wayfire_sock.has_value()) {
|
||||||
|
return std::make_unique<WayfireSocket>(wayfire_sock.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_unique<DummyWaylandConfig>();
|
||||||
|
}
|
||||||
|
|
||||||
45
src/canvas/wayland/config.hpp
Normal file
45
src/canvas/wayland/config.hpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef WAYLAND_CONFIG_H
|
||||||
|
#define WAYLAND_CONFIG_H
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
struct WaylandWindowGeometry {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
};
|
||||||
|
|
||||||
|
class WaylandConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static auto get() -> std::unique_ptr<WaylandConfig>;
|
||||||
|
|
||||||
|
virtual ~WaylandConfig() = default;
|
||||||
|
|
||||||
|
virtual auto get_focused_output_name() -> std::string = 0;
|
||||||
|
virtual auto get_window_info() -> struct WaylandWindowGeometry = 0;
|
||||||
|
virtual auto is_dummy() -> bool { return false; }
|
||||||
|
virtual void initial_setup(std::string_view appid) = 0;
|
||||||
|
virtual void move_window(std::string_view appid, int xcoord, int ycoord) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
29
src/canvas/wayland/config/dummy.cpp
Normal file
29
src/canvas/wayland/config/dummy.cpp
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "dummy.hpp"
|
||||||
|
|
||||||
|
auto DummyWaylandConfig::get_window_info() -> struct WaylandWindowGeometry
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void DummyWaylandConfig::initial_setup([[maybe_unused]] std::string_view appid)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void DummyWaylandConfig::move_window([[maybe_unused]] std::string_view appid,
|
||||||
|
[[maybe_unused]] int xcoord, [[maybe_unused]] int ycoord)
|
||||||
|
{}
|
||||||
35
src/canvas/wayland/config/dummy.hpp
Normal file
35
src/canvas/wayland/config/dummy.hpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef DUMMY_WAYLAND_CONFIG_H
|
||||||
|
#define DUMMY_WAYLAND_CONFIG_H
|
||||||
|
|
||||||
|
#include "../config.hpp"
|
||||||
|
|
||||||
|
class DummyWaylandConfig : public WaylandConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
DummyWaylandConfig() = default;
|
||||||
|
~DummyWaylandConfig() override = default;
|
||||||
|
|
||||||
|
auto get_focused_output_name() -> std::string override { return {}; };
|
||||||
|
auto get_window_info() -> struct WaylandWindowGeometry override;
|
||||||
|
auto is_dummy() -> bool override { return true; }
|
||||||
|
void initial_setup(std::string_view appid) override;
|
||||||
|
void move_window(std::string_view appid, int xcoord, int ycoord) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
151
src/canvas/wayland/config/hyprland.cpp
Normal file
151
src/canvas/wayland/config/hyprland.cpp
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "hyprland.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "tmux.hpp"
|
||||||
|
#include "util/socket.hpp"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <range/v3/all.hpp>
|
||||||
|
|
||||||
|
using njson = nlohmann::json;
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
HyprlandSocket::HyprlandSocket(const std::string_view signature)
|
||||||
|
: logger(spdlog::get("wayland"))
|
||||||
|
{
|
||||||
|
const auto socket_base_dir = os::getenv("XDG_RUNTIME_DIR").value_or("/tmp");
|
||||||
|
const auto socket_rel_path = fmt::format("hypr/{}/.socket.sock", signature);
|
||||||
|
socket_path = fmt::format("{}/{}", socket_base_dir, socket_rel_path);
|
||||||
|
// XDG_RUNTIME_DIR set but hyprland < 0.40
|
||||||
|
if (!fs::exists(socket_path)) {
|
||||||
|
socket_path = fmt::format("/tmp/{}", socket_rel_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger->info("Using hyprland socket {}", socket_path);
|
||||||
|
const auto active = request_result("j/activewindow");
|
||||||
|
address = active.at("address");
|
||||||
|
set_active_monitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandSocket::set_active_monitor()
|
||||||
|
{
|
||||||
|
const auto monitors = request_result("j/monitors");
|
||||||
|
for (const auto &monitor : monitors) {
|
||||||
|
bool focused = monitor.at("focused");
|
||||||
|
if (focused) {
|
||||||
|
output_name = monitor.at("name");
|
||||||
|
output_scale = monitor.at("scale");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto HyprlandSocket::get_focused_output_name() -> std::string
|
||||||
|
{
|
||||||
|
return output_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto HyprlandSocket::request_result(const std::string_view payload) -> nlohmann::json
|
||||||
|
{
|
||||||
|
const UnixSocket socket{socket_path};
|
||||||
|
socket.write(payload.data(), payload.size());
|
||||||
|
const std::string result = socket.read_until_empty();
|
||||||
|
return njson::parse(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandSocket::request(const std::string_view payload)
|
||||||
|
{
|
||||||
|
const UnixSocket socket{socket_path};
|
||||||
|
logger->debug("Running socket command {}", payload);
|
||||||
|
socket.write(payload.data(), payload.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto HyprlandSocket::get_active_window() -> nlohmann::json
|
||||||
|
{
|
||||||
|
// recalculate address in case it changed
|
||||||
|
if (tmux::is_used()) {
|
||||||
|
const auto active = request_result("j/activewindow");
|
||||||
|
address = active.at("address");
|
||||||
|
}
|
||||||
|
const auto clients = request_result("j/clients");
|
||||||
|
const auto client = ranges::find_if(clients, [this](const njson &json) { return json.at("address") == address; });
|
||||||
|
if (client == clients.end()) {
|
||||||
|
throw std::runtime_error("Active window not found");
|
||||||
|
}
|
||||||
|
return *client;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto HyprlandSocket::get_window_info() -> struct WaylandWindowGeometry {
|
||||||
|
const auto terminal = get_active_window();
|
||||||
|
const auto &sizes = terminal.at("size");
|
||||||
|
const auto &coords = terminal.at("at");
|
||||||
|
|
||||||
|
return {
|
||||||
|
.width = sizes.at(0),
|
||||||
|
.height = sizes.at(1),
|
||||||
|
.x = coords.at(0),
|
||||||
|
.y = coords.at(1),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandSocket::initial_setup(const std::string_view appid)
|
||||||
|
{
|
||||||
|
disable_focus(appid);
|
||||||
|
enable_floating(appid);
|
||||||
|
remove_borders(appid);
|
||||||
|
remove_rounding(appid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandSocket::remove_rounding(const std::string_view appid)
|
||||||
|
{
|
||||||
|
const auto payload = fmt::format("/keyword windowrulev2 rounding 0,title:{}", appid);
|
||||||
|
request(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandSocket::disable_focus(const std::string_view appid)
|
||||||
|
{
|
||||||
|
const auto payload = fmt::format("/keyword windowrulev2 nofocus,title:{}", appid);
|
||||||
|
request(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandSocket::enable_floating(const std::string_view appid)
|
||||||
|
{
|
||||||
|
const auto payload = fmt::format("/keyword windowrulev2 float,title:{}", appid);
|
||||||
|
request(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandSocket::remove_borders(const std::string_view appid)
|
||||||
|
{
|
||||||
|
const auto payload = fmt::format("/keyword windowrulev2 noborder,title:{}", appid);
|
||||||
|
request(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HyprlandSocket::move_window(const std::string_view appid, int xcoord, int ycoord)
|
||||||
|
{
|
||||||
|
int res_x = xcoord;
|
||||||
|
int res_y = ycoord;
|
||||||
|
if (output_scale > 1.0F) {
|
||||||
|
const int offset = 10;
|
||||||
|
res_x = res_x / 2 + offset;
|
||||||
|
res_y = res_y / 2 + offset;
|
||||||
|
}
|
||||||
|
const auto payload = fmt::format("/dispatch movewindowpixel exact {} {},title:{}", res_x, res_y, appid);
|
||||||
|
request(payload);
|
||||||
|
}
|
||||||
53
src/canvas/wayland/config/hyprland.hpp
Normal file
53
src/canvas/wayland/config/hyprland.hpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef HYPRLAND_SOCKET_H
|
||||||
|
#define HYPRLAND_SOCKET_H
|
||||||
|
|
||||||
|
#include "../config.hpp"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
class HyprlandSocket : public WaylandConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit HyprlandSocket(std::string_view signature);
|
||||||
|
~HyprlandSocket() override = default;
|
||||||
|
|
||||||
|
auto get_window_info() -> struct WaylandWindowGeometry override;
|
||||||
|
auto get_focused_output_name() -> std::string override;
|
||||||
|
void initial_setup(std::string_view appid) override;
|
||||||
|
void move_window(std::string_view appid, int xcoord, int ycoord) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void disable_focus(std::string_view appid);
|
||||||
|
void enable_floating(std::string_view appid);
|
||||||
|
void remove_borders(std::string_view appid);
|
||||||
|
void remove_rounding(std::string_view appid);
|
||||||
|
void request(std::string_view payload);
|
||||||
|
auto request_result(std::string_view payload) -> nlohmann::json;
|
||||||
|
auto get_active_window() -> nlohmann::json;
|
||||||
|
void set_active_monitor();
|
||||||
|
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
std::string socket_path;
|
||||||
|
std::string address;
|
||||||
|
std::string output_name;
|
||||||
|
float output_scale = 1.0F;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
183
src/canvas/wayland/config/sway.cpp
Normal file
183
src/canvas/wayland/config/sway.cpp
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "sway.hpp"
|
||||||
|
#include "application.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "tmux.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <stack>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <range/v3/all.hpp>
|
||||||
|
|
||||||
|
using njson = nlohmann::json;
|
||||||
|
|
||||||
|
constexpr auto ipc_magic = std::string_view{"i3-ipc"};
|
||||||
|
constexpr auto ipc_header_size = ipc_magic.size() + 8;
|
||||||
|
|
||||||
|
SwaySocket::SwaySocket(const std::string_view endpoint)
|
||||||
|
: socket(endpoint),
|
||||||
|
logger(spdlog::get("wayland"))
|
||||||
|
{
|
||||||
|
logger->info("Using sway socket {}", endpoint);
|
||||||
|
set_active_output_info();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct __attribute__((packed)) ipc_header {
|
||||||
|
std::array<char, ipc_magic.size()> magic;
|
||||||
|
uint32_t len;
|
||||||
|
uint32_t type;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto SwaySocket::get_window_info() -> struct WaylandWindowGeometry {
|
||||||
|
const auto nodes = get_nodes();
|
||||||
|
const auto window = get_active_window(nodes);
|
||||||
|
const auto &rect = window.at("rect");
|
||||||
|
return {.width = rect.at("width"),
|
||||||
|
.height = rect.at("height"),
|
||||||
|
.x = rect.at("x").get<int>() - output_info.x,
|
||||||
|
.y = rect.at("y").get<int>() - output_info.y};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SwaySocket::get_active_window(const std::vector<nlohmann::json>& nodes) -> nlohmann::json
|
||||||
|
{
|
||||||
|
const auto pids = tmux::get_client_pids().value_or(std::vector<int>{Application::parent_pid});
|
||||||
|
|
||||||
|
for (const auto pid : pids) {
|
||||||
|
const auto tree = util::get_process_tree(pid);
|
||||||
|
const auto found = ranges::find_if(nodes, [&tree](const njson &json) -> bool {
|
||||||
|
try {
|
||||||
|
return ranges::find(tree, json.at("pid").get<int>()) != tree.end();
|
||||||
|
} catch (const njson::out_of_range &err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (found != nodes.end()) {
|
||||||
|
return *found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SwaySocket::get_focused_output_name() -> std::string
|
||||||
|
{
|
||||||
|
return output_info.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
void SwaySocket::set_active_output_info()
|
||||||
|
{
|
||||||
|
const auto outputs = ipc_message(IPC_GET_OUTPUTS);
|
||||||
|
for (const auto &node : outputs) {
|
||||||
|
bool focused = node.at("focused");
|
||||||
|
if (focused) {
|
||||||
|
const auto &rect = node.at("rect");
|
||||||
|
output_info = {
|
||||||
|
.x = rect.at("x"),
|
||||||
|
.y = rect.at("y"),
|
||||||
|
.scale = node.at("scale"),
|
||||||
|
.name = node.at("name"),
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwaySocket::initial_setup(const std::string_view appid)
|
||||||
|
{
|
||||||
|
disable_focus(appid);
|
||||||
|
enable_floating(appid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwaySocket::disable_focus(const std::string_view appid)
|
||||||
|
{
|
||||||
|
const auto payload = fmt::format(R"(no_focus [app_id="{}"])", appid);
|
||||||
|
ipc_command(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwaySocket::enable_floating(const std::string_view appid)
|
||||||
|
{
|
||||||
|
ipc_command(appid, "floating enable");
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwaySocket::move_window(const std::string_view appid, int xcoord, int ycoord)
|
||||||
|
{
|
||||||
|
int res_x = xcoord;
|
||||||
|
int res_y = ycoord;
|
||||||
|
if (output_info.scale > 1.0F) {
|
||||||
|
res_x = res_x / 2;
|
||||||
|
res_y = res_y / 2;
|
||||||
|
}
|
||||||
|
const auto payload = fmt::format(R"([app_id="{}"] move position {} {})", appid, res_x, res_y);
|
||||||
|
ipc_command(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SwaySocket::ipc_message(ipc_message_type type, const std::string_view payload) const -> nlohmann::json
|
||||||
|
{
|
||||||
|
struct ipc_header header;
|
||||||
|
header.len = payload.size();
|
||||||
|
header.type = type;
|
||||||
|
ipc_magic.copy(header.magic.data(), ipc_magic.size());
|
||||||
|
|
||||||
|
if (!payload.empty()) {
|
||||||
|
logger->debug("Running socket command {}", payload);
|
||||||
|
}
|
||||||
|
socket.write(&header, ipc_header_size);
|
||||||
|
socket.write(payload.data(), payload.size());
|
||||||
|
|
||||||
|
socket.read(&header, ipc_header_size);
|
||||||
|
std::string buff(header.len, 0);
|
||||||
|
socket.read(buff.data(), buff.size());
|
||||||
|
return njson::parse(buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto SwaySocket::get_nodes() const -> std::vector<nlohmann::json>
|
||||||
|
{
|
||||||
|
logger->debug("Obtaining sway tree");
|
||||||
|
const auto tree = ipc_message(IPC_GET_TREE);
|
||||||
|
std::stack<njson> nodes_st;
|
||||||
|
std::vector<njson> nodes_vec;
|
||||||
|
|
||||||
|
nodes_st.push(tree);
|
||||||
|
|
||||||
|
while (!nodes_st.empty()) {
|
||||||
|
const auto top = nodes_st.top();
|
||||||
|
nodes_st.pop();
|
||||||
|
nodes_vec.push_back(top);
|
||||||
|
for (const auto &node : top.at("nodes")) {
|
||||||
|
nodes_st.push(node);
|
||||||
|
}
|
||||||
|
for (const auto &node : top.at("floating_nodes")) {
|
||||||
|
nodes_st.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nodes_vec;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwaySocket::ipc_command(const std::string_view appid, const std::string_view command) const
|
||||||
|
{
|
||||||
|
const auto payload = fmt::format(R"(for_window [app_id="{}"] {})", appid, command);
|
||||||
|
ipc_command(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwaySocket::ipc_command(const std::string_view payload) const
|
||||||
|
{
|
||||||
|
std::ignore = ipc_message(IPC_COMMAND, payload);
|
||||||
|
}
|
||||||
62
src/canvas/wayland/config/sway.hpp
Normal file
62
src/canvas/wayland/config/sway.hpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef SWAY_SOCKET_H
|
||||||
|
#define SWAY_SOCKET_H
|
||||||
|
|
||||||
|
#include "../config.hpp"
|
||||||
|
#include "util/socket.hpp"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
enum ipc_message_type { IPC_COMMAND = 0, IPC_GET_WORKSPACES = 1, IPC_GET_OUTPUTS = 3, IPC_GET_TREE = 4 };
|
||||||
|
|
||||||
|
struct SwayOutputInfo {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
float scale;
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SwaySocket : public WaylandConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit SwaySocket(std::string_view endpoint);
|
||||||
|
~SwaySocket() override = default;
|
||||||
|
|
||||||
|
auto get_focused_output_name() -> std::string override;
|
||||||
|
auto get_window_info() -> struct WaylandWindowGeometry override;
|
||||||
|
void initial_setup(std::string_view appid) override;
|
||||||
|
void move_window(std::string_view appid, int xcoord, int ycoord) override;
|
||||||
|
static auto get_active_window(const std::vector<nlohmann::json> &nodes) -> nlohmann::json;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void disable_focus(std::string_view appid);
|
||||||
|
void enable_floating(std::string_view appid);
|
||||||
|
void ipc_command(std::string_view appid, std::string_view command) const;
|
||||||
|
void ipc_command(std::string_view payload) const;
|
||||||
|
void set_active_output_info();
|
||||||
|
|
||||||
|
[[nodiscard]] auto get_nodes() const -> std::vector<nlohmann::json>;
|
||||||
|
[[nodiscard]] auto ipc_message(ipc_message_type type, std::string_view payload = "") const -> nlohmann::json;
|
||||||
|
|
||||||
|
UnixSocket socket;
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
struct SwayOutputInfo output_info;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
73
src/canvas/wayland/config/wayfire.cpp
Normal file
73
src/canvas/wayland/config/wayfire.cpp
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "wayfire.hpp"
|
||||||
|
|
||||||
|
using njson = nlohmann::json;
|
||||||
|
|
||||||
|
WayfireSocket::WayfireSocket(const std::string_view endpoint):
|
||||||
|
socket(endpoint),
|
||||||
|
logger(spdlog::get("wayland"))
|
||||||
|
{
|
||||||
|
logger->info("Using wayfire socket {}", endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto WayfireSocket::get_window_info() -> struct WaylandWindowGeometry
|
||||||
|
{
|
||||||
|
const auto response = request("window-rules/get-focused-view");
|
||||||
|
const auto& info = response.at("info");
|
||||||
|
const auto& geometry = info.at("geometry");
|
||||||
|
const int decoration_height = 25;
|
||||||
|
return {
|
||||||
|
.width = geometry.at("width"),
|
||||||
|
.height = geometry.at("height").get<int>() - decoration_height,
|
||||||
|
.x = 0,
|
||||||
|
.y = 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void WayfireSocket::initial_setup([[maybe_unused]] const std::string_view appid)
|
||||||
|
{
|
||||||
|
// all is handled by the ueberzug plugin
|
||||||
|
}
|
||||||
|
|
||||||
|
void WayfireSocket::move_window(const std::string_view appid, int xcoord, int ycoord)
|
||||||
|
{
|
||||||
|
const njson payload_data = {
|
||||||
|
{"app-id", appid},
|
||||||
|
{"x", xcoord},
|
||||||
|
{"y", ycoord}
|
||||||
|
};
|
||||||
|
std::ignore = request("ueberzugpp/set_offset", payload_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto WayfireSocket::request(const std::string_view method, const njson& data) const -> njson
|
||||||
|
{
|
||||||
|
const njson json = {
|
||||||
|
{"method", method},
|
||||||
|
{"data", data}
|
||||||
|
};
|
||||||
|
const auto payload = json.dump();
|
||||||
|
const uint32_t payload_size = payload.length();
|
||||||
|
socket.write(&payload_size, sizeof(uint32_t));
|
||||||
|
socket.write(payload.c_str(), payload_size);
|
||||||
|
|
||||||
|
uint32_t response_size = 0;
|
||||||
|
socket.read(&response_size, sizeof(uint32_t));
|
||||||
|
std::string buffer (response_size, 0);
|
||||||
|
socket.read(buffer.data(), response_size);
|
||||||
|
return njson::parse(buffer);
|
||||||
|
}
|
||||||
44
src/canvas/wayland/config/wayfire.hpp
Normal file
44
src/canvas/wayland/config/wayfire.hpp
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef WAYFIRE_SOCKET_H
|
||||||
|
#define WAYFIRE_SOCKET_H
|
||||||
|
|
||||||
|
#include "../config.hpp"
|
||||||
|
#include "util/socket.hpp"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
class WayfireSocket : public WaylandConfig
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit WayfireSocket(std::string_view endpoint);
|
||||||
|
~WayfireSocket() override = default;
|
||||||
|
|
||||||
|
auto get_window_info() -> struct WaylandWindowGeometry override;
|
||||||
|
auto get_focused_output_name() -> std::string override { return {}; };
|
||||||
|
void initial_setup(std::string_view appid) override;
|
||||||
|
void move_window(std::string_view appid, int xcoord, int ycoord) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] auto request(std::string_view method, const nlohmann::json &data = {}) const -> nlohmann::json;
|
||||||
|
|
||||||
|
UnixSocket socket;
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
222
src/canvas/wayland/wayland.cpp
Normal file
222
src/canvas/wayland/wayland.cpp
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "wayland.hpp"
|
||||||
|
#include "application.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
# include "window/waylandegl.hpp"
|
||||||
|
# include <EGL/eglext.h>
|
||||||
|
#endif
|
||||||
|
#include "window/waylandshm.hpp"
|
||||||
|
|
||||||
|
constexpr struct wl_registry_listener registry_listener = {.global = WaylandCanvas::registry_handle_global,
|
||||||
|
.global_remove = [](auto...) { /*unused*/ }};
|
||||||
|
|
||||||
|
constexpr struct xdg_wm_base_listener xdg_wm_base_listener = {.ping = WaylandCanvas::xdg_wm_base_ping};
|
||||||
|
|
||||||
|
constexpr struct wl_output_listener wl_output_listener = {.geometry = [](auto...) { /*unused*/ },
|
||||||
|
.mode = [](auto...) { /*unused*/ },
|
||||||
|
.done = WaylandCanvas::output_done,
|
||||||
|
.scale = WaylandCanvas::output_scale,
|
||||||
|
.name = WaylandCanvas::output_name,
|
||||||
|
.description = [](auto...) { /*unused*/ }};
|
||||||
|
|
||||||
|
void WaylandCanvas::registry_handle_global(void *data, wl_registry *registry, uint32_t name, const char *interface,
|
||||||
|
[[maybe_unused]] uint32_t version)
|
||||||
|
{
|
||||||
|
const std::string_view interface_str{interface};
|
||||||
|
#ifdef WSL
|
||||||
|
const uint32_t compositor_ver = 4;
|
||||||
|
const uint32_t shm_ver = 1;
|
||||||
|
const uint32_t xdg_base_ver = 1;
|
||||||
|
const uint32_t output_ver = 3;
|
||||||
|
#else
|
||||||
|
const uint32_t compositor_ver = 5;
|
||||||
|
const uint32_t shm_ver = 1;
|
||||||
|
const uint32_t xdg_base_ver = 2;
|
||||||
|
const uint32_t output_ver = 4;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto *canvas = static_cast<WaylandCanvas *>(data);
|
||||||
|
if (interface_str == wl_compositor_interface.name) {
|
||||||
|
canvas->compositor = static_cast<struct wl_compositor *>(
|
||||||
|
wl_registry_bind(registry, name, &wl_compositor_interface, compositor_ver));
|
||||||
|
} else if (interface_str == wl_shm_interface.name) {
|
||||||
|
canvas->wl_shm = static_cast<struct wl_shm *>(wl_registry_bind(registry, name, &wl_shm_interface, shm_ver));
|
||||||
|
} else if (interface_str == xdg_wm_base_interface.name) {
|
||||||
|
canvas->xdg_base =
|
||||||
|
static_cast<struct xdg_wm_base *>(wl_registry_bind(registry, name, &xdg_wm_base_interface, xdg_base_ver));
|
||||||
|
xdg_wm_base_add_listener(canvas->xdg_base, &xdg_wm_base_listener, canvas);
|
||||||
|
} else if (interface_str == wl_output_interface.name) {
|
||||||
|
auto *output =
|
||||||
|
static_cast<struct wl_output *>(wl_registry_bind(registry, name, &wl_output_interface, output_ver));
|
||||||
|
wl_output_add_listener(output, &wl_output_listener, canvas);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandCanvas::xdg_wm_base_ping([[maybe_unused]] void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
|
||||||
|
{
|
||||||
|
xdg_wm_base_pong(xdg_wm_base, serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandCanvas::output_name(void *data, [[maybe_unused]] struct wl_output *output, const char *name)
|
||||||
|
{
|
||||||
|
auto *canvas = static_cast<WaylandCanvas *>(data);
|
||||||
|
canvas->output_pair.first = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandCanvas::output_scale(void *data, [[maybe_unused]] struct wl_output *output, int32_t scale)
|
||||||
|
{
|
||||||
|
auto *canvas = static_cast<WaylandCanvas *>(data);
|
||||||
|
canvas->output_pair.second = scale;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandCanvas::output_done(void *data, [[maybe_unused]] struct wl_output *output)
|
||||||
|
{
|
||||||
|
auto *canvas = static_cast<WaylandCanvas *>(data);
|
||||||
|
const auto active_output = canvas->config->get_focused_output_name();
|
||||||
|
if (active_output == canvas->output_pair.first) {
|
||||||
|
canvas->flags->scale_factor = canvas->output_pair.second;
|
||||||
|
canvas->flags->needs_scaling = canvas->output_pair.second > 1;
|
||||||
|
}
|
||||||
|
canvas->output_info.insert(canvas->output_pair);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaylandCanvas::WaylandCanvas()
|
||||||
|
: display(wl_display_connect(nullptr))
|
||||||
|
{
|
||||||
|
if (display == nullptr) {
|
||||||
|
throw std::runtime_error("Failed to connect to wayland display.");
|
||||||
|
}
|
||||||
|
logger = spdlog::get("wayland");
|
||||||
|
flags = Flags::instance();
|
||||||
|
config = WaylandConfig::get();
|
||||||
|
registry = wl_display_get_registry(display);
|
||||||
|
wl_registry_add_listener(registry, ®istry_listener, this);
|
||||||
|
wl_display_roundtrip(display);
|
||||||
|
event_handler = std::thread(&WaylandCanvas::handle_events, this);
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
if (flags->use_opengl) {
|
||||||
|
try {
|
||||||
|
egl = std::make_unique<EGLUtil<struct wl_display, struct wl_egl_window>>(EGL_PLATFORM_WAYLAND_EXT, display);
|
||||||
|
} catch (const std::runtime_error &) {
|
||||||
|
egl_available = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
egl_available = false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
logger->info("Canvas created");
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandCanvas::show()
|
||||||
|
{
|
||||||
|
for (const auto &[key, value] : windows) {
|
||||||
|
value->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandCanvas::hide()
|
||||||
|
{
|
||||||
|
for (const auto &[key, value] : windows) {
|
||||||
|
value->hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WaylandCanvas::~WaylandCanvas()
|
||||||
|
{
|
||||||
|
windows.clear();
|
||||||
|
if (event_handler.joinable()) {
|
||||||
|
event_handler.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
egl.reset();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (wl_shm != nullptr) {
|
||||||
|
wl_shm_destroy(wl_shm);
|
||||||
|
}
|
||||||
|
if (compositor != nullptr) {
|
||||||
|
wl_compositor_destroy(compositor);
|
||||||
|
}
|
||||||
|
if (registry != nullptr) {
|
||||||
|
wl_registry_destroy(registry);
|
||||||
|
}
|
||||||
|
wl_display_disconnect(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandCanvas::add_image(const std::string &identifier, std::unique_ptr<Image> new_image)
|
||||||
|
{
|
||||||
|
std::shared_ptr<WaylandWindow> window;
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
if (egl_available) {
|
||||||
|
try {
|
||||||
|
window = std::make_shared<WaylandEglWindow>(compositor, xdg_base, egl.get(), std::move(new_image),
|
||||||
|
config.get(), &xdg_agg);
|
||||||
|
} catch (const std::runtime_error &err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (window == nullptr) {
|
||||||
|
window = std::make_shared<WaylandShmWindow>(this, std::move(new_image), &xdg_agg, config.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
window->finish_init();
|
||||||
|
windows.insert_or_assign(identifier, std::move(window));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandCanvas::handle_events()
|
||||||
|
{
|
||||||
|
const auto wl_fd = wl_display_get_fd(display);
|
||||||
|
bool in_event = false;
|
||||||
|
|
||||||
|
while (!Application::stop_flag) {
|
||||||
|
// prepare to read wayland events
|
||||||
|
while (wl_display_prepare_read(display) != 0) {
|
||||||
|
wl_display_dispatch_pending(display);
|
||||||
|
}
|
||||||
|
wl_display_flush(display);
|
||||||
|
|
||||||
|
try {
|
||||||
|
constexpr int waitms = 100;
|
||||||
|
in_event = os::wait_for_data_on_fd(wl_fd, waitms);
|
||||||
|
} catch (const std::system_error &err) {
|
||||||
|
Application::stop_flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (in_event) {
|
||||||
|
wl_display_read_events(display);
|
||||||
|
wl_display_dispatch_pending(display);
|
||||||
|
} else {
|
||||||
|
wl_display_cancel_read(display);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandCanvas::remove_image(const std::string &identifier)
|
||||||
|
{
|
||||||
|
windows.erase(identifier);
|
||||||
|
wl_display_flush(display);
|
||||||
|
}
|
||||||
84
src/canvas/wayland/wayland.hpp
Normal file
84
src/canvas/wayland/wayland.hpp
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef WAYLAND_CANVAS_H
|
||||||
|
#define WAYLAND_CANVAS_H
|
||||||
|
|
||||||
|
#include "canvas.hpp"
|
||||||
|
#include "config.hpp"
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "wayland-xdg-shell-client-protocol.h"
|
||||||
|
#include "window/waylandwindow.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <thread>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
# include "util/egl.hpp"
|
||||||
|
# include <wayland-egl.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class WaylandCanvas : public Canvas
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit WaylandCanvas();
|
||||||
|
~WaylandCanvas() override;
|
||||||
|
|
||||||
|
static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface,
|
||||||
|
uint32_t version);
|
||||||
|
static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial);
|
||||||
|
|
||||||
|
static void output_scale(void *data, struct wl_output *output, int32_t scale);
|
||||||
|
static void output_name(void *data, struct wl_output *output, const char *name);
|
||||||
|
static void output_done(void *data, struct wl_output *output);
|
||||||
|
|
||||||
|
void add_image(const std::string &identifier, std::unique_ptr<Image> new_image) override;
|
||||||
|
void remove_image(const std::string &identifier) override;
|
||||||
|
void show() override;
|
||||||
|
void hide() override;
|
||||||
|
|
||||||
|
struct wl_compositor *compositor = nullptr;
|
||||||
|
struct wl_shm *wl_shm = nullptr;
|
||||||
|
struct xdg_wm_base *xdg_base = nullptr;
|
||||||
|
|
||||||
|
std::pair<std::string, int32_t> output_pair;
|
||||||
|
std::unordered_map<std::string, int32_t> output_info;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct wl_display *display = nullptr;
|
||||||
|
struct wl_registry *registry = nullptr;
|
||||||
|
std::thread event_handler;
|
||||||
|
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
std::unique_ptr<WaylandConfig> config;
|
||||||
|
std::shared_ptr<Flags> flags;
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<WaylandWindow>> windows;
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
std::unique_ptr<EGLUtil<struct wl_display, struct wl_egl_window>> egl;
|
||||||
|
bool egl_available = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct XdgStructAgg xdg_agg;
|
||||||
|
|
||||||
|
void handle_events();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
75
src/canvas/wayland/window/shm.cpp
Normal file
75
src/canvas/wayland/window/shm.cpp
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "shm.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
#include "util/ptr.hpp"
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
WaylandShm::WaylandShm(int width, int height, int scale_factor, struct wl_shm *shm)
|
||||||
|
: shm(shm),
|
||||||
|
width(width),
|
||||||
|
height(height),
|
||||||
|
stride(width * 4),
|
||||||
|
pool_size(height * stride * scale_factor)
|
||||||
|
{
|
||||||
|
const int path_size = 32;
|
||||||
|
shm_path = fmt::format("/{}", util::generate_random_string(path_size));
|
||||||
|
create_shm_file();
|
||||||
|
allocate_pool_buffers();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShm::create_shm_file()
|
||||||
|
{
|
||||||
|
fd = memfd_create("ueberzugpp-shm", 0);
|
||||||
|
if (fd == -1) {
|
||||||
|
throw std::system_error(errno, std::system_category());
|
||||||
|
}
|
||||||
|
int res = ftruncate(fd, pool_size);
|
||||||
|
if (res == -1) {
|
||||||
|
throw std::system_error(errno, std::system_category());
|
||||||
|
}
|
||||||
|
auto *pool_ptr = mmap(nullptr, pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
||||||
|
if (pool_ptr == MAP_FAILED) {
|
||||||
|
throw std::system_error(errno, std::system_category());
|
||||||
|
}
|
||||||
|
pool_data = static_cast<uint8_t *>(pool_ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShm::allocate_pool_buffers()
|
||||||
|
{
|
||||||
|
const auto pool = c_unique_ptr<struct wl_shm_pool, wl_shm_pool_destroy>{wl_shm_create_pool(shm, fd, pool_size)};
|
||||||
|
buffer = wl_shm_pool_create_buffer(pool.get(), 0, width, height, stride, WL_SHM_FORMAT_ARGB8888);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaylandShm::~WaylandShm()
|
||||||
|
{
|
||||||
|
shm_unlink(shm_path.c_str());
|
||||||
|
close(fd);
|
||||||
|
munmap(pool_data, pool_size);
|
||||||
|
if (buffer != nullptr) {
|
||||||
|
wl_buffer_destroy(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/canvas/wayland/window/shm.hpp
Normal file
47
src/canvas/wayland/window/shm.hpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef WAYLAND_SHM_H
|
||||||
|
#define WAYLAND_SHM_H
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
class WaylandShm
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WaylandShm(int width, int height, int scale_factor, struct wl_shm *shm);
|
||||||
|
~WaylandShm();
|
||||||
|
|
||||||
|
struct wl_buffer *buffer = nullptr;
|
||||||
|
uint8_t *pool_data;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void create_shm_file();
|
||||||
|
void allocate_pool_buffers();
|
||||||
|
|
||||||
|
struct wl_shm *shm = nullptr;
|
||||||
|
|
||||||
|
int fd = 0;
|
||||||
|
std::string shm_path;
|
||||||
|
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
int stride = 0;
|
||||||
|
int pool_size = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
235
src/canvas/wayland/window/waylandegl.cpp
Normal file
235
src/canvas/wayland/window/waylandegl.cpp
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "waylandegl.hpp"
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
constexpr int id_len = 10;
|
||||||
|
|
||||||
|
constexpr struct xdg_surface_listener xdg_surface_listener_egl = {
|
||||||
|
.configure = WaylandEglWindow::xdg_surface_configure,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr struct wl_callback_listener frame_listener_egl = {.done = WaylandEglWindow::wl_surface_frame_done};
|
||||||
|
|
||||||
|
WaylandEglWindow::WaylandEglWindow(struct wl_compositor *compositor, struct xdg_wm_base *xdg_base,
|
||||||
|
const EGLUtil<struct wl_display, struct wl_egl_window> *egl,
|
||||||
|
std::unique_ptr<Image> new_image, WaylandConfig *new_config,
|
||||||
|
struct XdgStructAgg *xdg_agg)
|
||||||
|
: compositor(compositor),
|
||||||
|
xdg_base(xdg_base),
|
||||||
|
surface(wl_compositor_create_surface(compositor)),
|
||||||
|
xdg_surface(xdg_wm_base_get_xdg_surface(xdg_base, surface)),
|
||||||
|
xdg_toplevel(xdg_surface_get_toplevel(xdg_surface)),
|
||||||
|
image(std::move(new_image)),
|
||||||
|
config(new_config),
|
||||||
|
egl_window(wl_egl_window_create(surface, image->width(), image->height())),
|
||||||
|
egl(egl),
|
||||||
|
appid(fmt::format("ueberzugpp_{}", util::generate_random_string(id_len))),
|
||||||
|
xdg_agg(xdg_agg)
|
||||||
|
{
|
||||||
|
config->initial_setup(appid);
|
||||||
|
opengl_setup();
|
||||||
|
xdg_setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
WaylandEglWindow::~WaylandEglWindow()
|
||||||
|
{
|
||||||
|
opengl_cleanup();
|
||||||
|
delete_xdg_structs();
|
||||||
|
delete_wayland_structs();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::opengl_cleanup()
|
||||||
|
{
|
||||||
|
egl->run_contained(egl_context, egl_surface, [this] {
|
||||||
|
glDeleteTextures(1, &texture);
|
||||||
|
glDeleteFramebuffers(1, &fbo);
|
||||||
|
});
|
||||||
|
eglDestroySurface(egl->display, egl_surface);
|
||||||
|
eglDestroyContext(egl->display, egl_context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::finish_init()
|
||||||
|
{
|
||||||
|
auto xdg = std::make_unique<XdgStruct>();
|
||||||
|
xdg->ptr = weak_from_this();
|
||||||
|
this_ptr = xdg.get();
|
||||||
|
xdg_agg->ptrs.push_back(std::move(xdg));
|
||||||
|
setup_listeners();
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::opengl_setup()
|
||||||
|
{
|
||||||
|
egl_surface = egl->create_surface(egl_window);
|
||||||
|
if (egl_surface == EGL_NO_SURFACE) {
|
||||||
|
throw std::runtime_error("");
|
||||||
|
}
|
||||||
|
|
||||||
|
egl_context = egl->create_context(egl_surface);
|
||||||
|
if (egl_context == EGL_NO_CONTEXT) {
|
||||||
|
throw std::runtime_error("");
|
||||||
|
}
|
||||||
|
|
||||||
|
egl->run_contained(egl_surface, egl_context, [this] {
|
||||||
|
eglSwapInterval(egl->display, 0);
|
||||||
|
glGenFramebuffers(1, &fbo);
|
||||||
|
glGenTextures(1, &texture);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::setup_listeners()
|
||||||
|
{
|
||||||
|
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener_egl, this_ptr);
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
|
||||||
|
if (image->is_animated()) {
|
||||||
|
callback = wl_surface_frame(surface);
|
||||||
|
wl_callback_add_listener(callback, &frame_listener_egl, this_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::xdg_setup()
|
||||||
|
{
|
||||||
|
xdg_toplevel_set_app_id(xdg_toplevel, appid.c_str());
|
||||||
|
xdg_toplevel_set_title(xdg_toplevel, appid.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::delete_xdg_structs()
|
||||||
|
{
|
||||||
|
if (xdg_toplevel != nullptr) {
|
||||||
|
xdg_toplevel_destroy(xdg_toplevel);
|
||||||
|
xdg_toplevel = nullptr;
|
||||||
|
}
|
||||||
|
if (xdg_surface != nullptr) {
|
||||||
|
xdg_surface_destroy(xdg_surface);
|
||||||
|
xdg_surface = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::delete_wayland_structs()
|
||||||
|
{
|
||||||
|
if (egl_window != nullptr) {
|
||||||
|
wl_egl_window_destroy(egl_window);
|
||||||
|
egl_window = nullptr;
|
||||||
|
}
|
||||||
|
if (surface != nullptr) {
|
||||||
|
wl_surface_destroy(surface);
|
||||||
|
surface = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::draw()
|
||||||
|
{
|
||||||
|
load_framebuffer();
|
||||||
|
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
move_window();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::load_framebuffer()
|
||||||
|
{
|
||||||
|
std::scoped_lock lock{egl_mutex};
|
||||||
|
egl->run_contained(egl_surface, egl_context, [this] {
|
||||||
|
egl->get_texture_from_image(*image, texture);
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
|
||||||
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
|
||||||
|
glBlitFramebuffer(0, 0, image->width(), image->height(), 0, 0, image->width(), image->height(),
|
||||||
|
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||||
|
|
||||||
|
eglSwapBuffers(egl->display, egl_surface);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::move_window()
|
||||||
|
{
|
||||||
|
const auto dims = image->dimensions();
|
||||||
|
const auto cur_window = config->get_window_info();
|
||||||
|
const int wayland_x = dims.xpixels() + dims.padding_horizontal;
|
||||||
|
const int wayland_y = dims.ypixels() + dims.padding_vertical;
|
||||||
|
const int xcoord = cur_window.x + wayland_x;
|
||||||
|
const int ycoord = cur_window.y + wayland_y;
|
||||||
|
config->move_window(appid, xcoord, ycoord);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::generate_frame()
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(image->frame_delay()));
|
||||||
|
callback = wl_surface_frame(surface);
|
||||||
|
wl_callback_add_listener(callback, &frame_listener_egl, this_ptr);
|
||||||
|
|
||||||
|
image->next_frame();
|
||||||
|
load_framebuffer();
|
||||||
|
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::show()
|
||||||
|
{
|
||||||
|
if (visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visible = true;
|
||||||
|
xdg_surface = xdg_wm_base_get_xdg_surface(xdg_base, surface);
|
||||||
|
xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
|
||||||
|
xdg_setup();
|
||||||
|
setup_listeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::hide()
|
||||||
|
{
|
||||||
|
if (!visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visible = false;
|
||||||
|
const std::scoped_lock lock{draw_mutex};
|
||||||
|
delete_xdg_structs();
|
||||||
|
wl_surface_attach(surface, nullptr, 0, 0);
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial)
|
||||||
|
{
|
||||||
|
xdg_surface_ack_configure(xdg_surface, serial);
|
||||||
|
const auto *tmp = static_cast<struct XdgStruct *>(data);
|
||||||
|
const auto window = tmp->ptr.lock();
|
||||||
|
if (!window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *egl_window = dynamic_cast<WaylandEglWindow *>(window.get());
|
||||||
|
egl_window->draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandEglWindow::wl_surface_frame_done(void *data, struct wl_callback *callback, [[maybe_unused]] uint32_t time)
|
||||||
|
{
|
||||||
|
wl_callback_destroy(callback);
|
||||||
|
const auto *tmp = static_cast<struct XdgStruct *>(data);
|
||||||
|
const auto window = tmp->ptr.lock();
|
||||||
|
if (!window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *egl_window = dynamic_cast<WaylandEglWindow *>(window.get());
|
||||||
|
const std::scoped_lock lock{egl_window->draw_mutex};
|
||||||
|
if (!egl_window->visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
egl_window->generate_frame();
|
||||||
|
}
|
||||||
88
src/canvas/wayland/window/waylandegl.hpp
Normal file
88
src/canvas/wayland/window/waylandegl.hpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef WAYLAND_EGL_WINDOW_H
|
||||||
|
#define WAYLAND_EGL_WINDOW_H
|
||||||
|
|
||||||
|
#include "../config.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "util/egl.hpp"
|
||||||
|
#include "wayland-xdg-shell-client-protocol.h"
|
||||||
|
#include "waylandwindow.hpp"
|
||||||
|
|
||||||
|
#include <wayland-client.h>
|
||||||
|
#include <wayland-egl.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
class WaylandEglWindow : public WaylandWindow
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WaylandEglWindow(struct wl_compositor *compositor, struct xdg_wm_base *xdg_base,
|
||||||
|
const EGLUtil<struct wl_display, struct wl_egl_window> *egl, std::unique_ptr<Image> new_image,
|
||||||
|
WaylandConfig *new_config, struct XdgStructAgg *xdg_agg);
|
||||||
|
~WaylandEglWindow() override;
|
||||||
|
static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial);
|
||||||
|
static void wl_surface_frame_done(void *data, struct wl_callback *callback, uint32_t time);
|
||||||
|
|
||||||
|
void draw() override;
|
||||||
|
void generate_frame() override;
|
||||||
|
void show() override;
|
||||||
|
void hide() override;
|
||||||
|
|
||||||
|
void finish_init() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct wl_compositor *compositor;
|
||||||
|
struct xdg_wm_base *xdg_base;
|
||||||
|
|
||||||
|
struct wl_surface *surface = nullptr;
|
||||||
|
struct xdg_surface *xdg_surface = nullptr;
|
||||||
|
struct xdg_toplevel *xdg_toplevel = nullptr;
|
||||||
|
struct wl_callback *callback;
|
||||||
|
|
||||||
|
std::unique_ptr<Image> image;
|
||||||
|
WaylandConfig *config;
|
||||||
|
|
||||||
|
EGLSurface egl_surface;
|
||||||
|
EGLContext egl_context;
|
||||||
|
|
||||||
|
struct wl_egl_window *egl_window = nullptr;
|
||||||
|
const EGLUtil<struct wl_display, struct wl_egl_window> *egl;
|
||||||
|
|
||||||
|
GLuint texture;
|
||||||
|
GLuint fbo;
|
||||||
|
|
||||||
|
std::mutex draw_mutex;
|
||||||
|
std::mutex egl_mutex;
|
||||||
|
|
||||||
|
std::string appid;
|
||||||
|
void *this_ptr;
|
||||||
|
struct XdgStructAgg *xdg_agg;
|
||||||
|
bool visible = false;
|
||||||
|
|
||||||
|
void move_window();
|
||||||
|
void delete_wayland_structs();
|
||||||
|
void delete_xdg_structs();
|
||||||
|
|
||||||
|
void opengl_cleanup();
|
||||||
|
void xdg_setup();
|
||||||
|
void setup_listeners();
|
||||||
|
void opengl_setup();
|
||||||
|
void load_framebuffer();
|
||||||
|
};
|
||||||
|
#endif
|
||||||
186
src/canvas/wayland/window/waylandshm.cpp
Normal file
186
src/canvas/wayland/window/waylandshm.cpp
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "waylandshm.hpp"
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "shm.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
constexpr int id_len = 10;
|
||||||
|
|
||||||
|
constexpr struct xdg_surface_listener xdg_surface_listener = {
|
||||||
|
.configure = WaylandShmWindow::xdg_surface_configure,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr struct wl_callback_listener frame_listener = {.done = WaylandShmWindow::wl_surface_frame_done};
|
||||||
|
|
||||||
|
void WaylandShmWindow::xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial)
|
||||||
|
{
|
||||||
|
xdg_surface_ack_configure(xdg_surface, serial);
|
||||||
|
const auto *tmp = static_cast<struct XdgStruct *>(data);
|
||||||
|
const auto window = tmp->ptr.lock();
|
||||||
|
if (!window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *shm_window = dynamic_cast<WaylandShmWindow *>(window.get());
|
||||||
|
shm_window->wl_draw(shm_window->output_scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShmWindow::wl_surface_frame_done(void *data, struct wl_callback *callback, [[maybe_unused]] uint32_t time)
|
||||||
|
{
|
||||||
|
wl_callback_destroy(callback);
|
||||||
|
const auto *tmp = static_cast<struct XdgStruct *>(data);
|
||||||
|
const auto window = tmp->ptr.lock();
|
||||||
|
if (!window) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *shm_window = dynamic_cast<WaylandShmWindow *>(window.get());
|
||||||
|
const std::scoped_lock lock{shm_window->draw_mutex};
|
||||||
|
if (!shm_window->visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
shm_window->generate_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
WaylandShmWindow::WaylandShmWindow(WaylandCanvas *canvas, std::unique_ptr<Image> new_image,
|
||||||
|
struct XdgStructAgg *xdg_agg, WaylandConfig *config)
|
||||||
|
: config(config),
|
||||||
|
xdg_base(canvas->xdg_base),
|
||||||
|
surface(wl_compositor_create_surface(canvas->compositor)),
|
||||||
|
xdg_surface(xdg_wm_base_get_xdg_surface(xdg_base, surface)),
|
||||||
|
xdg_toplevel(xdg_surface_get_toplevel(xdg_surface)),
|
||||||
|
image(std::move(new_image)),
|
||||||
|
appid(fmt::format("ueberzugpp_{}", util::generate_random_string(id_len))),
|
||||||
|
xdg_agg(xdg_agg)
|
||||||
|
{
|
||||||
|
config->initial_setup(appid);
|
||||||
|
xdg_setup();
|
||||||
|
output_scale = canvas->output_info.at(config->get_focused_output_name());
|
||||||
|
shm = std::make_unique<WaylandShm>(image->width(), image->height(), output_scale, canvas->wl_shm);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShmWindow::finish_init()
|
||||||
|
{
|
||||||
|
auto xdg = std::make_unique<XdgStruct>();
|
||||||
|
xdg->ptr = weak_from_this();
|
||||||
|
this_ptr = xdg.get();
|
||||||
|
xdg_agg->ptrs.push_back(std::move(xdg));
|
||||||
|
|
||||||
|
setup_listeners();
|
||||||
|
visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShmWindow::setup_listeners()
|
||||||
|
{
|
||||||
|
xdg_surface_add_listener(xdg_surface, &xdg_surface_listener, this_ptr);
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
|
||||||
|
if (image->is_animated()) {
|
||||||
|
callback = wl_surface_frame(surface);
|
||||||
|
wl_callback_add_listener(callback, &frame_listener, this_ptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShmWindow::xdg_setup()
|
||||||
|
{
|
||||||
|
xdg_toplevel_set_app_id(xdg_toplevel, appid.c_str());
|
||||||
|
xdg_toplevel_set_title(xdg_toplevel, appid.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
WaylandShmWindow::~WaylandShmWindow()
|
||||||
|
{
|
||||||
|
delete_xdg_structs();
|
||||||
|
delete_wayland_structs();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShmWindow::wl_draw(int32_t scale_factor)
|
||||||
|
{
|
||||||
|
std::memcpy(shm->pool_data, image->data(), image->size());
|
||||||
|
wl_surface_attach(surface, shm->buffer, 0, 0);
|
||||||
|
wl_surface_set_buffer_scale(surface, scale_factor);
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
move_window();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShmWindow::show()
|
||||||
|
{
|
||||||
|
if (visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visible = true;
|
||||||
|
xdg_surface = xdg_wm_base_get_xdg_surface(xdg_base, surface);
|
||||||
|
xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
|
||||||
|
xdg_setup();
|
||||||
|
setup_listeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShmWindow::hide()
|
||||||
|
{
|
||||||
|
if (!visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visible = false;
|
||||||
|
const std::scoped_lock lock{draw_mutex};
|
||||||
|
delete_xdg_structs();
|
||||||
|
wl_surface_attach(surface, nullptr, 0, 0);
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShmWindow::delete_xdg_structs()
|
||||||
|
{
|
||||||
|
if (xdg_toplevel != nullptr) {
|
||||||
|
xdg_toplevel_destroy(xdg_toplevel);
|
||||||
|
xdg_toplevel = nullptr;
|
||||||
|
}
|
||||||
|
if (xdg_surface != nullptr) {
|
||||||
|
xdg_surface_destroy(xdg_surface);
|
||||||
|
xdg_surface = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShmWindow::delete_wayland_structs()
|
||||||
|
{
|
||||||
|
if (surface != nullptr) {
|
||||||
|
wl_surface_destroy(surface);
|
||||||
|
surface = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShmWindow::move_window()
|
||||||
|
{
|
||||||
|
const auto dims = image->dimensions();
|
||||||
|
const auto cur_window = config->get_window_info();
|
||||||
|
const int wayland_x = dims.xpixels() + dims.padding_horizontal;
|
||||||
|
const int wayland_y = dims.ypixels() + dims.padding_vertical;
|
||||||
|
const int xcoord = cur_window.x + wayland_x;
|
||||||
|
const int ycoord = cur_window.y + wayland_y;
|
||||||
|
config->move_window(appid, xcoord, ycoord);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaylandShmWindow::generate_frame()
|
||||||
|
{
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(image->frame_delay()));
|
||||||
|
callback = wl_surface_frame(surface);
|
||||||
|
wl_callback_add_listener(callback, &frame_listener, this_ptr);
|
||||||
|
|
||||||
|
image->next_frame();
|
||||||
|
std::memcpy(shm->pool_data, image->data(), image->size());
|
||||||
|
wl_surface_attach(surface, shm->buffer, 0, 0);
|
||||||
|
wl_surface_damage_buffer(surface, 0, 0, image->width(), image->height());
|
||||||
|
wl_surface_commit(surface);
|
||||||
|
}
|
||||||
77
src/canvas/wayland/window/waylandshm.hpp
Normal file
77
src/canvas/wayland/window/waylandshm.hpp
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef WAYLAND_SHM_WINDOW_H
|
||||||
|
#define WAYLAND_SHM_WINDOW_H
|
||||||
|
|
||||||
|
#include "../wayland.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "shm.hpp"
|
||||||
|
#include "wayland-xdg-shell-client-protocol.h"
|
||||||
|
#include "waylandwindow.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <wayland-client.h>
|
||||||
|
|
||||||
|
class WaylandShmWindow : public WaylandWindow
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WaylandShmWindow(WaylandCanvas *canvas, std::unique_ptr<Image> new_image, struct XdgStructAgg *xdg_agg,
|
||||||
|
WaylandConfig *config);
|
||||||
|
~WaylandShmWindow() override;
|
||||||
|
static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial);
|
||||||
|
static void wl_surface_frame_done(void *data, struct wl_callback *callback, uint32_t time);
|
||||||
|
|
||||||
|
void draw() override {}
|
||||||
|
void wl_draw(int32_t scale_factor) override;
|
||||||
|
void generate_frame() override;
|
||||||
|
void show() override;
|
||||||
|
void hide() override;
|
||||||
|
|
||||||
|
void finish_init() override;
|
||||||
|
|
||||||
|
std::mutex draw_mutex;
|
||||||
|
std::atomic<bool> visible{false};
|
||||||
|
std::unique_ptr<WaylandShm> shm;
|
||||||
|
int32_t output_scale;
|
||||||
|
|
||||||
|
private:
|
||||||
|
WaylandConfig *config;
|
||||||
|
|
||||||
|
struct xdg_wm_base *xdg_base = nullptr;
|
||||||
|
struct wl_surface *surface = nullptr;
|
||||||
|
struct xdg_surface *xdg_surface = nullptr;
|
||||||
|
struct xdg_toplevel *xdg_toplevel = nullptr;
|
||||||
|
struct wl_callback *callback;
|
||||||
|
|
||||||
|
std::unique_ptr<Image> image;
|
||||||
|
std::string appid;
|
||||||
|
|
||||||
|
struct XdgStructAgg *xdg_agg;
|
||||||
|
void *this_ptr;
|
||||||
|
|
||||||
|
void move_window();
|
||||||
|
void xdg_setup();
|
||||||
|
|
||||||
|
void setup_listeners();
|
||||||
|
void delete_wayland_structs();
|
||||||
|
void delete_xdg_structs();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
46
src/canvas/wayland/window/waylandwindow.hpp
Normal file
46
src/canvas/wayland/window/waylandwindow.hpp
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef WAYLAND_WINDOW_H
|
||||||
|
#define WAYLAND_WINDOW_H
|
||||||
|
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class WaylandWindow:
|
||||||
|
public Window,
|
||||||
|
public std::enable_shared_from_this<WaylandWindow>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
~WaylandWindow() override = default;
|
||||||
|
|
||||||
|
virtual void wl_draw([[maybe_unused]] int32_t scale_factor) {};
|
||||||
|
virtual void finish_init() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct XdgStruct
|
||||||
|
{
|
||||||
|
std::weak_ptr<WaylandWindow> ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct XdgStructAgg
|
||||||
|
{
|
||||||
|
std::vector<std::unique_ptr<XdgStruct>> ptrs;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
119
src/canvas/x11/window/x11.cpp
Normal file
119
src/canvas/x11/window/x11.cpp
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "x11.hpp"
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
|
constexpr std::string_view win_name = "ueberzugpp";
|
||||||
|
|
||||||
|
X11Window::X11Window(xcb_connection_t *connection, xcb_screen_t *screen, xcb_window_t window, xcb_window_t parent,
|
||||||
|
std::shared_ptr<Image> image)
|
||||||
|
: connection(connection),
|
||||||
|
screen(screen),
|
||||||
|
window(window),
|
||||||
|
parent(parent),
|
||||||
|
gc(xcb_generate_id(connection)),
|
||||||
|
image(std::move(image))
|
||||||
|
{
|
||||||
|
logger = spdlog::get("X11");
|
||||||
|
create();
|
||||||
|
change_title();
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Window::create()
|
||||||
|
{
|
||||||
|
const uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
|
||||||
|
struct xcb_create_window_value_list_t value_list;
|
||||||
|
value_list.background_pixel = screen->black_pixel;
|
||||||
|
value_list.border_pixel = screen->black_pixel;
|
||||||
|
value_list.event_mask = XCB_EVENT_MASK_EXPOSURE;
|
||||||
|
value_list.colormap = screen->default_colormap;
|
||||||
|
|
||||||
|
const auto dimensions = image->dimensions();
|
||||||
|
const auto xcoord = static_cast<int16_t>(dimensions.xpixels() + dimensions.padding_horizontal);
|
||||||
|
const auto ycoord = static_cast<int16_t>(dimensions.ypixels() + dimensions.padding_vertical);
|
||||||
|
xcb_create_window_aux(connection, screen->root_depth, window, this->parent, xcoord, ycoord, image->width(),
|
||||||
|
image->height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, value_mask,
|
||||||
|
&value_list);
|
||||||
|
|
||||||
|
xcb_create_gc(connection, gc, window, 0, nullptr);
|
||||||
|
logger->debug("Created child window {} at ({},{}) with parent {}", window, xcoord, ycoord, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Window::change_title()
|
||||||
|
{
|
||||||
|
const int bits_in_char = 8;
|
||||||
|
xcb_change_property(connection, XCB_PROP_MODE_REPLACE, window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, bits_in_char,
|
||||||
|
win_name.size(), win_name.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Window::show()
|
||||||
|
{
|
||||||
|
if (visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visible = true;
|
||||||
|
xcb_map_window(connection, window);
|
||||||
|
xcb_flush(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Window::hide()
|
||||||
|
{
|
||||||
|
if (!visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visible = false;
|
||||||
|
xcb_unmap_window(connection, window);
|
||||||
|
xcb_flush(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Window::draw()
|
||||||
|
{
|
||||||
|
if (!xcb_image) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
xcb_image_put(connection, window, gc, xcb_image.get(), 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Window::generate_frame()
|
||||||
|
{
|
||||||
|
xcb_image.reset(xcb_image_create_native(connection, image->width(), image->height(), XCB_IMAGE_FORMAT_Z_PIXMAP,
|
||||||
|
screen->root_depth, nullptr, 0, nullptr));
|
||||||
|
xcb_image->data = const_cast<unsigned char *>(image->data());
|
||||||
|
send_expose_event();
|
||||||
|
}
|
||||||
|
|
||||||
|
X11Window::~X11Window()
|
||||||
|
{
|
||||||
|
xcb_destroy_window(connection, window);
|
||||||
|
xcb_free_gc(connection, gc);
|
||||||
|
xcb_flush(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Window::send_expose_event()
|
||||||
|
{
|
||||||
|
const int event_size = 32;
|
||||||
|
std::array<char, event_size> buffer;
|
||||||
|
auto *event = reinterpret_cast<xcb_expose_event_t *>(buffer.data());
|
||||||
|
event->response_type = XCB_EXPOSE;
|
||||||
|
event->window = window;
|
||||||
|
xcb_send_event(connection, 0, window, XCB_EVENT_MASK_EXPOSURE, reinterpret_cast<char *>(event));
|
||||||
|
xcb_flush(connection);
|
||||||
|
}
|
||||||
60
src/canvas/x11/window/x11.hpp
Normal file
60
src/canvas/x11/window/x11.hpp
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef X11_WINDOW_H
|
||||||
|
#define X11_WINDOW_H
|
||||||
|
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "util/ptr.hpp"
|
||||||
|
#include "window.hpp"
|
||||||
|
|
||||||
|
#include <xcb/xcb_image.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
class Dimensions;
|
||||||
|
|
||||||
|
class X11Window : public Window
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
X11Window(xcb_connection_t* connection, xcb_screen_t *screen,
|
||||||
|
xcb_window_t window, xcb_window_t parent, std::shared_ptr<Image> image);
|
||||||
|
~X11Window() override;
|
||||||
|
|
||||||
|
void draw() override;
|
||||||
|
void generate_frame() override;
|
||||||
|
void show() override;
|
||||||
|
void hide() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
xcb_connection_t *connection;
|
||||||
|
xcb_screen_t *screen;
|
||||||
|
|
||||||
|
xcb_window_t window;
|
||||||
|
xcb_window_t parent;
|
||||||
|
xcb_gcontext_t gc;
|
||||||
|
|
||||||
|
c_unique_ptr<xcb_image_t, xcb_image_destroy> xcb_image;
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
std::shared_ptr<Image> image;
|
||||||
|
|
||||||
|
bool visible = false;
|
||||||
|
|
||||||
|
void send_expose_event();
|
||||||
|
void create();
|
||||||
|
void change_title();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
138
src/canvas/x11/window/x11egl.cpp
Normal file
138
src/canvas/x11/window/x11egl.cpp
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "x11egl.hpp"
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
X11EGLWindow::X11EGLWindow(xcb_connection_t *connection, xcb_screen_t *screen, xcb_window_t windowid,
|
||||||
|
xcb_window_t parentid, const EGLUtil<xcb_connection_t, xcb_window_t> *egl,
|
||||||
|
std::shared_ptr<Image> new_image)
|
||||||
|
: connection(connection),
|
||||||
|
screen(screen),
|
||||||
|
windowid(windowid),
|
||||||
|
parentid(parentid),
|
||||||
|
image(std::move(new_image)),
|
||||||
|
egl(egl)
|
||||||
|
{
|
||||||
|
logger = spdlog::get("x11");
|
||||||
|
create();
|
||||||
|
opengl_setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
X11EGLWindow::~X11EGLWindow()
|
||||||
|
{
|
||||||
|
egl->run_contained(egl_surface, egl_context, [this] {
|
||||||
|
glDeleteTextures(1, &texture);
|
||||||
|
glDeleteFramebuffers(1, &fbo);
|
||||||
|
});
|
||||||
|
eglDestroySurface(egl->display, egl_surface);
|
||||||
|
eglDestroyContext(egl->display, egl_context);
|
||||||
|
|
||||||
|
xcb_destroy_window(connection, windowid);
|
||||||
|
xcb_flush(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11EGLWindow::create()
|
||||||
|
{
|
||||||
|
const uint32_t value_mask = XCB_CW_BACK_PIXEL | XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK | XCB_CW_COLORMAP;
|
||||||
|
struct xcb_create_window_value_list_t value_list;
|
||||||
|
value_list.background_pixel = screen->black_pixel;
|
||||||
|
value_list.border_pixel = screen->black_pixel;
|
||||||
|
value_list.event_mask = XCB_EVENT_MASK_EXPOSURE;
|
||||||
|
value_list.colormap = screen->default_colormap;
|
||||||
|
|
||||||
|
const auto dimensions = image->dimensions();
|
||||||
|
const auto xcoord = static_cast<int16_t>(dimensions.xpixels() + dimensions.padding_horizontal);
|
||||||
|
const auto ycoord = static_cast<int16_t>(dimensions.ypixels() + dimensions.padding_vertical);
|
||||||
|
xcb_create_window_aux(connection, screen->root_depth, windowid, parentid, xcoord, ycoord, image->width(),
|
||||||
|
image->height(), 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, screen->root_visual, value_mask,
|
||||||
|
&value_list);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11EGLWindow::opengl_setup()
|
||||||
|
{
|
||||||
|
egl_surface = egl->create_surface(&windowid);
|
||||||
|
if (egl_surface == EGL_NO_SURFACE) {
|
||||||
|
throw std::runtime_error("");
|
||||||
|
}
|
||||||
|
|
||||||
|
egl_context = egl->create_context(egl_surface);
|
||||||
|
if (egl_context == EGL_NO_CONTEXT) {
|
||||||
|
throw std::runtime_error("");
|
||||||
|
}
|
||||||
|
|
||||||
|
egl->run_contained(egl_surface, egl_context, [this] {
|
||||||
|
glGenFramebuffers(1, &fbo);
|
||||||
|
glGenTextures(1, &texture);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11EGLWindow::draw()
|
||||||
|
{
|
||||||
|
const std::scoped_lock lock{egl_mutex};
|
||||||
|
egl->run_contained(egl_surface, egl_context, [this] {
|
||||||
|
glBlitFramebuffer(0, 0, image->width(), image->height(), 0, 0, image->width(), image->height(),
|
||||||
|
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||||
|
eglSwapBuffers(egl->display, egl_surface);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11EGLWindow::generate_frame()
|
||||||
|
{
|
||||||
|
const std::scoped_lock lock{egl_mutex};
|
||||||
|
egl->run_contained(egl_surface, egl_context, [this] {
|
||||||
|
egl->get_texture_from_image(*image, texture);
|
||||||
|
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
|
||||||
|
glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
send_expose_event();
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11EGLWindow::show()
|
||||||
|
{
|
||||||
|
if (visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visible = true;
|
||||||
|
xcb_map_window(connection, windowid);
|
||||||
|
xcb_flush(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11EGLWindow::hide()
|
||||||
|
{
|
||||||
|
if (!visible) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
visible = false;
|
||||||
|
xcb_unmap_window(connection, windowid);
|
||||||
|
xcb_flush(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11EGLWindow::send_expose_event()
|
||||||
|
{
|
||||||
|
const int event_size = 32;
|
||||||
|
std::array<char, event_size> buffer;
|
||||||
|
auto *event = reinterpret_cast<xcb_expose_event_t *>(buffer.data());
|
||||||
|
event->response_type = XCB_EXPOSE;
|
||||||
|
event->window = windowid;
|
||||||
|
xcb_send_event(connection, 0, windowid, XCB_EVENT_MASK_EXPOSURE, reinterpret_cast<char *>(event));
|
||||||
|
xcb_flush(connection);
|
||||||
|
}
|
||||||
68
src/canvas/x11/window/x11egl.hpp
Normal file
68
src/canvas/x11/window/x11egl.hpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef X11_EGL_WINDOW_H
|
||||||
|
#define X11_EGL_WINDOW_H
|
||||||
|
|
||||||
|
#include "window.hpp"
|
||||||
|
#include "util/egl.hpp"
|
||||||
|
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
class Image;
|
||||||
|
|
||||||
|
class X11EGLWindow : public Window
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
X11EGLWindow(xcb_connection_t* connection, xcb_screen_t* screen,
|
||||||
|
xcb_window_t windowid, xcb_window_t parentid, const EGLUtil<xcb_connection_t, xcb_window_t>* egl,
|
||||||
|
std::shared_ptr<Image> new_image);
|
||||||
|
~X11EGLWindow() override;
|
||||||
|
|
||||||
|
void draw() override;
|
||||||
|
void generate_frame() override;
|
||||||
|
void show() override;
|
||||||
|
void hide() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
xcb_connection_t* connection;
|
||||||
|
xcb_screen_t* screen;
|
||||||
|
xcb_window_t windowid;
|
||||||
|
xcb_window_t parentid;
|
||||||
|
std::shared_ptr<Image> image;
|
||||||
|
const EGLUtil<xcb_connection_t, xcb_window_t>* egl;
|
||||||
|
|
||||||
|
GLuint texture;
|
||||||
|
GLuint fbo;
|
||||||
|
EGLContext egl_context;
|
||||||
|
EGLSurface egl_surface;
|
||||||
|
|
||||||
|
std::mutex egl_mutex;
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
|
||||||
|
bool visible = false;
|
||||||
|
|
||||||
|
void send_expose_event();
|
||||||
|
void create();
|
||||||
|
void change_title();
|
||||||
|
void opengl_setup();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
257
src/canvas/x11/x11.cpp
Normal file
257
src/canvas/x11/x11.cpp
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "x11.hpp"
|
||||||
|
#include "application.hpp"
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "tmux.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <range/v3/all.hpp>
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
# include "window/x11egl.hpp"
|
||||||
|
# include <EGL/eglext.h>
|
||||||
|
#endif
|
||||||
|
#include "window/x11.hpp"
|
||||||
|
|
||||||
|
X11Canvas::X11Canvas()
|
||||||
|
: connection(xcb_connect(nullptr, nullptr))
|
||||||
|
{
|
||||||
|
if (xcb_connection_has_error(connection) > 0) {
|
||||||
|
throw std::runtime_error("Can't connect to X11 server");
|
||||||
|
}
|
||||||
|
screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
|
||||||
|
|
||||||
|
#ifdef ENABLE_XCB_ERRORS
|
||||||
|
xcb_errors_context_new(connection, &err_ctx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
flags = Flags::instance();
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
if (flags->use_opengl) {
|
||||||
|
try {
|
||||||
|
egl = std::make_unique<EGLUtil<xcb_connection_t, xcb_window_t>>(EGL_PLATFORM_XCB_EXT, connection);
|
||||||
|
} catch (const std::runtime_error &err) {
|
||||||
|
egl_available = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
egl_available = false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
xutil = std::make_unique<X11Util>(connection);
|
||||||
|
logger = spdlog::get("X11");
|
||||||
|
event_handler = std::thread(&X11Canvas::handle_events, this);
|
||||||
|
logger->info("Canvas created");
|
||||||
|
}
|
||||||
|
|
||||||
|
X11Canvas::~X11Canvas()
|
||||||
|
{
|
||||||
|
draw_threads.clear();
|
||||||
|
windows.clear();
|
||||||
|
image_windows.clear();
|
||||||
|
|
||||||
|
if (event_handler.joinable()) {
|
||||||
|
event_handler.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_XCB_ERRORS
|
||||||
|
xcb_errors_context_free(err_ctx);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
xcb_disconnect(connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Canvas::draw(const std::string &identifier)
|
||||||
|
{
|
||||||
|
if (!images.at(identifier)->is_animated()) {
|
||||||
|
for (const auto &[wid, window] : image_windows.at(identifier)) {
|
||||||
|
window->generate_frame();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_threads.insert_or_assign(identifier, std::jthread([this, identifier](const std::stop_token &stoken) {
|
||||||
|
const auto image = images.at(identifier);
|
||||||
|
const auto wins = image_windows.at(identifier);
|
||||||
|
while (!stoken.stop_requested()) {
|
||||||
|
for (const auto &[wid, window] : wins) {
|
||||||
|
window->generate_frame();
|
||||||
|
}
|
||||||
|
image->next_frame();
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(image->frame_delay()));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Canvas::show()
|
||||||
|
{
|
||||||
|
const std::scoped_lock lock{windows_mutex};
|
||||||
|
for (const auto &[wid, window] : windows) {
|
||||||
|
window->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Canvas::hide()
|
||||||
|
{
|
||||||
|
const std::scoped_lock lock{windows_mutex};
|
||||||
|
for (const auto &[wid, window] : windows) {
|
||||||
|
window->hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Canvas::handle_events()
|
||||||
|
{
|
||||||
|
const int event_mask = 0x80;
|
||||||
|
const int waitms = 100;
|
||||||
|
const int connfd = xcb_get_file_descriptor(connection);
|
||||||
|
bool status = false;
|
||||||
|
|
||||||
|
while (!Application::stop_flag) {
|
||||||
|
try {
|
||||||
|
status = os::wait_for_data_on_fd(connfd, waitms);
|
||||||
|
} catch (const std::system_error &err) {
|
||||||
|
Application::stop_flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::scoped_lock lock{windows_mutex};
|
||||||
|
auto event = unique_C_ptr<xcb_generic_event_t>{xcb_poll_for_event(connection)};
|
||||||
|
while (event) {
|
||||||
|
const int real_event = event->response_type & ~event_mask;
|
||||||
|
switch (real_event) {
|
||||||
|
case 0: {
|
||||||
|
const auto *err = reinterpret_cast<xcb_generic_error_t *>(event.get());
|
||||||
|
print_xcb_error(err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case XCB_EXPOSE: {
|
||||||
|
const auto *expose = reinterpret_cast<xcb_expose_event_t *>(event.get());
|
||||||
|
try {
|
||||||
|
logger->debug("Received expose event for window {}", expose->window);
|
||||||
|
const auto window = windows.at(expose->window);
|
||||||
|
window->draw();
|
||||||
|
} catch (const std::out_of_range &oor) {
|
||||||
|
logger->debug("Discarding expose event for window {}", expose->window);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
logger->debug("Received unknown event {}", real_event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
event.reset(xcb_poll_for_event(connection));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Canvas::add_image(const std::string &identifier, std::unique_ptr<Image> new_image)
|
||||||
|
{
|
||||||
|
remove_image(identifier);
|
||||||
|
|
||||||
|
logger->debug("Initializing canvas");
|
||||||
|
images.insert({identifier, std::move(new_image)});
|
||||||
|
image_windows.insert({identifier, {}});
|
||||||
|
|
||||||
|
const auto image = images.at(identifier);
|
||||||
|
const auto dims = image->dimensions();
|
||||||
|
std::unordered_set<xcb_window_t> parent_ids{dims.terminal->x11_wid};
|
||||||
|
get_tmux_window_ids(parent_ids);
|
||||||
|
|
||||||
|
ranges::for_each(parent_ids, [this, &identifier, &image](xcb_window_t parent) {
|
||||||
|
const auto window_id = xcb_generate_id(connection);
|
||||||
|
std::shared_ptr<Window> window;
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
if (egl_available) {
|
||||||
|
try {
|
||||||
|
window = std::make_shared<X11EGLWindow>(connection, screen, window_id, parent, egl.get(), image);
|
||||||
|
} catch (const std::runtime_error &err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (window == nullptr) {
|
||||||
|
window = std::make_shared<X11Window>(connection, screen, window_id, parent, image);
|
||||||
|
}
|
||||||
|
windows.insert({window_id, window});
|
||||||
|
image_windows.at(identifier).insert({window_id, window});
|
||||||
|
window->show();
|
||||||
|
});
|
||||||
|
|
||||||
|
draw(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Canvas::get_tmux_window_ids(std::unordered_set<xcb_window_t> &windows)
|
||||||
|
{
|
||||||
|
const auto pids = tmux::get_client_pids();
|
||||||
|
if (!pids.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto pid_window_map = xutil->get_pid_window_map();
|
||||||
|
for (const auto pid : pids.value()) {
|
||||||
|
const auto ppids = util::get_process_tree(pid);
|
||||||
|
for (const auto ppid : ppids) {
|
||||||
|
const auto win = pid_window_map.find(ppid);
|
||||||
|
if (win == pid_window_map.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
windows.insert(win->second);
|
||||||
|
break; // prevent multiple windows being created
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Canvas::print_xcb_error(const xcb_generic_error_t *err)
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_XCB_ERRORS
|
||||||
|
const char *extension = nullptr;
|
||||||
|
const char *major = xcb_errors_get_name_for_major_code(err_ctx, err->major_code);
|
||||||
|
const char *minor = xcb_errors_get_name_for_minor_code(err_ctx, err->major_code, err->minor_code);
|
||||||
|
const char *error = xcb_errors_get_name_for_error(err_ctx, err->error_code, &extension);
|
||||||
|
|
||||||
|
const std::string_view ext_str = extension != nullptr ? extension : "no_extension";
|
||||||
|
const std::string_view minor_str = minor != nullptr ? minor : "no_minor";
|
||||||
|
logger->error("XCB: {}:{}, {}:{}, resource {} sequence {}", error, ext_str, major, minor_str, err->resource_id,
|
||||||
|
err->sequence);
|
||||||
|
#else
|
||||||
|
logger->error("XCB: resource {} sequence {}", err->resource_id, err->sequence);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void X11Canvas::remove_image(const std::string &identifier)
|
||||||
|
{
|
||||||
|
draw_threads.erase(identifier);
|
||||||
|
images.erase(identifier);
|
||||||
|
|
||||||
|
const std::scoped_lock lock{windows_mutex};
|
||||||
|
const auto old_windows = image_windows.extract(identifier);
|
||||||
|
if (old_windows.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const auto &[key, value] : old_windows.mapped()) {
|
||||||
|
windows.erase(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
93
src/canvas/x11/x11.hpp
Normal file
93
src/canvas/x11/x11.hpp
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef X11_CANVAS_H
|
||||||
|
#define X11_CANVAS_H
|
||||||
|
|
||||||
|
#include "canvas.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "window.hpp"
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "util/x11.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <thread>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#ifdef ENABLE_XCB_ERRORS
|
||||||
|
# include <xcb/xcb_errors.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
# include "util/egl.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
class Flags;
|
||||||
|
|
||||||
|
class X11Canvas : public Canvas
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit X11Canvas();
|
||||||
|
~X11Canvas() override;
|
||||||
|
|
||||||
|
void add_image(const std::string& identifier, std::unique_ptr<Image> new_image) override;
|
||||||
|
void remove_image(const std::string& identifier) override;
|
||||||
|
void hide() override;
|
||||||
|
void show() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
xcb_connection_t *connection;
|
||||||
|
xcb_screen_t *screen;
|
||||||
|
|
||||||
|
#ifdef ENABLE_XCB_ERRORS
|
||||||
|
xcb_errors_context_t *err_ctx;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
std::unique_ptr<X11Util> xutil;
|
||||||
|
|
||||||
|
// map for event handler
|
||||||
|
std::unordered_map<xcb_window_t, std::shared_ptr<Window>> windows;
|
||||||
|
|
||||||
|
// windows per image
|
||||||
|
std::unordered_map<std::string,
|
||||||
|
std::unordered_map<xcb_window_t, std::shared_ptr<Window>>> image_windows;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::shared_ptr<Image>> images;
|
||||||
|
std::unordered_map<std::string, std::jthread> draw_threads;
|
||||||
|
|
||||||
|
std::thread event_handler;
|
||||||
|
std::mutex windows_mutex;
|
||||||
|
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
std::shared_ptr<Flags> flags;
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
std::unique_ptr<EGLUtil<xcb_connection_t, xcb_window_t>> egl;
|
||||||
|
bool egl_available = true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void draw(const std::string& identifier);
|
||||||
|
void handle_events();
|
||||||
|
void get_tmux_window_ids(std::unordered_set<xcb_window_t>& windows);
|
||||||
|
void print_xcb_error(const xcb_generic_error_t* err);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
62
src/dimensions.cpp
Normal file
62
src/dimensions.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "tmux.hpp"
|
||||||
|
#include "terminal.hpp"
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
Dimensions::Dimensions(const Terminal* terminal, uint16_t xcoord,
|
||||||
|
uint16_t ycoord, int max_w, int max_h, std::string scaler):
|
||||||
|
max_w(max_w),
|
||||||
|
max_h(max_h),
|
||||||
|
padding_horizontal(terminal->padding_horizontal),
|
||||||
|
padding_vertical(terminal->padding_vertical),
|
||||||
|
scaler(std::move(scaler)),
|
||||||
|
terminal(terminal),
|
||||||
|
orig_x(xcoord),
|
||||||
|
orig_y(ycoord)
|
||||||
|
{
|
||||||
|
read_offsets();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Dimensions::read_offsets()
|
||||||
|
{
|
||||||
|
const auto [offset_x, offset_y] = tmux::get_offset();
|
||||||
|
x = orig_x + offset_x;
|
||||||
|
y = orig_y + offset_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Dimensions::xpixels() const -> int
|
||||||
|
{
|
||||||
|
return x * terminal->font_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Dimensions::ypixels() const -> int
|
||||||
|
{
|
||||||
|
return y * terminal->font_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Dimensions::max_wpixels() const -> int
|
||||||
|
{
|
||||||
|
return max_w * terminal->font_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Dimensions::max_hpixels() const -> int
|
||||||
|
{
|
||||||
|
return max_h * terminal->font_height;
|
||||||
|
}
|
||||||
52
src/flags.cpp
Normal file
52
src/flags.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
using json = nlohmann::json;
|
||||||
|
|
||||||
|
// read configuration file
|
||||||
|
Flags::Flags()
|
||||||
|
{
|
||||||
|
const auto home = os::getenv("HOME").value_or(util::temp_directory_path());
|
||||||
|
const auto config_home = os::getenv("XDG_CONFIG_HOME").value_or(fmt::format("{}/.config", home));
|
||||||
|
config_file = fmt::format("{}/ueberzugpp/config.json", config_home);
|
||||||
|
if (fs::exists(config_file)) {
|
||||||
|
read_config_file();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Flags::read_config_file()
|
||||||
|
{
|
||||||
|
std::ifstream ifs(config_file);
|
||||||
|
const auto data = json::parse(ifs);
|
||||||
|
if (!data.contains("layer")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto &layer = data.at("layer");
|
||||||
|
silent = layer.value("silent", false);
|
||||||
|
output = layer.value("output", "");
|
||||||
|
no_cache = layer.value("no-cache", false);
|
||||||
|
no_opencv = layer.value("no-opencv", false);
|
||||||
|
use_opengl = layer.value("opengl", false);
|
||||||
|
}
|
||||||
185
src/image.cpp
Normal file
185
src/image.cpp
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "image.hpp"
|
||||||
|
#ifdef ENABLE_OPENCV
|
||||||
|
# include "image/opencv.hpp"
|
||||||
|
#endif
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "image/libvips.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENCV
|
||||||
|
# include <opencv2/imgcodecs.hpp>
|
||||||
|
#endif
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <vips/vips.h>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
using njson = nlohmann::json;
|
||||||
|
|
||||||
|
auto Image::load(const njson &command, const Terminal *terminal) -> std::unique_ptr<Image>
|
||||||
|
{
|
||||||
|
const fs::path &filename = command.at("path");
|
||||||
|
if (!fs::exists(filename)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const auto flags = Flags::instance();
|
||||||
|
const auto logger = spdlog::get("main");
|
||||||
|
std::shared_ptr<Dimensions> dimensions;
|
||||||
|
try {
|
||||||
|
dimensions = get_dimensions(command, terminal);
|
||||||
|
} catch (const std::exception &) {
|
||||||
|
logger->error("Could not parse dimensions from command");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
std::string image_path = filename;
|
||||||
|
bool in_cache = false;
|
||||||
|
if (!flags->no_cache) {
|
||||||
|
image_path = check_cache(*dimensions, filename);
|
||||||
|
in_cache = image_path != filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENCV
|
||||||
|
if (cv::haveImageReader(image_path) && !flags->no_opencv) {
|
||||||
|
try {
|
||||||
|
return std::make_unique<OpencvImage>(dimensions, image_path, in_cache);
|
||||||
|
} catch (const std::runtime_error &) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
const auto *vips_loader = vips_foreign_find_load(image_path.c_str());
|
||||||
|
if (vips_loader != nullptr) {
|
||||||
|
try {
|
||||||
|
return std::make_unique<LibvipsImage>(dimensions, image_path, in_cache);
|
||||||
|
} catch (const vips::VError &) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Image::check_cache(const Dimensions &dimensions, const fs::path &orig_path) -> std::string
|
||||||
|
{
|
||||||
|
const fs::path cache_path = util::get_cache_file_save_location(orig_path);
|
||||||
|
if (!fs::exists(cache_path)) {
|
||||||
|
return orig_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
vips::VImage cache_img;
|
||||||
|
try {
|
||||||
|
cache_img = vips::VImage::new_from_file(cache_path.c_str());
|
||||||
|
} catch (const vips::VError &) {
|
||||||
|
return orig_path;
|
||||||
|
}
|
||||||
|
const uint32_t img_width = cache_img.width();
|
||||||
|
const uint32_t img_height = cache_img.height();
|
||||||
|
const uint32_t dim_width = dimensions.max_wpixels();
|
||||||
|
const uint32_t dim_height = dimensions.max_hpixels();
|
||||||
|
const int delta = 10;
|
||||||
|
|
||||||
|
if ((dim_width >= img_width && dim_height >= img_height) &&
|
||||||
|
((dim_width - img_width) <= delta || (dim_height - img_height) <= delta)) {
|
||||||
|
return cache_path;
|
||||||
|
}
|
||||||
|
return orig_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Image::get_new_sizes(double max_width, double max_height, std::string_view scaler, int scale_factor) const
|
||||||
|
-> std::pair<int, int>
|
||||||
|
{
|
||||||
|
int img_width = width();
|
||||||
|
int img_height = height();
|
||||||
|
int new_width = img_width;
|
||||||
|
int new_height = img_height;
|
||||||
|
double new_scale = 0;
|
||||||
|
double width_scale = 0;
|
||||||
|
double height_scale = 0;
|
||||||
|
double min_scale = 0;
|
||||||
|
double max_scale = 0;
|
||||||
|
|
||||||
|
if (scaler == "fit_contain" || scaler == "forced_cover") {
|
||||||
|
// I believe these should work the same
|
||||||
|
new_scale = max_height / img_height;
|
||||||
|
if (img_width >= img_height) {
|
||||||
|
new_scale = max_width / img_width;
|
||||||
|
}
|
||||||
|
new_width = static_cast<int>(img_width * new_scale);
|
||||||
|
new_height = static_cast<int>(img_height * new_scale);
|
||||||
|
new_scale = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_height > max_height) {
|
||||||
|
if (new_width > max_width) {
|
||||||
|
width_scale = max_width / new_width;
|
||||||
|
height_scale = max_height / new_height;
|
||||||
|
min_scale = std::min(width_scale, height_scale);
|
||||||
|
max_scale = std::max(width_scale, height_scale);
|
||||||
|
if (new_width * max_scale <= max_width && new_height * max_scale <= max_height) {
|
||||||
|
new_scale = max_scale;
|
||||||
|
} else {
|
||||||
|
new_scale = min_scale;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
new_scale = max_height / new_height;
|
||||||
|
}
|
||||||
|
} else if (new_width > max_width) {
|
||||||
|
new_scale = max_width / new_width;
|
||||||
|
}
|
||||||
|
if (new_scale != 1) {
|
||||||
|
new_width = static_cast<int>(new_width * new_scale);
|
||||||
|
new_height = static_cast<int>(new_height * new_scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::make_pair(util::round_up(new_width, scale_factor), util::round_up(new_height, scale_factor));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Image::get_dimensions(const njson &json, const Terminal *terminal) -> std::shared_ptr<Dimensions>
|
||||||
|
{
|
||||||
|
using std::string;
|
||||||
|
int xcoord = 0;
|
||||||
|
int ycoord = 0;
|
||||||
|
int max_width = 0;
|
||||||
|
int max_height = 0;
|
||||||
|
string width_key = "max_width";
|
||||||
|
string height_key = "max_height";
|
||||||
|
const string scaler = json.value("scaler", "contain");
|
||||||
|
if (json.contains("width")) {
|
||||||
|
width_key = "width";
|
||||||
|
height_key = "height";
|
||||||
|
}
|
||||||
|
if (json.at(width_key).is_string()) {
|
||||||
|
const string &width = json.at(width_key);
|
||||||
|
const string &height = json.at(height_key);
|
||||||
|
max_width = std::stoi(width);
|
||||||
|
max_height = std::stoi(height);
|
||||||
|
} else {
|
||||||
|
max_width = json.at(width_key);
|
||||||
|
max_height = json.at(height_key);
|
||||||
|
}
|
||||||
|
if (json.at("x").is_string()) {
|
||||||
|
const string &xcoords = json.at("x");
|
||||||
|
const string &ycoords = json.at("y");
|
||||||
|
xcoord = std::stoi(xcoords);
|
||||||
|
ycoord = std::stoi(ycoords);
|
||||||
|
} else {
|
||||||
|
xcoord = json.at("x");
|
||||||
|
ycoord = json.at("y");
|
||||||
|
}
|
||||||
|
return std::make_shared<Dimensions>(terminal, xcoord, ycoord, max_width, max_height, scaler);
|
||||||
|
}
|
||||||
216
src/image/libvips.cpp
Normal file
216
src/image/libvips.cpp
Normal file
@ -0,0 +1,216 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "libvips.hpp"
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "terminal.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENCV
|
||||||
|
# include <opencv2/videoio.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using vips::VError;
|
||||||
|
using vips::VImage;
|
||||||
|
|
||||||
|
LibvipsImage::LibvipsImage(std::shared_ptr<Dimensions> new_dims, const std::string &filename, bool in_cache)
|
||||||
|
: path(filename),
|
||||||
|
dims(std::move(new_dims)),
|
||||||
|
max_width(dims->max_wpixels()),
|
||||||
|
max_height(dims->max_hpixels()),
|
||||||
|
in_cache(in_cache)
|
||||||
|
{
|
||||||
|
image = VImage::new_from_file(path.c_str()).colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
|
flags = Flags::instance();
|
||||||
|
logger = spdlog::get("vips");
|
||||||
|
logger->info("loading file {}", filename);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// animated images should have both n-pages and delay
|
||||||
|
npages = image.get_int("n-pages");
|
||||||
|
std::ignore = image.get_array_int("delay");
|
||||||
|
is_anim = true;
|
||||||
|
logger->info("file is an animated image");
|
||||||
|
auto *opts = VImage::option()->set("n", -1);
|
||||||
|
backup = VImage::new_from_file(filename.c_str(), opts).colourspace(VIPS_INTERPRETATION_sRGB);
|
||||||
|
orig_height = backup.height() / npages;
|
||||||
|
image = backup.crop(0, 0, backup.width(), orig_height);
|
||||||
|
} catch (const VError &err) {
|
||||||
|
logger->debug("Failed to process image animation");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_anim) {
|
||||||
|
image = image.autorot();
|
||||||
|
}
|
||||||
|
process_image();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::dimensions() const -> const Dimensions &
|
||||||
|
{
|
||||||
|
return *dims;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::filename() const -> std::string
|
||||||
|
{
|
||||||
|
return path.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::width() const -> int
|
||||||
|
{
|
||||||
|
return image.width();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::height() const -> int
|
||||||
|
{
|
||||||
|
return image.height();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::size() const -> size_t
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::data() const -> const unsigned char *
|
||||||
|
{
|
||||||
|
return _data.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::channels() const -> int
|
||||||
|
{
|
||||||
|
return image.bands();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::is_animated() const -> bool
|
||||||
|
{
|
||||||
|
return is_anim;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::next_frame() -> void
|
||||||
|
{
|
||||||
|
if (!is_anim) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
top += orig_height;
|
||||||
|
if (top == backup.height()) {
|
||||||
|
top = 0;
|
||||||
|
}
|
||||||
|
image = backup.crop(0, top, backup.width(), orig_height);
|
||||||
|
process_image();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::frame_delay() const -> int
|
||||||
|
{
|
||||||
|
if (!is_anim) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const auto delays = backup.get_array_int("delay");
|
||||||
|
const int ms_per_sec = 1000;
|
||||||
|
if (delays.at(0) == 0) {
|
||||||
|
#ifdef ENABLE_OPENCV
|
||||||
|
const cv::VideoCapture video(path);
|
||||||
|
if (video.isOpened()) {
|
||||||
|
return static_cast<int>((1.0 / video.get(cv::CAP_PROP_FPS)) * ms_per_sec);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return static_cast<int>((1.0 / npages) * ms_per_sec);
|
||||||
|
}
|
||||||
|
return delays.at(0);
|
||||||
|
} catch (const VError &err) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::resize_image() -> void
|
||||||
|
{
|
||||||
|
if (in_cache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto [new_width, new_height] = get_new_sizes(max_width, max_height, dims->scaler, flags->scale_factor);
|
||||||
|
if (new_width <= 0 && new_height <= 0) {
|
||||||
|
// ensure width and height are pair
|
||||||
|
if (flags->needs_scaling) {
|
||||||
|
const auto curw = width();
|
||||||
|
const auto curh = height();
|
||||||
|
if ((curw % 2) != 0 || (curh % 2) != 0) {
|
||||||
|
auto *opts = VImage::option()
|
||||||
|
->set("height", util::round_up(curh, flags->scale_factor))
|
||||||
|
->set("size", VIPS_SIZE_FORCE);
|
||||||
|
image = image.thumbnail_image(util::round_up(curw, flags->scale_factor), opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger->debug("Resizing image");
|
||||||
|
|
||||||
|
auto *opts = VImage::option()->set("height", new_height)->set("size", VIPS_SIZE_FORCE);
|
||||||
|
image = image.thumbnail_image(new_width, opts);
|
||||||
|
|
||||||
|
if (is_anim || flags->no_cache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto save_location = util::get_cache_file_save_location(path);
|
||||||
|
try {
|
||||||
|
image.write_to_file(save_location.c_str());
|
||||||
|
logger->debug("Saved resized image");
|
||||||
|
} catch (const VError &err) {
|
||||||
|
logger->debug("Could not save resized image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto LibvipsImage::process_image() -> void
|
||||||
|
{
|
||||||
|
resize_image();
|
||||||
|
if (flags->origin_center) {
|
||||||
|
const double img_width = static_cast<double>(width()) / dims->terminal->font_width;
|
||||||
|
const double img_height = static_cast<double>(height()) / dims->terminal->font_height;
|
||||||
|
dims->x -= std::floor(img_width / 2);
|
||||||
|
dims->y -= std::floor(img_height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::unordered_set<std::string_view> bgra_trifecta = {"x11", "chafa", "wayland"};
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
if (flags->use_opengl) {
|
||||||
|
image = image.flipver();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (bgra_trifecta.contains(flags->output)) {
|
||||||
|
// alpha channel required
|
||||||
|
if (!image.has_alpha()) {
|
||||||
|
const int alpha_value = 255;
|
||||||
|
image = image.bandjoin(alpha_value);
|
||||||
|
}
|
||||||
|
// convert from RGB to BGR
|
||||||
|
auto bands = image.bandsplit();
|
||||||
|
std::swap(bands[0], bands[2]);
|
||||||
|
image = VImage::bandjoin(bands);
|
||||||
|
} else if (flags->output == "sixel") {
|
||||||
|
// sixel expects RGB888
|
||||||
|
if (image.has_alpha()) {
|
||||||
|
image = image.flatten();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_size = VIPS_IMAGE_SIZEOF_IMAGE(image.get_image());
|
||||||
|
_data.reset(static_cast<unsigned char *>(image.write_to_memory(&_size)));
|
||||||
|
}
|
||||||
71
src/image/libvips.hpp
Normal file
71
src/image/libvips.hpp
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef LIBVIPS_IMAGE_H
|
||||||
|
#define LIBVIPS_IMAGE_H
|
||||||
|
|
||||||
|
#include "image.hpp"
|
||||||
|
#include "util/ptr.hpp"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
|
class LibvipsImage : public Image
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
LibvipsImage(std::shared_ptr<Dimensions> new_dims, const std::string &filename, bool in_cache);
|
||||||
|
|
||||||
|
[[nodiscard]] auto dimensions() const -> const Dimensions & override;
|
||||||
|
[[nodiscard]] auto width() const -> int override;
|
||||||
|
[[nodiscard]] auto height() const -> int override;
|
||||||
|
[[nodiscard]] auto size() const -> size_t override;
|
||||||
|
[[nodiscard]] auto data() const -> const unsigned char * override;
|
||||||
|
[[nodiscard]] auto channels() const -> int override;
|
||||||
|
|
||||||
|
void next_frame() override;
|
||||||
|
[[nodiscard]] auto frame_delay() const -> int override;
|
||||||
|
[[nodiscard]] auto is_animated() const -> bool override;
|
||||||
|
[[nodiscard]] auto filename() const -> std::string override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
vips::VImage image;
|
||||||
|
vips::VImage backup;
|
||||||
|
|
||||||
|
c_unique_ptr<unsigned char, g_free> _data;
|
||||||
|
std::filesystem::path path;
|
||||||
|
std::shared_ptr<Dimensions> dims;
|
||||||
|
|
||||||
|
std::shared_ptr<Flags> flags;
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
|
||||||
|
uint32_t max_width;
|
||||||
|
uint32_t max_height;
|
||||||
|
size_t _size = 0;
|
||||||
|
|
||||||
|
// for animated pictures
|
||||||
|
int top = 0;
|
||||||
|
int orig_height;
|
||||||
|
int npages = 0;
|
||||||
|
bool is_anim = false;
|
||||||
|
bool in_cache;
|
||||||
|
|
||||||
|
void process_image();
|
||||||
|
void resize_image();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
249
src/image/opencv.cpp
Normal file
249
src/image/opencv.cpp
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "opencv.hpp"
|
||||||
|
#include "dimensions.hpp"
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "terminal.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include <opencv2/core/ocl.hpp>
|
||||||
|
#include <opencv2/imgcodecs.hpp>
|
||||||
|
#include <opencv2/imgproc.hpp>
|
||||||
|
|
||||||
|
enum {
|
||||||
|
EXIF_ORIENTATION_2 = 2,
|
||||||
|
EXIF_ORIENTATION_3,
|
||||||
|
EXIF_ORIENTATION_4,
|
||||||
|
EXIF_ORIENTATION_5,
|
||||||
|
EXIF_ORIENTATION_6,
|
||||||
|
EXIF_ORIENTATION_7,
|
||||||
|
EXIF_ORIENTATION_8,
|
||||||
|
};
|
||||||
|
|
||||||
|
OpencvImage::OpencvImage(std::shared_ptr<Dimensions> new_dims, const std::string &filename, bool in_cache)
|
||||||
|
: path(filename),
|
||||||
|
dims(std::move(new_dims)),
|
||||||
|
max_width(dims->max_wpixels()),
|
||||||
|
max_height(dims->max_hpixels()),
|
||||||
|
in_cache(in_cache)
|
||||||
|
{
|
||||||
|
logger = spdlog::get("opencv");
|
||||||
|
image = cv::imread(filename, cv::IMREAD_UNCHANGED);
|
||||||
|
|
||||||
|
if (image.empty()) {
|
||||||
|
logger->warn("unable to read image");
|
||||||
|
throw std::runtime_error("");
|
||||||
|
}
|
||||||
|
logger->info("loading file {}", filename);
|
||||||
|
flags = Flags::instance();
|
||||||
|
|
||||||
|
rotate_image();
|
||||||
|
process_image();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto OpencvImage::filename() const -> std::string
|
||||||
|
{
|
||||||
|
return path.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto OpencvImage::dimensions() const -> const Dimensions &
|
||||||
|
{
|
||||||
|
return *dims;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto OpencvImage::width() const -> int
|
||||||
|
{
|
||||||
|
return image.cols;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto OpencvImage::height() const -> int
|
||||||
|
{
|
||||||
|
return image.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto OpencvImage::size() const -> size_t
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto OpencvImage::data() const -> const unsigned char *
|
||||||
|
{
|
||||||
|
return image.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto OpencvImage::channels() const -> int
|
||||||
|
{
|
||||||
|
return image.channels();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpencvImage::wayland_processing()
|
||||||
|
{
|
||||||
|
if (flags->output != "wayland") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpencvImage::rotate_image()
|
||||||
|
{
|
||||||
|
const auto rotation = util::read_exif_rotation(path);
|
||||||
|
if (!rotation.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto value = rotation.value();
|
||||||
|
|
||||||
|
// kudos https://jdhao.github.io/2019/07/31/image_rotation_exif_info/
|
||||||
|
switch (value) {
|
||||||
|
case EXIF_ORIENTATION_2:
|
||||||
|
cv::flip(image, image, 1);
|
||||||
|
break;
|
||||||
|
case EXIF_ORIENTATION_3:
|
||||||
|
cv::flip(image, image, -1);
|
||||||
|
break;
|
||||||
|
case EXIF_ORIENTATION_4:
|
||||||
|
cv::flip(image, image, 0);
|
||||||
|
break;
|
||||||
|
case EXIF_ORIENTATION_5:
|
||||||
|
cv::rotate(image, image, cv::ROTATE_90_CLOCKWISE);
|
||||||
|
cv::flip(image, image, 1);
|
||||||
|
break;
|
||||||
|
case EXIF_ORIENTATION_6:
|
||||||
|
cv::rotate(image, image, cv::ROTATE_90_CLOCKWISE);
|
||||||
|
break;
|
||||||
|
case EXIF_ORIENTATION_7:
|
||||||
|
cv::rotate(image, image, cv::ROTATE_90_COUNTERCLOCKWISE);
|
||||||
|
cv::flip(image, image, 1);
|
||||||
|
break;
|
||||||
|
case EXIF_ORIENTATION_8:
|
||||||
|
cv::rotate(image, image, cv::ROTATE_90_COUNTERCLOCKWISE);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// only use opencl if required
|
||||||
|
auto OpencvImage::resize_image() -> void
|
||||||
|
{
|
||||||
|
if (in_cache) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto [new_width, new_height] = get_new_sizes(max_width, max_height, dims->scaler, flags->scale_factor);
|
||||||
|
if (new_width <= 0 && new_height <= 0) {
|
||||||
|
// ensure width and height are pair
|
||||||
|
if (flags->needs_scaling) {
|
||||||
|
const auto curw = width();
|
||||||
|
const auto curh = height();
|
||||||
|
if ((curw % 2) != 0 || (curh % 2) != 0) {
|
||||||
|
resize_image_helper(image, util::round_up(curw, flags->scale_factor),
|
||||||
|
util::round_up(curh, flags->scale_factor));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto opencl_ctx = cv::ocl::Context::getDefault();
|
||||||
|
opencl_available = opencl_ctx.ptr() != nullptr;
|
||||||
|
|
||||||
|
if (opencl_available) {
|
||||||
|
logger->debug("OpenCL is available");
|
||||||
|
image.copyTo(uimage);
|
||||||
|
resize_image_helper(uimage, new_width, new_height);
|
||||||
|
uimage.copyTo(image);
|
||||||
|
} else {
|
||||||
|
resize_image_helper(image, new_width, new_height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpencvImage::resize_image_helper(cv::InputOutputArray &mat, int new_width, int new_height)
|
||||||
|
{
|
||||||
|
logger->debug("Resizing image");
|
||||||
|
cv::resize(mat, mat, cv::Size(new_width, new_height), 0, 0, cv::INTER_AREA);
|
||||||
|
|
||||||
|
if (flags->no_cache) {
|
||||||
|
logger->debug("Caching is disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto save_location = util::get_cache_file_save_location(path);
|
||||||
|
try {
|
||||||
|
cv::imwrite(save_location, mat);
|
||||||
|
logger->debug("Saved resized image");
|
||||||
|
} catch (const cv::Exception &ex) {
|
||||||
|
logger->error("Could not save image");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OpencvImage::process_image()
|
||||||
|
{
|
||||||
|
resize_image();
|
||||||
|
if (flags->origin_center) {
|
||||||
|
const double img_width = static_cast<double>(width()) / dims->terminal->font_width;
|
||||||
|
const double img_height = static_cast<double>(height()) / dims->terminal->font_height;
|
||||||
|
dims->x -= std::floor(img_width / 2);
|
||||||
|
dims->y -= std::floor(img_height / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::unordered_set<std::string_view> bgra_trifecta = {"x11", "chafa", "wayland"};
|
||||||
|
|
||||||
|
if (image.depth() == CV_16U) {
|
||||||
|
const float alpha = 0.00390625; // 1 / 256
|
||||||
|
image.convertTo(image, CV_8U, alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image.channels() == 4) {
|
||||||
|
// premultiply alpha
|
||||||
|
image.forEach<cv::Vec4b>([](cv::Vec4b &pix, const int *) {
|
||||||
|
const uint8_t alpha = pix[3];
|
||||||
|
const uint8_t div = 255;
|
||||||
|
pix[0] = (pix[0] * alpha) / div;
|
||||||
|
pix[1] = (pix[1] * alpha) / div;
|
||||||
|
pix[2] = (pix[2] * alpha) / div;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_OPENGL
|
||||||
|
if (flags->use_opengl) {
|
||||||
|
cv::flip(image, image, 0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (image.channels() == 1) {
|
||||||
|
cv::cvtColor(image, image, cv::COLOR_GRAY2BGRA);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bgra_trifecta.contains(flags->output)) {
|
||||||
|
if (image.channels() == 3) {
|
||||||
|
cv::cvtColor(image, image, cv::COLOR_BGR2BGRA);
|
||||||
|
}
|
||||||
|
} else if (flags->output == "kitty") {
|
||||||
|
if (image.channels() == 4) {
|
||||||
|
cv::cvtColor(image, image, cv::COLOR_BGRA2RGBA);
|
||||||
|
} else {
|
||||||
|
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
|
||||||
|
}
|
||||||
|
} else if (flags->output == "sixel") {
|
||||||
|
if (image.channels() == 4) {
|
||||||
|
cv::cvtColor(image, image, cv::COLOR_BGRA2RGB);
|
||||||
|
} else {
|
||||||
|
cv::cvtColor(image, image, cv::COLOR_BGR2RGB);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_size = image.total() * image.elemSize();
|
||||||
|
}
|
||||||
68
src/image/opencv.hpp
Normal file
68
src/image/opencv.hpp
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#ifndef OPENCV_IMAGE_H
|
||||||
|
#define OPENCV_IMAGE_H
|
||||||
|
|
||||||
|
#include "image.hpp"
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <opencv2/core.hpp>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
class OpencvImage : public Image
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
OpencvImage(std::shared_ptr<Dimensions> new_dims, const std::string &filename, bool in_cache);
|
||||||
|
~OpencvImage() override = default;
|
||||||
|
|
||||||
|
[[nodiscard]] auto dimensions() const -> const Dimensions & override;
|
||||||
|
[[nodiscard]] auto width() const -> int override;
|
||||||
|
[[nodiscard]] auto height() const -> int override;
|
||||||
|
[[nodiscard]] auto size() const -> size_t override;
|
||||||
|
[[nodiscard]] auto data() const -> const unsigned char * override;
|
||||||
|
[[nodiscard]] auto channels() const -> int override;
|
||||||
|
|
||||||
|
[[nodiscard]] auto filename() const -> std::string override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
cv::Mat image;
|
||||||
|
cv::UMat uimage;
|
||||||
|
|
||||||
|
fs::path path;
|
||||||
|
std::shared_ptr<Dimensions> dims;
|
||||||
|
|
||||||
|
uint64_t _size = 0;
|
||||||
|
uint32_t max_width;
|
||||||
|
uint32_t max_height;
|
||||||
|
bool in_cache;
|
||||||
|
bool opencl_available = false;
|
||||||
|
|
||||||
|
std::shared_ptr<spdlog::logger> logger;
|
||||||
|
std::shared_ptr<Flags> flags;
|
||||||
|
|
||||||
|
void process_image();
|
||||||
|
void resize_image();
|
||||||
|
void resize_image_helper(cv::InputOutputArray &mat, int new_width, int new_height);
|
||||||
|
|
||||||
|
void rotate_image();
|
||||||
|
void wayland_processing();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
151
src/main.cpp
Normal file
151
src/main.cpp
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <csignal>
|
||||||
|
|
||||||
|
#include <CLI/App.hpp>
|
||||||
|
#include <CLI/Config.hpp>
|
||||||
|
#include <CLI/Formatter.hpp>
|
||||||
|
#include <spdlog/cfg/env.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <CLI/Version.hpp>
|
||||||
|
#if (CLI11_VERSION_MAJOR >= 3) || (CLI11_VERSION_MAJOR == 2 && CLI11_VERSION_MINOR >= 6)
|
||||||
|
// CLI11 2.6.0 and beyond requires this header file for the CLI::IsMember validator.
|
||||||
|
# include <CLI/ExtraValidators.hpp>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "application.hpp"
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "tmux.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
void signal_handler(const int signal)
|
||||||
|
{
|
||||||
|
Application::stop_flag = true;
|
||||||
|
|
||||||
|
const auto logger = spdlog::get("main");
|
||||||
|
if (!logger) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (signal) {
|
||||||
|
case SIGINT:
|
||||||
|
logger->error("SIGINT received, exiting...");
|
||||||
|
break;
|
||||||
|
case SIGTERM:
|
||||||
|
logger->error("SIGTERM received, exiting...");
|
||||||
|
break;
|
||||||
|
case SIGHUP:
|
||||||
|
logger->error("SIGHUP received, exiting...");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger->error("UNKNOWN({}) signal received, exiting...", signal);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto main(int argc, char *argv[]) -> int
|
||||||
|
{
|
||||||
|
// handle signals
|
||||||
|
struct sigaction sga;
|
||||||
|
sga.sa_handler = signal_handler;
|
||||||
|
sigemptyset(&sga.sa_mask);
|
||||||
|
sga.sa_flags = 0;
|
||||||
|
sigaction(SIGINT, &sga, nullptr);
|
||||||
|
sigaction(SIGTERM, &sga, nullptr);
|
||||||
|
sigaction(SIGHUP, &sga, nullptr);
|
||||||
|
sigaction(SIGCHLD, nullptr, nullptr);
|
||||||
|
|
||||||
|
spdlog::cfg::load_env_levels();
|
||||||
|
|
||||||
|
std::shared_ptr<Flags> flags;
|
||||||
|
try {
|
||||||
|
flags = Flags::instance();
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
std::cerr << "Could not parse config file: " << e.what() << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
CLI::App program("Display images in the terminal", "ueberzug");
|
||||||
|
program.add_flag("-V,--version", flags->print_version, "Print version information.");
|
||||||
|
|
||||||
|
auto *layer_command = program.add_subcommand("layer", "Display images on the terminal.");
|
||||||
|
layer_command->add_flag("-s,--silent", flags->silent, "Print stderr to /dev/null.");
|
||||||
|
layer_command
|
||||||
|
->add_flag("--use-escape-codes", flags->use_escape_codes, "Use escape codes to get terminal capabilities.")
|
||||||
|
->default_val(false);
|
||||||
|
layer_command->add_option("--pid-file", flags->pid_file, "Output file where to write the daemon PID.");
|
||||||
|
layer_command->add_flag("--no-stdin", flags->no_stdin, "Do not listen on stdin for commands.")->needs("--pid-file");
|
||||||
|
layer_command->add_flag("--no-cache", flags->no_cache, "Disable caching of resized images.");
|
||||||
|
layer_command->add_flag("--no-opencv", flags->no_opencv, "Do not use OpenCV, use Libvips instead.");
|
||||||
|
layer_command->add_option("-o,--output", flags->output, "Image output method")
|
||||||
|
->check(CLI::IsMember({"x11", "wayland", "sixel", "kitty", "iterm2", "chafa"}));
|
||||||
|
layer_command->add_flag("--origin-center", flags->origin_center, "Location of the origin wrt the image");
|
||||||
|
layer_command->add_option("-p,--parser", nullptr, "**UNUSED**, only present for backwards compatibility.");
|
||||||
|
layer_command->add_option("-l,--loader", nullptr, "**UNUSED**, only present for backwards compatibility.");
|
||||||
|
|
||||||
|
auto *cmd_comand = program.add_subcommand("cmd", "Send a command to a running ueberzugpp instance.");
|
||||||
|
cmd_comand->add_option("-s,--socket", flags->cmd_socket, "UNIX socket of running instance");
|
||||||
|
cmd_comand->add_option("-i,--identifier", flags->cmd_id, "Preview identifier");
|
||||||
|
cmd_comand->add_option("-a,--action", flags->cmd_action, "Action to send");
|
||||||
|
cmd_comand->add_option("-f,--file", flags->cmd_file_path, "Path of image file");
|
||||||
|
cmd_comand->add_option("-x,--xpos", flags->cmd_x, "X position of preview");
|
||||||
|
cmd_comand->add_option("-y,--ypos", flags->cmd_y, "Y position of preview");
|
||||||
|
cmd_comand->add_option("--max-width", flags->cmd_max_width, "Max width of preview");
|
||||||
|
cmd_comand->add_option("--max-height", flags->cmd_max_height, "Max height of preview");
|
||||||
|
|
||||||
|
auto *tmux_command = program.add_subcommand("tmux", "Handle tmux hooks. Used internaly.");
|
||||||
|
tmux_command->allow_extras();
|
||||||
|
|
||||||
|
auto *query_win_command =
|
||||||
|
program.add_subcommand("query_windows", "**UNUSED**, only present for backwards compatibility.");
|
||||||
|
query_win_command->allow_extras();
|
||||||
|
|
||||||
|
CLI11_PARSE(program, argc, argv);
|
||||||
|
|
||||||
|
if (query_win_command->parsed()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags->print_version) {
|
||||||
|
Application::print_version();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!layer_command->parsed() && !tmux_command->parsed() && !cmd_comand->parsed()) {
|
||||||
|
program.exit(CLI::CallForHelp());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layer_command->parsed()) {
|
||||||
|
Application application(argv[0]);
|
||||||
|
application.command_loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmux_command->parsed()) {
|
||||||
|
try {
|
||||||
|
const auto positionals = tmux_command->remaining();
|
||||||
|
tmux::handle_hook(positionals.at(0), std::stoi(positionals.at(1)));
|
||||||
|
} catch (const std::out_of_range &oor) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd_comand->parsed()) {
|
||||||
|
util::send_command(*flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
126
src/os.cpp
Normal file
126
src/os.cpp
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "util/ptr.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
auto os::exec(const std::string &cmd) -> std::string
|
||||||
|
{
|
||||||
|
const int bufsize = 128;
|
||||||
|
std::array<char, bufsize> buffer{};
|
||||||
|
std::string result;
|
||||||
|
const c_unique_ptr<FILE, &pclose> pipe{popen(cmd.c_str(), "r")};
|
||||||
|
if (!pipe) {
|
||||||
|
throw std::system_error(errno, std::generic_category());
|
||||||
|
}
|
||||||
|
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
|
||||||
|
result.append(buffer.data());
|
||||||
|
}
|
||||||
|
if (!result.empty()) {
|
||||||
|
result.erase(result.length() - 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto os::read_data_from_fd(int filde, char sep) -> std::string
|
||||||
|
{
|
||||||
|
using std::errc;
|
||||||
|
std::string response;
|
||||||
|
char readch = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const auto status = read(filde, &readch, 1);
|
||||||
|
if (status == -1) {
|
||||||
|
throw std::system_error(errno, std::system_category());
|
||||||
|
}
|
||||||
|
if (status == 0 || readch == sep) {
|
||||||
|
if (response.empty()) {
|
||||||
|
throw std::system_error(EIO, std::system_category());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
response.push_back(readch);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto os::read_data_from_stdin(char sep) -> std::string
|
||||||
|
{
|
||||||
|
return read_data_from_fd(STDIN_FILENO, sep);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto os::wait_for_data_on_fd(int filde, int waitms) -> bool
|
||||||
|
{
|
||||||
|
struct pollfd fds;
|
||||||
|
fds.fd = filde;
|
||||||
|
fds.events = POLLIN;
|
||||||
|
|
||||||
|
poll(&fds, 1, waitms);
|
||||||
|
|
||||||
|
if ((fds.revents & (POLLERR | POLLNVAL | POLLHUP)) != 0) {
|
||||||
|
throw std::system_error(EIO, std::generic_category());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (fds.revents & POLLIN) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto os::wait_for_data_on_stdin(int waitms) -> bool
|
||||||
|
{
|
||||||
|
return wait_for_data_on_fd(STDIN_FILENO, waitms);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto os::getenv(const std::string &var) -> std::optional<std::string>
|
||||||
|
{
|
||||||
|
const char *env_p = std::getenv(var.c_str()); // NOLINT
|
||||||
|
if (env_p == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return env_p;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto os::get_pid() -> int
|
||||||
|
{
|
||||||
|
return getpid();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto os::get_ppid() -> int
|
||||||
|
{
|
||||||
|
return getppid();
|
||||||
|
}
|
||||||
|
|
||||||
|
void os::daemonize()
|
||||||
|
{
|
||||||
|
const int pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
std::exit(EXIT_FAILURE); // NOLINT
|
||||||
|
}
|
||||||
|
if (pid > 0) {
|
||||||
|
std::exit(EXIT_SUCCESS); // NOLINT
|
||||||
|
}
|
||||||
|
const int status = setsid();
|
||||||
|
if (status < 0) {
|
||||||
|
std::exit(EXIT_FAILURE); // NOLINT
|
||||||
|
}
|
||||||
|
}
|
||||||
43
src/process/apple.cpp
Normal file
43
src/process/apple.cpp
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "process.hpp"
|
||||||
|
#include "tmux.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <libproc.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
Process::Process(int pid)
|
||||||
|
: pid(pid)
|
||||||
|
{
|
||||||
|
struct proc_bsdshortinfo sproc;
|
||||||
|
struct proc_bsdinfo proc;
|
||||||
|
|
||||||
|
int status = proc_pidinfo(pid, PROC_PIDT_SHORTBSDINFO, 0, &sproc, PROC_PIDT_SHORTBSDINFO_SIZE);
|
||||||
|
if (status == PROC_PIDT_SHORTBSDINFO_SIZE) {
|
||||||
|
ppid = static_cast<int>(sproc.pbsi_ppid);
|
||||||
|
}
|
||||||
|
|
||||||
|
status = proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &proc, PROC_PIDTBSDINFO_SIZE);
|
||||||
|
if (status == PROC_PIDTBSDINFO_SIZE) {
|
||||||
|
tty_nr = static_cast<int>(proc.e_tdev);
|
||||||
|
minor_dev = minor(tty_nr);
|
||||||
|
pty_path = fmt::format("/dev/ttys{:0>3}", minor_dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/process/linux.cpp
Normal file
39
src/process/linux.cpp
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "process.hpp"
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <fstream>
|
||||||
|
#include <limits>
|
||||||
|
#if defined(__FreeBSD__) || defined(__NetBSD__)
|
||||||
|
# include <sys/types.h>
|
||||||
|
#else
|
||||||
|
# include <sys/sysmacros.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
constexpr auto max_size = std::numeric_limits<std::streamsize>::max();
|
||||||
|
|
||||||
|
Process::Process(int pid)
|
||||||
|
: pid(pid)
|
||||||
|
{
|
||||||
|
const auto stat = fmt::format("/proc/{}/stat", pid);
|
||||||
|
std::ifstream ifs(stat);
|
||||||
|
ifs.ignore(max_size, ')'); // skip pid and executable name
|
||||||
|
ifs >> state >> ppid >> process_group_id >> session_id >> tty_nr;
|
||||||
|
minor_dev = minor(tty_nr); // NOLINT
|
||||||
|
pty_path = fmt::format("/dev/pts/{}", minor_dev);
|
||||||
|
}
|
||||||
335
src/terminal.cpp
Normal file
335
src/terminal.cpp
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "terminal.hpp"
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "process.hpp"
|
||||||
|
#include "tmux.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
#ifdef ENABLE_X11
|
||||||
|
# include "util/x11.hpp"
|
||||||
|
#endif
|
||||||
|
#ifdef ENABLE_WAYLAND
|
||||||
|
# include "canvas/wayland/config.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <iostream>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <range/v3/all.hpp>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <system_error>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
Terminal::Terminal()
|
||||||
|
: terminal_pid(pid)
|
||||||
|
{
|
||||||
|
flags = Flags::instance();
|
||||||
|
logger = spdlog::get("terminal");
|
||||||
|
term = os::getenv("TERM").value_or("xterm-256color");
|
||||||
|
term_program = os::getenv("TERM_PROGRAM").value_or("");
|
||||||
|
logger->info("TERM = {}", term);
|
||||||
|
if (!term_program.empty()) {
|
||||||
|
logger->info("TERM_PROGRAM = {}", term_program);
|
||||||
|
}
|
||||||
|
open_first_pty();
|
||||||
|
get_terminal_size();
|
||||||
|
set_detected_output();
|
||||||
|
}
|
||||||
|
|
||||||
|
Terminal::~Terminal()
|
||||||
|
{
|
||||||
|
if (pty_fd > 0) {
|
||||||
|
close(pty_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::get_terminal_size()
|
||||||
|
{
|
||||||
|
struct winsize size;
|
||||||
|
ioctl(pty_fd, TIOCGWINSZ, &size);
|
||||||
|
cols = size.ws_col;
|
||||||
|
rows = size.ws_row;
|
||||||
|
xpixel = size.ws_xpixel;
|
||||||
|
ypixel = size.ws_ypixel;
|
||||||
|
logger->debug("ioctl sizes: COLS={} ROWS={} XPIXEL={} YPIXEL={}", cols, rows, xpixel, ypixel);
|
||||||
|
|
||||||
|
check_iterm2_support();
|
||||||
|
if (flags->use_escape_codes) {
|
||||||
|
init_termios();
|
||||||
|
if (xpixel == 0 || ypixel == 0) {
|
||||||
|
get_terminal_size_escape_code();
|
||||||
|
}
|
||||||
|
if (xpixel == 0 || ypixel == 0) {
|
||||||
|
get_terminal_size_xtsm();
|
||||||
|
}
|
||||||
|
check_sixel_support();
|
||||||
|
check_kitty_support();
|
||||||
|
reset_termios();
|
||||||
|
}
|
||||||
|
|
||||||
|
get_fallback_x11_terminal_sizes();
|
||||||
|
get_fallback_wayland_terminal_sizes();
|
||||||
|
|
||||||
|
if (xpixel == 0 || ypixel == 0) {
|
||||||
|
xpixel = fallback_xpixel;
|
||||||
|
ypixel = fallback_ypixel;
|
||||||
|
}
|
||||||
|
if (xpixel == 0 || ypixel == 0) {
|
||||||
|
throw std::runtime_error("Unable to calculate terminal sizes");
|
||||||
|
}
|
||||||
|
|
||||||
|
const double padding_horiz = guess_padding(cols, xpixel);
|
||||||
|
const double padding_vert = guess_padding(rows, ypixel);
|
||||||
|
|
||||||
|
padding_horizontal = static_cast<uint16_t>(std::max(padding_horiz, padding_vert));
|
||||||
|
padding_vertical = padding_horizontal;
|
||||||
|
font_width = static_cast<uint16_t>(
|
||||||
|
std::floor(guess_font_size(cols, static_cast<float>(xpixel), static_cast<float>(padding_horizontal))));
|
||||||
|
font_height = static_cast<uint16_t>(
|
||||||
|
std::floor(guess_font_size(rows, static_cast<float>(ypixel), static_cast<float>(padding_vertical))));
|
||||||
|
|
||||||
|
if (xpixel < fallback_xpixel && ypixel < fallback_ypixel) {
|
||||||
|
padding_horizontal = static_cast<uint16_t>((fallback_xpixel - xpixel) / 2);
|
||||||
|
padding_vertical = static_cast<uint16_t>((fallback_ypixel - ypixel) / 2);
|
||||||
|
font_width = static_cast<uint16_t>(xpixel / cols);
|
||||||
|
font_height = static_cast<uint16_t>(ypixel / rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger->debug("padding_horiz={} padding_vert={}", padding_horizontal, padding_vertical);
|
||||||
|
logger->debug("font_width={} font_height={}", font_width, font_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::set_detected_output()
|
||||||
|
{
|
||||||
|
if (supports_sixel) {
|
||||||
|
detected_output = "sixel";
|
||||||
|
}
|
||||||
|
if (supports_kitty) {
|
||||||
|
detected_output = "kitty";
|
||||||
|
}
|
||||||
|
if (supports_iterm2) {
|
||||||
|
detected_output = "iterm2";
|
||||||
|
}
|
||||||
|
if (supports_x11) {
|
||||||
|
detected_output = "x11";
|
||||||
|
}
|
||||||
|
if (supports_wayland) {
|
||||||
|
detected_output = "wayland";
|
||||||
|
}
|
||||||
|
if (flags->output.empty()) {
|
||||||
|
if (detected_output.empty()) {
|
||||||
|
flags->output = "chafa";
|
||||||
|
} else {
|
||||||
|
flags->output = detected_output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Terminal::guess_padding(uint16_t chars, double pixels) -> double
|
||||||
|
{
|
||||||
|
const double font_size = std::floor(pixels / chars);
|
||||||
|
return (pixels - font_size * chars) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Terminal::guess_font_size(uint16_t chars, float pixels, float padding) -> float
|
||||||
|
{
|
||||||
|
return (pixels - 2 * padding) / static_cast<float>(chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::get_terminal_size_escape_code()
|
||||||
|
{
|
||||||
|
const auto resp = read_raw_str("\033[14t").erase(0, 4);
|
||||||
|
if (resp.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto sizes = util::str_split(resp, ";");
|
||||||
|
try {
|
||||||
|
ypixel = std::stoi(sizes[0]);
|
||||||
|
xpixel = std::stoi(sizes[1]);
|
||||||
|
} catch (const std::invalid_argument &) {
|
||||||
|
logger->debug("Got unexpected values in get_terminal_size_escape_code");
|
||||||
|
}
|
||||||
|
// some old vte terminals respond to these values in a different order
|
||||||
|
// assume everything older than 7000 is broken
|
||||||
|
const auto vte_ver_str = os::getenv("VTE_VERSION").value_or("");
|
||||||
|
if (!vte_ver_str.empty()) {
|
||||||
|
const auto vte_ver = std::stoi(vte_ver_str);
|
||||||
|
const auto working_ver = 7000;
|
||||||
|
if (vte_ver <= working_ver) {
|
||||||
|
std::swap(ypixel, xpixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger->debug("ESC sizes XPIXEL={} YPIXEL={}", xpixel, ypixel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::get_terminal_size_xtsm()
|
||||||
|
{
|
||||||
|
const auto resp = read_raw_str("\033[?2;1;0S").erase(0, 3);
|
||||||
|
if (resp.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto sizes = util::str_split(resp, ";");
|
||||||
|
if (sizes.size() != 4) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ypixel = std::stoi(sizes[3]);
|
||||||
|
xpixel = std::stoi(sizes[2]);
|
||||||
|
} catch (const std::invalid_argument &) {
|
||||||
|
logger->debug("Got unexpected values in get_terminal_size_xtsm");
|
||||||
|
}
|
||||||
|
logger->debug("XTSM sizes XPIXEL={} YPIXEL={}", xpixel, ypixel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::check_sixel_support()
|
||||||
|
{
|
||||||
|
// some terminals support sixel but don't respond to escape sequences
|
||||||
|
const auto supported_terms = std::unordered_set<std::string_view>{"yaft-256color", "iTerm.app"};
|
||||||
|
const auto resp = read_raw_str("\033[?1;1;0S").erase(0, 3);
|
||||||
|
const auto vals = util::str_split(resp, ";");
|
||||||
|
if (vals.size() > 2 || supported_terms.contains(term) || supported_terms.contains(term_program)) {
|
||||||
|
supports_sixel = true;
|
||||||
|
logger->debug("sixel is supported");
|
||||||
|
} else {
|
||||||
|
logger->debug("sixel is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::check_kitty_support()
|
||||||
|
{
|
||||||
|
const auto resp = read_raw_str("\033_Gi=31,s=1,v=1,a=q,t=d,f=24;AAAA\033\\\033[c");
|
||||||
|
if (resp.find("OK") != std::string::npos) {
|
||||||
|
supports_kitty = true;
|
||||||
|
logger->debug("kitty is supported");
|
||||||
|
} else {
|
||||||
|
logger->debug("kitty is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::check_iterm2_support()
|
||||||
|
{
|
||||||
|
const auto supported_terms = std::unordered_set<std::string_view>{"WezTerm", "iTerm.app"};
|
||||||
|
if (supported_terms.contains(term_program)) {
|
||||||
|
supports_iterm2 = true;
|
||||||
|
logger->debug("iterm2 is supported");
|
||||||
|
} else {
|
||||||
|
logger->debug("iterm2 is not supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Terminal::read_raw_str(const std::string_view esc) -> std::string
|
||||||
|
{
|
||||||
|
const auto waitms = 100;
|
||||||
|
std::cout << esc << std::flush;
|
||||||
|
try {
|
||||||
|
const auto in_event = os::wait_for_data_on_stdin(waitms);
|
||||||
|
if (!in_event) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return os::read_data_from_stdin(esc.back());
|
||||||
|
} catch (const std::system_error &) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::init_termios()
|
||||||
|
{
|
||||||
|
tcgetattr(0, &old_term); /* grab old terminal i/o settings */
|
||||||
|
new_term = old_term; /* make new settings same as old settings */
|
||||||
|
new_term.c_lflag &= ~ICANON; /* disable buffered i/o */
|
||||||
|
new_term.c_lflag &= ~ECHO; /* set echo mode */
|
||||||
|
tcsetattr(0, TCSANOW, &new_term); /* use these new terminal i/o settings now */
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::reset_termios() const
|
||||||
|
{
|
||||||
|
tcsetattr(0, TCSANOW, &old_term);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::get_fallback_x11_terminal_sizes()
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_X11
|
||||||
|
const auto xutil = X11Util();
|
||||||
|
if (xutil.connected) {
|
||||||
|
supports_x11 = true;
|
||||||
|
logger->debug("X11 is supported");
|
||||||
|
} else {
|
||||||
|
logger->debug("x11 is not supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
x11_wid = xutil.get_parent_window(terminal_pid);
|
||||||
|
logger->debug("Using fallback X11 window id {}", x11_wid);
|
||||||
|
const auto [xpix, ypix] = xutil.get_window_dimensions(x11_wid);
|
||||||
|
fallback_xpixel = xpix;
|
||||||
|
fallback_ypixel = ypix;
|
||||||
|
logger->debug("X11 sizes: XPIXEL={} YPIXEL={}", fallback_xpixel, fallback_ypixel);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::get_fallback_wayland_terminal_sizes()
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_WAYLAND
|
||||||
|
const auto config = WaylandConfig::get();
|
||||||
|
if (!config->is_dummy()) {
|
||||||
|
supports_wayland = true;
|
||||||
|
logger->debug("Wayland is supported.");
|
||||||
|
} else {
|
||||||
|
logger->debug("Wayland is not supported");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto window = config->get_window_info();
|
||||||
|
fallback_xpixel = static_cast<uint16_t>(window.width);
|
||||||
|
fallback_ypixel = static_cast<uint16_t>(window.height);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void Terminal::open_first_pty()
|
||||||
|
{
|
||||||
|
const auto pids = tmux::get_client_pids().value_or(std::vector<int>{pid});
|
||||||
|
|
||||||
|
struct stat stat_info;
|
||||||
|
for (const auto spid : pids) {
|
||||||
|
auto tree = util::get_process_tree_v2(spid);
|
||||||
|
ranges::reverse(tree);
|
||||||
|
for (const auto &proc : tree) {
|
||||||
|
const int stat_res = stat(proc.pty_path.c_str(), &stat_info);
|
||||||
|
if (stat_res == -1) {
|
||||||
|
const auto err = std::error_code(errno, std::generic_category());
|
||||||
|
logger->debug("stat failed ({}) for pty {}, pid {}, ignoring", err.message(), proc.pty_path, proc.pid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (proc.tty_nr == static_cast<int>(stat_info.st_rdev)) {
|
||||||
|
pty_fd = open(proc.pty_path.c_str(), O_RDONLY | O_NOCTTY);
|
||||||
|
if (pty_fd == -1) {
|
||||||
|
const auto err = std::error_code(errno, std::generic_category());
|
||||||
|
logger->debug("could not open pty {}, {}, ignoring", proc.pty_path, err.message());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
terminal_pid = proc.pid;
|
||||||
|
logger->info("PTY = {}", proc.pty_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logger->warn("Could not open pty, using stdout as fallback");
|
||||||
|
pty_fd = STDOUT_FILENO;
|
||||||
|
}
|
||||||
142
src/tmux.cpp
Normal file
142
src/tmux.cpp
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "tmux.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
constexpr auto session_hooks = std::to_array<std::string_view>(
|
||||||
|
{"session-window-changed", "client-detached", "window-layout-changed"});
|
||||||
|
|
||||||
|
constexpr auto global_hooks = std::to_array<std::string_view>(
|
||||||
|
{"client-session-changed"});
|
||||||
|
|
||||||
|
auto tmux::get_session_id() -> std::string
|
||||||
|
{
|
||||||
|
const auto cmd = fmt::format("tmux display -p -F '#{{session_id}}' -t {}", tmux::get_pane());
|
||||||
|
return os::exec(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tmux::is_used() -> bool
|
||||||
|
{
|
||||||
|
return !tmux::get_pane().empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tmux::is_window_focused() -> bool
|
||||||
|
{
|
||||||
|
const auto cmd = fmt::format("tmux display -p -F '#{{session_attached}},#{{window_active}},#{{pane_in_mode}}' -t {}", tmux::get_pane());
|
||||||
|
return os::exec(cmd) == "1,1,0";
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tmux::get_pane() -> std::string
|
||||||
|
{
|
||||||
|
return os::getenv("TMUX_PANE").value_or("");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tmux::get_client_pids() -> std::optional<std::vector<int>>
|
||||||
|
{
|
||||||
|
if (!tmux::is_used()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!tmux::is_window_focused()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<int> pids;
|
||||||
|
const auto cmd = fmt::format("tmux list-clients -F '#{{client_pid}}' -t {}", tmux::get_pane());
|
||||||
|
const auto output = os::exec(cmd);
|
||||||
|
|
||||||
|
for (const auto &line : util::str_split(output, "\n")) {
|
||||||
|
pids.push_back(std::stoi(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pids;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tmux::get_offset() -> std::pair<int, int>
|
||||||
|
{
|
||||||
|
if (!tmux::is_used()) {
|
||||||
|
return std::make_pair(0, 0);
|
||||||
|
}
|
||||||
|
const auto [p_x, p_y] = tmux::get_pane_offset();
|
||||||
|
const auto s_y = tmux::get_statusbar_offset();
|
||||||
|
return std::make_pair(p_x, p_y + s_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tmux::get_pane_offset() -> std::pair<int, int>
|
||||||
|
{
|
||||||
|
const auto cmd = fmt::format(R"(tmux display -p -F '#{{pane_top}},#{{pane_left}},
|
||||||
|
#{{pane_bottom}},#{{pane_right}},
|
||||||
|
#{{window_height}},#{{window_width}}' -t {})",
|
||||||
|
tmux::get_pane());
|
||||||
|
const auto output = util::str_split(os::exec(cmd), ",");
|
||||||
|
return std::make_pair(std::stoi(output.at(1)), std::stoi(output.at(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto tmux::get_statusbar_offset() -> int
|
||||||
|
{
|
||||||
|
const std::string cmd = "tmux display -p '#{status},#{status-position}'";
|
||||||
|
const auto output = util::str_split(os::exec(cmd), ",");
|
||||||
|
if (output.at(1) != "top" || output.at(0) == "off") {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (output.at(0) == "on") {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return std::stoi(output.at(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void tmux::handle_hook(const std::string_view hook, int pid)
|
||||||
|
{
|
||||||
|
const auto msg = fmt::format(R"({{"action":"tmux","hook":"{}"}})", hook);
|
||||||
|
const auto endpoint = util::get_socket_path(pid);
|
||||||
|
util::send_socket_message(msg, endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tmux::register_hooks()
|
||||||
|
{
|
||||||
|
if (!tmux::is_used()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const auto &hook : session_hooks) {
|
||||||
|
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());
|
||||||
|
os::exec(cmd);
|
||||||
|
}
|
||||||
|
for (const auto &hook : global_hooks) {
|
||||||
|
std::string cmd = fmt::format(R"(tmux set-hook -g {0} "run-shell 'ueberzugpp tmux {0} {1}'")", hook, os::get_pid());
|
||||||
|
os::exec(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tmux::unregister_hooks()
|
||||||
|
{
|
||||||
|
if (!tmux::is_used()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const auto &hook : session_hooks) {
|
||||||
|
const auto cmd = fmt::format("tmux set-hook -u -t {} {}", tmux::get_pane(), hook);
|
||||||
|
os::exec(cmd);
|
||||||
|
}
|
||||||
|
for (const auto &hook : global_hooks) {
|
||||||
|
const auto cmd = fmt::format("tmux set-hook -gu {}", hook);
|
||||||
|
os::exec(cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/util/dbus.cpp
Normal file
15
src/util/dbus.cpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#include "util/dbus.hpp"
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
DbusUtil::DbusUtil(const std::string &address)
|
||||||
|
: connection(dbus_connection_open(address.c_str(), nullptr))
|
||||||
|
{
|
||||||
|
if (connection == nullptr) {
|
||||||
|
throw std::invalid_argument("Could not connect to dbus address");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DbusUtil::~DbusUtil()
|
||||||
|
{
|
||||||
|
dbus_connection_unref(connection);
|
||||||
|
}
|
||||||
228
src/util/egl.cpp
Normal file
228
src/util/egl.cpp
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "util/egl.hpp"
|
||||||
|
#include "image.hpp"
|
||||||
|
|
||||||
|
#include <range/v3/all.hpp>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
constexpr EGLint egl_major_version = 1;
|
||||||
|
constexpr EGLint egl_minor_version = 5;
|
||||||
|
constexpr EGLint opengl_major_version = 4;
|
||||||
|
constexpr EGLint opengl_minor_version = 6;
|
||||||
|
|
||||||
|
constexpr auto config_attrs = std::to_array<EGLint>({EGL_SURFACE_TYPE,
|
||||||
|
EGL_WINDOW_BIT,
|
||||||
|
EGL_CONFORMANT,
|
||||||
|
EGL_OPENGL_BIT,
|
||||||
|
EGL_RENDERABLE_TYPE,
|
||||||
|
EGL_OPENGL_BIT,
|
||||||
|
EGL_COLOR_BUFFER_TYPE,
|
||||||
|
EGL_RGB_BUFFER,
|
||||||
|
|
||||||
|
EGL_RED_SIZE,
|
||||||
|
8,
|
||||||
|
EGL_GREEN_SIZE,
|
||||||
|
8,
|
||||||
|
EGL_BLUE_SIZE,
|
||||||
|
8,
|
||||||
|
EGL_ALPHA_SIZE,
|
||||||
|
8,
|
||||||
|
|
||||||
|
EGL_DEPTH_SIZE,
|
||||||
|
24,
|
||||||
|
EGL_STENCIL_SIZE,
|
||||||
|
8,
|
||||||
|
|
||||||
|
EGL_NONE});
|
||||||
|
|
||||||
|
const auto context_attrs = std::to_array<EGLint>(
|
||||||
|
{EGL_CONTEXT_MAJOR_VERSION, opengl_major_version, EGL_CONTEXT_MINOR_VERSION, opengl_minor_version,
|
||||||
|
#ifdef DEBUG
|
||||||
|
EGL_CONTEXT_OPENGL_DEBUG, EGL_TRUE,
|
||||||
|
#endif
|
||||||
|
EGL_CONTEXT_OPENGL_ROBUST_ACCESS, EGL_TRUE, EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
|
||||||
|
EGL_NONE});
|
||||||
|
|
||||||
|
void GLAPIENTRY debug_callback([[maybe_unused]] GLenum source, [[maybe_unused]] GLenum type,
|
||||||
|
[[maybe_unused]] GLuint gl_id, GLenum severity, [[maybe_unused]] GLsizei length,
|
||||||
|
const GLchar *message, [[maybe_unused]] const void *user)
|
||||||
|
{
|
||||||
|
if (type != GL_DEBUG_TYPE_ERROR) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto logger = spdlog::get("opengl");
|
||||||
|
logger->error("Type: {:#X}, Severity: {:#X}, Message: {}", type, severity, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class V>
|
||||||
|
EGLUtil<T, V>::EGLUtil(EGLenum platform, T *native_display, const EGLAttrib *attrib)
|
||||||
|
: display(eglGetPlatformDisplay(platform, native_display, attrib))
|
||||||
|
{
|
||||||
|
logger = spdlog::get("opengl");
|
||||||
|
|
||||||
|
if (display == EGL_NO_DISPLAY) {
|
||||||
|
const auto err = error_to_string();
|
||||||
|
logger->error("Could not obtain display, error {}", err);
|
||||||
|
throw std::runtime_error("");
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLint egl_major = 0;
|
||||||
|
EGLint egl_minor = 0;
|
||||||
|
EGLBoolean eglres = eglInitialize(display, &egl_major, &egl_minor);
|
||||||
|
if (eglres != EGL_TRUE) {
|
||||||
|
const auto err = error_to_string();
|
||||||
|
logger->error("Could not initialize display, error {}", err);
|
||||||
|
throw std::runtime_error("");
|
||||||
|
}
|
||||||
|
if (egl_major != egl_major_version && egl_minor != egl_minor_version) {
|
||||||
|
logger->error("EGL {}.{} is not available", egl_major_version, egl_minor_version);
|
||||||
|
throw std::runtime_error("");
|
||||||
|
}
|
||||||
|
|
||||||
|
eglres = eglBindAPI(EGL_OPENGL_API);
|
||||||
|
if (eglres != EGL_TRUE) {
|
||||||
|
const auto err = error_to_string();
|
||||||
|
logger->error("Could not bind to OpenGL API, error {}", err);
|
||||||
|
throw std::runtime_error("");
|
||||||
|
}
|
||||||
|
|
||||||
|
int num_config = 0;
|
||||||
|
eglres = eglChooseConfig(display, config_attrs.data(), &config, 1, &num_config);
|
||||||
|
if (eglres != EGL_TRUE || num_config != 1) {
|
||||||
|
const auto err = error_to_string();
|
||||||
|
logger->error("Could not create config, error {}", err);
|
||||||
|
throw std::runtime_error("");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger->info("Using EGL {}.{} and OpenGL {}.{}", egl_major_version, egl_minor_version, opengl_major_version,
|
||||||
|
opengl_minor_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class V>
|
||||||
|
EGLUtil<T, V>::~EGLUtil()
|
||||||
|
{
|
||||||
|
eglTerminate(display);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class V>
|
||||||
|
auto EGLUtil<T, V>::error_to_string() const -> std::string
|
||||||
|
{
|
||||||
|
using pair_type = std::pair<EGLint, std::string_view>;
|
||||||
|
constexpr auto error_codes = std::to_array<pair_type>({
|
||||||
|
{EGL_SUCCESS, "EGL_SUCCESS"},
|
||||||
|
{EGL_BAD_ACCESS, "EGL_BAD_ACCESS"},
|
||||||
|
{EGL_BAD_ALLOC, "EGL_BAD_ALLOC"},
|
||||||
|
{EGL_BAD_ATTRIBUTE, "EGL_BAD_ATTRIBUTE"},
|
||||||
|
{EGL_BAD_CONFIG, "EGL_BAD_CONFIG"},
|
||||||
|
{EGL_BAD_CONTEXT, "EGL_BAD_CONTEXT"},
|
||||||
|
{EGL_BAD_CURRENT_SURFACE, "EGL_BAD_CURRENT_SURFACE"},
|
||||||
|
{EGL_BAD_DISPLAY, "EGL_BAD_DISPLAY"},
|
||||||
|
{EGL_BAD_MATCH, "EGL_BAD_MATCH"},
|
||||||
|
{EGL_BAD_NATIVE_PIXMAP, "EGL_BAD_NATIVE_PIXMAP"},
|
||||||
|
{EGL_BAD_NATIVE_WINDOW, "EGL_BAD_NATIVE_WINDOW"},
|
||||||
|
{EGL_BAD_PARAMETER, "EGL_BAD_PARAMETER"},
|
||||||
|
{EGL_BAD_SURFACE, "EGL_BAD_SURFACE"},
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto current_error = eglGetError();
|
||||||
|
|
||||||
|
const auto found =
|
||||||
|
ranges::find_if(error_codes, [current_error](const pair_type &pair) { return pair.first == current_error; });
|
||||||
|
|
||||||
|
if (found == error_codes.end()) {
|
||||||
|
return "EGL_UNKNOWN_ERROR";
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::string(found->second);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class V>
|
||||||
|
auto EGLUtil<T, V>::create_context(EGLSurface surface) const -> EGLContext
|
||||||
|
{
|
||||||
|
EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attrs.data());
|
||||||
|
if (context == EGL_NO_CONTEXT) {
|
||||||
|
const auto err = error_to_string();
|
||||||
|
logger->error("Could not create context, error {}", err);
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DEBUG
|
||||||
|
run_contained(surface, context, [] {
|
||||||
|
glDebugMessageCallback(debug_callback, nullptr);
|
||||||
|
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class V>
|
||||||
|
auto EGLUtil<T, V>::create_surface(V *native_window) const -> EGLSurface
|
||||||
|
{
|
||||||
|
EGLSurface surface = eglCreatePlatformWindowSurface(display, config, native_window, nullptr);
|
||||||
|
if (surface == EGL_NO_SURFACE) {
|
||||||
|
const auto err = error_to_string();
|
||||||
|
logger->error("Could not create surface, error {}", err);
|
||||||
|
}
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class V>
|
||||||
|
void EGLUtil<T, V>::make_current(EGLSurface surface, EGLContext context) const
|
||||||
|
{
|
||||||
|
eglMakeCurrent(display, surface, surface, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class V>
|
||||||
|
void EGLUtil<T, V>::restore() const
|
||||||
|
{
|
||||||
|
eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class V>
|
||||||
|
void EGLUtil<T, V>::run_contained(EGLSurface surface, EGLContext context, const std::function<void()> &func) const
|
||||||
|
{
|
||||||
|
make_current(surface, context);
|
||||||
|
func();
|
||||||
|
restore();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <class T, class V>
|
||||||
|
void EGLUtil<T, V>::get_texture_from_image(const Image &image, GLuint texture) const
|
||||||
|
{
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
|
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.width(), image.height(), 0, GL_BGRA, GL_UNSIGNED_BYTE, image.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ENABLE_X11
|
||||||
|
# include <xcb/xcb.h>
|
||||||
|
template class EGLUtil<xcb_connection_t, xcb_window_t>;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ENABLE_WAYLAND
|
||||||
|
# include <wayland-client.h>
|
||||||
|
# include <wayland-egl.h>
|
||||||
|
template class EGLUtil<struct wl_display, struct wl_egl_window>;
|
||||||
|
#endif
|
||||||
169
src/util/socket.cpp
Normal file
169
src/util/socket.cpp
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "util/socket.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstring>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <system_error>
|
||||||
|
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
UnixSocket::UnixSocket(const std::string_view endpoint)
|
||||||
|
: fd(socket(AF_UNIX, SOCK_STREAM, 0))
|
||||||
|
{
|
||||||
|
if (fd == -1) {
|
||||||
|
throw std::system_error(errno, std::generic_category());
|
||||||
|
}
|
||||||
|
connect_to_endpoint(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
UnixSocket::UnixSocket()
|
||||||
|
: fd(socket(AF_UNIX, SOCK_STREAM, 0))
|
||||||
|
{
|
||||||
|
if (fd == -1) {
|
||||||
|
throw std::system_error(errno, std::generic_category());
|
||||||
|
}
|
||||||
|
const int bufsize = 8192;
|
||||||
|
buffer.reserve(bufsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnixSocket::connect_to_endpoint(const std::string_view endpoint)
|
||||||
|
{
|
||||||
|
if (!fs::exists(endpoint)) {
|
||||||
|
connected = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
struct sockaddr_un sock;
|
||||||
|
std::memset(&sock, 0, sizeof(sockaddr_un));
|
||||||
|
sock.sun_family = AF_UNIX;
|
||||||
|
endpoint.copy(sock.sun_path, endpoint.size());
|
||||||
|
|
||||||
|
int res = connect(fd, reinterpret_cast<const struct sockaddr *>(&sock), sizeof(struct sockaddr_un));
|
||||||
|
if (res == -1) {
|
||||||
|
throw std::system_error(errno, std::generic_category());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnixSocket::bind_to_endpoint(const std::string_view endpoint) const
|
||||||
|
{
|
||||||
|
struct sockaddr_un sock;
|
||||||
|
std::memset(&sock, 0, sizeof(sockaddr_un));
|
||||||
|
sock.sun_family = AF_UNIX;
|
||||||
|
endpoint.copy(sock.sun_path, endpoint.size());
|
||||||
|
|
||||||
|
int res = bind(fd, reinterpret_cast<const struct sockaddr *>(&sock), sizeof(struct sockaddr_un));
|
||||||
|
if (res == -1) {
|
||||||
|
throw std::system_error(errno, std::generic_category());
|
||||||
|
}
|
||||||
|
res = listen(fd, SOMAXCONN);
|
||||||
|
if (res == -1) {
|
||||||
|
throw std::system_error(errno, std::generic_category());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto UnixSocket::wait_for_connections(int waitms) const -> int
|
||||||
|
{
|
||||||
|
const auto in_event = os::wait_for_data_on_fd(fd, waitms);
|
||||||
|
if (in_event) {
|
||||||
|
return accept(fd, nullptr, nullptr);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto UnixSocket::read_data_from_connection(int filde) -> std::vector<std::string>
|
||||||
|
{
|
||||||
|
// a single connection could send multiples commands at once
|
||||||
|
// each command should end with a '\n' character
|
||||||
|
const int read_buffer_size = 4096;
|
||||||
|
std::array<char, read_buffer_size> read_buffer;
|
||||||
|
while (true) {
|
||||||
|
const auto status = recv(filde, read_buffer.data(), read_buffer_size, 0);
|
||||||
|
if (status <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
buffer.append(read_buffer.data(), status);
|
||||||
|
}
|
||||||
|
auto cmds = util::str_split(buffer, "\n");
|
||||||
|
buffer.clear();
|
||||||
|
close(filde);
|
||||||
|
return cmds;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnixSocket::write(const void *data, std::size_t len) const
|
||||||
|
{
|
||||||
|
if (!connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto *runner = static_cast<const uint8_t *>(data);
|
||||||
|
while (len != 0) {
|
||||||
|
const auto status = send(fd, runner, len, MSG_NOSIGNAL);
|
||||||
|
if (status == -1) {
|
||||||
|
throw std::system_error(errno, std::generic_category());
|
||||||
|
}
|
||||||
|
len -= status;
|
||||||
|
runner += status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnixSocket::read(void *data, std::size_t len) const
|
||||||
|
{
|
||||||
|
if (!connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *runner = static_cast<uint8_t *>(data);
|
||||||
|
while (len != 0) {
|
||||||
|
const auto status = recv(fd, runner, len, 0);
|
||||||
|
if (status == 0) {
|
||||||
|
return; // no data
|
||||||
|
}
|
||||||
|
if (status == -1) {
|
||||||
|
throw std::system_error(errno, std::generic_category());
|
||||||
|
}
|
||||||
|
len -= status;
|
||||||
|
runner += status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto UnixSocket::read_until_empty() const -> std::string
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
const int read_buffer_size = 4096;
|
||||||
|
std::array<char, read_buffer_size> read_buffer;
|
||||||
|
result.reserve(read_buffer_size);
|
||||||
|
while (true) {
|
||||||
|
const auto status = recv(fd, read_buffer.data(), read_buffer_size, 0);
|
||||||
|
if (status <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
result.append(read_buffer.data(), status);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnixSocket::~UnixSocket()
|
||||||
|
{
|
||||||
|
close(fd);
|
||||||
|
}
|
||||||
262
src/util/util.cpp
Normal file
262
src/util/util.cpp
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "util.hpp"
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "process.hpp"
|
||||||
|
#include "util/ptr.hpp"
|
||||||
|
#include "util/socket.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
#include <random>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <openssl/evp.h>
|
||||||
|
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||||
|
# define EVP_MD_CTX_new EVP_MD_CTX_create
|
||||||
|
# define EVP_MD_CTX_free EVP_MD_CTX_destroy
|
||||||
|
#endif
|
||||||
|
#ifdef ENABLE_TURBOBASE64
|
||||||
|
# ifdef WITH_SYSTEM_TURBOBASE64
|
||||||
|
# include <turbobase64/turbob64.h>
|
||||||
|
# else
|
||||||
|
# include "turbob64.h"
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
#include <range/v3/all.hpp>
|
||||||
|
|
||||||
|
#include <vips/vips8>
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
using njson = nlohmann::json;
|
||||||
|
|
||||||
|
auto util::str_split(std::string_view str, std::string_view delim) -> std::vector<std::string>
|
||||||
|
{
|
||||||
|
std::vector<std::string> result;
|
||||||
|
for (const auto word : ranges::views::split(str, delim)) {
|
||||||
|
result.emplace_back(ranges::to<std::string>(word));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::get_process_tree(int pid) -> std::vector<int>
|
||||||
|
{
|
||||||
|
std::vector<int> res;
|
||||||
|
Process runner(pid);
|
||||||
|
while (runner.pid > 1) {
|
||||||
|
res.push_back(runner.pid);
|
||||||
|
runner = Process(runner.ppid);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::get_process_tree_v2(int pid) -> std::vector<Process>
|
||||||
|
{
|
||||||
|
std::vector<Process> tree;
|
||||||
|
Process runner(pid);
|
||||||
|
while (runner.pid > 1) {
|
||||||
|
tree.push_back(runner);
|
||||||
|
runner = Process(runner.ppid);
|
||||||
|
}
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::get_cache_path() -> std::string
|
||||||
|
{
|
||||||
|
const auto home = os::getenv("HOME").value_or(util::temp_directory_path());
|
||||||
|
const auto cache_home = os::getenv("XDG_CACHE_HOME").value_or(fmt::format("{}/.cache", home));
|
||||||
|
return fmt::format("{}/ueberzugpp/", cache_home);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::get_log_filename() -> std::string
|
||||||
|
{
|
||||||
|
const auto user = os::getenv("USER").value_or("NOUSER");
|
||||||
|
return fmt::format("ueberzugpp-{}.log", user);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::get_socket_path(int pid) -> std::string
|
||||||
|
{
|
||||||
|
return fmt::format("{}/ueberzugpp-{}.socket", util::temp_directory_path().string(), pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::send_socket_message(const std::string_view msg, const std::string_view endpoint)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
UnixSocket socket;
|
||||||
|
socket.connect_to_endpoint(endpoint);
|
||||||
|
socket.write(msg.data(), msg.size());
|
||||||
|
} catch (const std::system_error &err) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::base64_encode(const unsigned char *input, size_t length) -> std::string
|
||||||
|
{
|
||||||
|
const size_t bufsize = 4 * ((length + 2) / 3) + 1;
|
||||||
|
std::vector<char> res(bufsize, 0);
|
||||||
|
base64_encode_v2(input, length, reinterpret_cast<unsigned char *>(res.data()));
|
||||||
|
return {res.data()};
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::base64_encode_v2(const unsigned char *input, size_t length, unsigned char *out)
|
||||||
|
{
|
||||||
|
#ifdef ENABLE_TURBOBASE64
|
||||||
|
tb64enc(input, length, out);
|
||||||
|
#else
|
||||||
|
EVP_EncodeBlock(out, input, static_cast<int>(length));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::get_b2_hash_ssl(const std::string_view str) -> std::string
|
||||||
|
{
|
||||||
|
std::stringstream sstream;
|
||||||
|
const auto mdctx = c_unique_ptr<EVP_MD_CTX, EVP_MD_CTX_free>{EVP_MD_CTX_new()};
|
||||||
|
#ifdef LIBRESSL_VERSION_NUMBER
|
||||||
|
const auto *evp = EVP_sha256();
|
||||||
|
#else
|
||||||
|
const auto *evp = EVP_blake2b512();
|
||||||
|
#endif
|
||||||
|
auto digest = std::array<unsigned char, EVP_MAX_MD_SIZE>();
|
||||||
|
|
||||||
|
EVP_DigestInit_ex(mdctx.get(), evp, nullptr);
|
||||||
|
EVP_DigestUpdate(mdctx.get(), str.data(), str.size());
|
||||||
|
unsigned int digest_len = 0;
|
||||||
|
EVP_DigestFinal_ex(mdctx.get(), digest.data(), &digest_len);
|
||||||
|
|
||||||
|
sstream << std::hex << std::setw(2) << std::setfill('0');
|
||||||
|
for (unsigned int i = 0; i < digest_len; ++i) {
|
||||||
|
sstream << static_cast<int>(digest[i]);
|
||||||
|
}
|
||||||
|
return sstream.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::move_cursor(int row, int col)
|
||||||
|
{
|
||||||
|
std::cout << "\033[" << row << ";" << col << "f" << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::save_cursor_position()
|
||||||
|
{
|
||||||
|
std::cout << "\0337" << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::restore_cursor_position()
|
||||||
|
{
|
||||||
|
std::cout << "\0338" << std::flush;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::get_cache_file_save_location(const fs::path &path) -> std::string
|
||||||
|
{
|
||||||
|
return fmt::format("{}{}{}", get_cache_path(), get_b2_hash_ssl(path.string()), path.extension().string());
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::benchmark(const std::function<void(void)> &func)
|
||||||
|
{
|
||||||
|
using std::chrono::duration;
|
||||||
|
using std::chrono::duration_cast;
|
||||||
|
using std::chrono::high_resolution_clock;
|
||||||
|
using std::chrono::milliseconds;
|
||||||
|
|
||||||
|
const auto ti1 = high_resolution_clock::now();
|
||||||
|
func();
|
||||||
|
const auto ti2 = high_resolution_clock::now();
|
||||||
|
const duration<double, std::milli> ms_double = ti2 - ti1;
|
||||||
|
|
||||||
|
std::cout << ms_double.count() << "ms\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::send_command(const Flags &flags)
|
||||||
|
{
|
||||||
|
if (flags.cmd_action == "exit") {
|
||||||
|
util::send_socket_message("EXIT", flags.cmd_socket);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags.cmd_action == "remove") {
|
||||||
|
const njson json = {{"action", "remove"}, {"identifier", flags.cmd_id}};
|
||||||
|
util::send_socket_message(json.dump(), flags.cmd_socket);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const njson json = {{"action", flags.cmd_action},
|
||||||
|
{"identifier", flags.cmd_id},
|
||||||
|
{"max_width", std::stoi(flags.cmd_max_width)},
|
||||||
|
{"max_height", std::stoi(flags.cmd_max_height)},
|
||||||
|
{"x", std::stoi(flags.cmd_x)},
|
||||||
|
{"y", std::stoi(flags.cmd_y)},
|
||||||
|
{"path", flags.cmd_file_path}};
|
||||||
|
util::send_socket_message(json.dump(), flags.cmd_socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
void util::clear_terminal_area(int xcoord, int ycoord, int width, int height)
|
||||||
|
{
|
||||||
|
save_cursor_position();
|
||||||
|
const auto line_clear = std::string(width, ' ');
|
||||||
|
for (int i = ycoord; i <= height + 2; ++i) {
|
||||||
|
util::move_cursor(i, xcoord);
|
||||||
|
std::cout << line_clear;
|
||||||
|
}
|
||||||
|
std::cout << std::flush;
|
||||||
|
restore_cursor_position();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::generate_random_string(size_t length) -> std::string
|
||||||
|
{
|
||||||
|
constexpr auto chars =
|
||||||
|
std::to_array({'1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
|
||||||
|
'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'});
|
||||||
|
auto rng_dev = std::random_device();
|
||||||
|
auto rng = std::mt19937(rng_dev());
|
||||||
|
auto dist = std::uniform_int_distribution{{}, chars.size() - 1};
|
||||||
|
std::string result(length, 0);
|
||||||
|
std::generate_n(std::begin(result), length, [&chars, &dist, &rng] { return chars[dist(rng)]; });
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::read_exif_rotation(const fs::path &path) -> std::optional<std::uint16_t>
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
auto image = vips::VImage::new_from_file(path.c_str());
|
||||||
|
return image.get_int("orientation");
|
||||||
|
} catch (const vips::VError &) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::round_up(int num_to_round, int multiple) -> int
|
||||||
|
{
|
||||||
|
if (multiple == 0) {
|
||||||
|
return num_to_round;
|
||||||
|
}
|
||||||
|
|
||||||
|
int remainder = num_to_round % multiple;
|
||||||
|
if (remainder == 0) {
|
||||||
|
return num_to_round;
|
||||||
|
}
|
||||||
|
|
||||||
|
return num_to_round + multiple - remainder;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto util::temp_directory_path() -> fs::path
|
||||||
|
{
|
||||||
|
return os::getenv("UEBERZUGPP_TMPDIR").value_or(fs::temp_directory_path());
|
||||||
|
}
|
||||||
171
src/util/x11.cpp
Normal file
171
src/util/x11.cpp
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
// Display images inside a terminal
|
||||||
|
// Copyright (C) 2023 JustKidding
|
||||||
|
//
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "util/x11.hpp"
|
||||||
|
#include "flags.hpp"
|
||||||
|
#include "os.hpp"
|
||||||
|
#include "util.hpp"
|
||||||
|
#include "util/ptr.hpp"
|
||||||
|
|
||||||
|
#include <range/v3/all.hpp>
|
||||||
|
#include <xcb/res.h>
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <stack>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
X11Util::X11Util()
|
||||||
|
: connection(xcb_connect(nullptr, nullptr))
|
||||||
|
{
|
||||||
|
const auto flags = Flags::instance();
|
||||||
|
const auto xdg_session = os::getenv("XDG_SESSION_TYPE").value_or("");
|
||||||
|
if (xcb_connection_has_error(connection) == 0) {
|
||||||
|
screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
|
||||||
|
if (xdg_session != "wayland" || flags->output == "x11") {
|
||||||
|
connected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
X11Util::X11Util(xcb_connection_t *connection)
|
||||||
|
: connection(connection),
|
||||||
|
screen(xcb_setup_roots_iterator(xcb_get_setup(connection)).data),
|
||||||
|
owns_connection(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
X11Util::~X11Util()
|
||||||
|
{
|
||||||
|
if (owns_connection) {
|
||||||
|
xcb_disconnect(connection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto X11Util::get_server_window_ids() const -> std::vector<xcb_window_t>
|
||||||
|
{
|
||||||
|
const int num_clients = 256;
|
||||||
|
std::vector<xcb_window_t> windows;
|
||||||
|
std::stack<xcb_query_tree_cookie_t> cookies_st;
|
||||||
|
windows.reserve(num_clients);
|
||||||
|
|
||||||
|
cookies_st.push(xcb_query_tree_unchecked(connection, screen->root));
|
||||||
|
|
||||||
|
while (!cookies_st.empty()) {
|
||||||
|
const auto cookie = cookies_st.top();
|
||||||
|
cookies_st.pop();
|
||||||
|
|
||||||
|
const auto reply = unique_C_ptr<xcb_query_tree_reply_t>{xcb_query_tree_reply(connection, cookie, nullptr)};
|
||||||
|
if (!reply) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto num_children = xcb_query_tree_children_length(reply.get());
|
||||||
|
if (num_children == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto *children = xcb_query_tree_children(reply.get());
|
||||||
|
for (int i = 0; i < num_children; ++i) {
|
||||||
|
const auto child = children[i];
|
||||||
|
const bool is_complete_window = window_has_properties(child, {XCB_ATOM_WM_CLASS, XCB_ATOM_WM_NAME});
|
||||||
|
if (is_complete_window) {
|
||||||
|
windows.push_back(child);
|
||||||
|
}
|
||||||
|
cookies_st.push(xcb_query_tree_unchecked(connection, child));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return windows;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto X11Util::get_pid_window_map() const -> std::unordered_map<uint32_t, xcb_window_t>
|
||||||
|
{
|
||||||
|
const auto windows = get_server_window_ids();
|
||||||
|
std::unordered_map<uint32_t, xcb_window_t> res;
|
||||||
|
std::vector<xcb_res_query_client_ids_cookie_t> cookies;
|
||||||
|
res.reserve(windows.size());
|
||||||
|
cookies.reserve(windows.size());
|
||||||
|
|
||||||
|
struct xcb_res_client_id_spec_t spec;
|
||||||
|
spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID;
|
||||||
|
|
||||||
|
// bulk request pids
|
||||||
|
for (const auto window : windows) {
|
||||||
|
spec.client = window;
|
||||||
|
cookies.push_back(xcb_res_query_client_ids_unchecked(connection, 1, &spec));
|
||||||
|
}
|
||||||
|
|
||||||
|
// process replies
|
||||||
|
auto win_iter = windows.cbegin();
|
||||||
|
for (const auto cookie : cookies) {
|
||||||
|
const auto reply =
|
||||||
|
unique_C_ptr<xcb_res_query_client_ids_reply_t>{xcb_res_query_client_ids_reply(connection, cookie, nullptr)};
|
||||||
|
if (!reply) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto iter = xcb_res_query_client_ids_ids_iterator(reply.get());
|
||||||
|
const auto pid = *xcb_res_client_id_value_value(iter.data);
|
||||||
|
res.insert_or_assign(pid, *win_iter);
|
||||||
|
std::advance(win_iter, 1);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto X11Util::window_has_properties(xcb_window_t window, std::initializer_list<xcb_atom_t> properties) const -> bool
|
||||||
|
{
|
||||||
|
std::vector<xcb_get_property_cookie_t> cookies;
|
||||||
|
cookies.reserve(properties.size());
|
||||||
|
for (const auto prop : properties) {
|
||||||
|
cookies.push_back(xcb_get_property_unchecked(connection, 0, window, prop, XCB_ATOM_ANY, 0, 4));
|
||||||
|
}
|
||||||
|
return ranges::any_of(cookies, [this](xcb_get_property_cookie_t cookie) -> bool {
|
||||||
|
const auto reply = unique_C_ptr<xcb_get_property_reply_t>{xcb_get_property_reply(connection, cookie, nullptr)};
|
||||||
|
return reply && xcb_get_property_value_length(reply.get()) != 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto X11Util::get_window_dimensions(xcb_window_t window) const -> std::pair<uint16_t, uint16_t>
|
||||||
|
{
|
||||||
|
const auto cookie = xcb_get_geometry_unchecked(connection, window);
|
||||||
|
const auto reply = unique_C_ptr<xcb_get_geometry_reply_t>{xcb_get_geometry_reply(connection, cookie, nullptr)};
|
||||||
|
if (!reply) {
|
||||||
|
return std::make_pair(0, 0);
|
||||||
|
}
|
||||||
|
return std::make_pair(reply->width, reply->height);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto X11Util::get_parent_window(int pid) const -> xcb_window_t
|
||||||
|
{
|
||||||
|
const auto wid = os::getenv("WINDOWID");
|
||||||
|
if (wid.has_value()) {
|
||||||
|
try {
|
||||||
|
return std::stoi(wid.value());
|
||||||
|
} catch (const std::out_of_range &oor) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto pid_window_map = get_pid_window_map();
|
||||||
|
const auto tree = util::get_process_tree(pid);
|
||||||
|
for (const auto spid : tree) {
|
||||||
|
const auto win = pid_window_map.find(spid);
|
||||||
|
if (win != pid_window_map.end()) {
|
||||||
|
return win->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user