Skip to content

Commit

Permalink
[contour] Adds CLI command contour image PATH to upload an image to…
Browse files Browse the repository at this point in the history
… the terminal for displaying.
  • Loading branch information
christianparpart committed Apr 20, 2022
1 parent 720303e commit c3d9774
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ jobs:
BUILD_DIR="build" \
CMAKE_BUILD_TYPE="RelWithDebInfo" \
CXX="g++-8" \
EXTRA_CMAKE_FLAGS="-DCONTOUR_BLUR_PLATFORM_KWIN=ON -DUSE_BOOST_FILESYSTEM=ON -DCONTOUR_INSTALL_TOOLS=ON" \
EXTRA_CMAKE_FLAGS="-DCONTOUR_BLUR_PLATFORM_KWIN=ON -DUSE_BOOST_FILESYSTEM=ON -DCONTOUR_INSTALL_TOOLS=ON -DCONTOUR_COMMAND_IMAGE=ON" \
./scripts/ci-prepare-contour.sh
- name: "build"
run: cmake --build build/ -- -j3
Expand Down
3 changes: 2 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
### 0.3.1 (unreleased)

* Fixes assertion on font resize when a (Sixel) image is currently being rendered (#642).
- Fixes assertion on font resize when a (Sixel) image is currently being rendered (#642).
- Fixes assertion on too quick shell terminations (#647).
- Adds new command `contour image PATH_TO_IMAGE` to send an image file to the terminal for displaying for platforms that do provide libsixel.

### 0.3.0 (2022-04-18)

Expand Down
79 changes: 79 additions & 0 deletions cmake/Findlibsixel.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file Copyright.txt or https://cmake.org/licensing for details.

#[=======================================================================[.rst:
FindLibsixel
------------

Find Libsixel headers and library.

Imported Targets
^^^^^^^^^^^^^^^^

``Libsixel::Libsixel``
The Libsixel library, if found.

Result Variables
^^^^^^^^^^^^^^^^

This will define the following variables in your project:

``Libsixel_FOUND``
true if (the requested version of) Fontconfig is available.
``Libsixel_VERSION``
the version of Fontconfig.
``Libsixel_LIBRARIES``
the libraries to link against to use Fontconfig.
``Libsixel_INCLUDE_DIRS``
where to find the Fontconfig headers.
``Libsixel_COMPILE_OPTIONS``
this should be passed to target_compile_options(), if the
target is not used for linking

#]=======================================================================]

find_package(PkgConfig QUIET)
pkg_check_modules(PKG_LIBSIXEL QUIET libsixel)
set(libsixel_COMPILE_OPTIONS ${PKG_LIBSIXEL_CFLAGS_OTHER})
#set(libsixel_VERSION ${PKG_LIBSIXEL_VERSION})

find_path(libsixel_INCLUDE_DIR
NAMES
sixel.h
HINTS
${PKG_LIBSIXEL_INCLUDE_DIRS}
)

find_library(libsixel_LIBRARY
NAMES
sixel
PATHS
${PKG_LIBSIXEL_LIBRARY_DIRS}
)

include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(libsixel
FOUND_VAR
libsixel_FOUND
REQUIRED_VARS
libsixel_LIBRARY
libsixel_INCLUDE_DIR
VERSION_VAR
libsixel_VERSION
)

if(libsixel_FOUND AND NOT TARGET Libsixel::Libsixel)
add_library(Libsixel::Libsixel UNKNOWN IMPORTED)
set_target_properties(Libsixel::Libsixel PROPERTIES
IMPORTED_LOCATION "${libsixel_LIBRARY}"
INTERFACE_COMPILE_OPTIONS "${libsixel_COMPILE_OPTIONS}"
INTERFACE_INCLUDE_DIRECTORIES "${libsixel_INCLUDE_DIR}"
)
endif()

mark_as_advanced(libsixel_LIBRARY libsixel_INCLUDE_DIR)

if(libsixel_FOUND)
set(libsixel_LIBRARIES ${libsixel_LIBRARY})
set(libsixel_INCLUDE_DIRS ${libsixel_INCLUDE_DIR})
endif()
2 changes: 2 additions & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Build-Depends:
libharfbuzz-dev,
libkf5windowsystem-dev,
libqt5opengl5-dev,
libsixel-dev,
libyaml-cpp-dev,
make,
ncurses-bin,
Expand Down Expand Up @@ -48,4 +49,5 @@ Depends: ${shlibs:Depends}, ${misc:Depends},
libqt5core5a,
libqt5gui5,
libqt5network5,
libsixel1,
libyaml-cpp0.6,
6 changes: 6 additions & 0 deletions scripts/install-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ install_deps_ubuntu()
libkf5windowsystem-dev
libqt5gui5
libqt5opengl5-dev
libsixel-dev
libyaml-cpp-dev
make
ncurses-bin
Expand Down Expand Up @@ -200,6 +201,7 @@ install_deps_FreeBSD()
freetype2 \
harfbuzz \
libfmt \
libsixel \
microsoft-gsl \
ncurses \
ninja \
Expand Down Expand Up @@ -227,6 +229,7 @@ install_deps_arch()
fontconfig \
git \
harfbuzz \
libsixel \
microsoft-gsl \
ninja \
qt5-base \
Expand All @@ -240,6 +243,8 @@ install_deps_fedora()
fetch_and_unpack_fmtlib
[ x$PREPARE_ONLY_EMBEDS = xON ] && return

# TODO(pr) Houston we have a problem! Fedora does not like to have libsixel.

# catch-devel
local packages="
catch-devel
Expand Down Expand Up @@ -275,6 +280,7 @@ install_deps_darwin()
fmt \
freetype \
harfbuzz \
libsixel \
pkg-config \
range-v3 \
qt@5 \
Expand Down
12 changes: 12 additions & 0 deletions src/contour/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ option(CONTOUR_VT_METRICS "Enables collecting and exit-printing some VT usage me
option(CONTOUR_SCROLLBAR "Enables scrollbar in GUI frontend." ON)
option(CONTOUR_BLUR_PLATFORM_KWIN "Enables support for blurring transparent background when using KWin (KDE window manager)." OFF)
option(CONTOUR_BUILD_WITH_QT6 "Use Qt 6" OFF)
option(CONTOUR_COMMAND_IMAGE "Enables CLI command: contour image PATH_TO_IMAGE" OFF)

if(CONTOUR_BUILD_WITH_QT6)
find_package(Qt6 COMPONENTS Core Gui Network OpenGL OpenGLWidgets Widgets REQUIRED)
Expand Down Expand Up @@ -72,6 +73,12 @@ target_compile_definitions(contour PRIVATE
set_target_properties(contour PROPERTIES AUTOMOC ON)
set_target_properties(contour PROPERTIES AUTORCC ON)

if(CONTOUR_COMMAND_IMAGE)
# This is (for now) by default disabled.
target_compile_definitions(contour PRIVATE CONTOUR_COMMAND_IMAGE)
find_package(libsixel REQUIRED)
endif()

if(CONTOUR_PERF_STATS)
target_compile_definitions(contour PRIVATE CONTOUR_PERF_STATS)
endif()
Expand Down Expand Up @@ -115,6 +122,11 @@ elseif(APPLE)
endif()

target_link_libraries(contour terminal yaml-cpp)

if(CONTOUR_COMMAND_IMAGE)
target_link_libraries(contour terminal Libsixel::Libsixel)
endif()

if(FREEBSD)
# FreeBSD does not find yaml-cpp in /usr/local but weirdly everything else.
target_link_directories(contour PUBLIC "/usr/local/lib")
Expand Down
120 changes: 120 additions & 0 deletions src/contour/ContourApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
#include <fmt/format.h>

#include <QtCore/QFile>
#include <QtCore/QMetaEnum>
#include <QtGui/QImage>

#include <chrono>
#include <cstdio>
Expand All @@ -34,6 +36,16 @@
#include <memory>
#include <signal.h>

#if defined(CONTOUR_COMMAND_IMAGE)
#if __has_include(<sixel/sixel.h>)
#include <sixel/sixel.h>
#define HAVE_SIXEL 1
#elif __has_include(<sixel.h>)
#include <sixel.h>
#define HAVE_SIXEL 1
#endif
#endif

#if !defined(_WIN32)
#include <unistd.h>

Expand Down Expand Up @@ -167,6 +179,9 @@ ContourApp::ContourApp(): App("contour", "Contour Terminal Emulator", CONTOUR_VE
link("contour.generate.terminfo", bind(&ContourApp::terminfoAction, this));
link("contour.generate.config", bind(&ContourApp::configAction, this));
link("contour.generate.integration", bind(&ContourApp::integrationAction, this));
#if defined(CONTOUR_COMMAND_IMAGE)
link("contour.image", bind(&ContourApp::imageAction, this));
#endif
}

template <typename Callback>
Expand Down Expand Up @@ -206,6 +221,93 @@ int ContourApp::integrationAction()
});
}

#if defined(CONTOUR_COMMAND_IMAGE)
int sixelWriter(char* data, int size, void* priv)
{
cout.write(data, size);
return size;
};
#endif

terminal::ImageSize terminalCellSize()
{
using namespace terminal;
#if !defined(_WIN32)
auto ws = winsize {};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
return {};

auto const viewSize = ImageSize { Width(ws.ws_xpixel), Height(ws.ws_ypixel) };
auto const pageSize = PageSize { LineCount(ws.ws_row), ColumnCount(ws.ws_col) };
auto const cellSize = viewSize / pageSize;
// fmt::print("cell size: {} / {} = {}\n", viewSize, pageSize, cellSize);
return cellSize;
#else
return {};
#endif
}

int ContourApp::imageAction()
{
#if defined(CONTOUR_COMMAND_IMAGE)
// Possible advanced options:
// - grid cell dimensions
// - image resize policy
// - image alignment policy
// - auto-scroll?
// - top-left position (or current cursor position)

auto const cellSize = terminalCellSize();
if (cellSize.area() == 0)
{
errorlog()("Could not detect terminal's grid cell size in pixels.");
return EXIT_FAILURE;
}

auto const filePath =
QString::fromUtf8(parameters().verbatim.front().data(), (int) parameters().verbatim.front().size());
auto qImage = QImage(filePath).convertToFormat(QImage::Format_RGB888);
if (qImage.format() != QImage::Format_RGB888)
{
errorlog()("Unsupported image format {} in file {}.",
QMetaEnum::fromType<QImage::Format>().valueToKey(qImage.format()),
filePath.toStdString());
return EXIT_FAILURE;
}

auto const requestedNumLines = parameters().uint("contour.image.lines");
auto const requestedNumColumns = parameters().uint("contour.image.columns");
if (requestedNumLines && requestedNumColumns)
{
auto const w = (int) (requestedNumColumns * cellSize.width.value);
auto const h = (int) (requestedNumLines * cellSize.height.value);
qImage = qImage.scaled(QSize(w, h));
}
else if (requestedNumLines)
{
auto const h = (int) (requestedNumLines * cellSize.height.value);
qImage = qImage.scaledToHeight(h);
}
else if (requestedNumColumns)
{
auto const w = (int) (requestedNumColumns * cellSize.width.value);
qImage = qImage.scaledToWidth(w);
}

auto const pixels = const_cast<unsigned char*>(qImage.constBits());
sixel_output_t* output = nullptr;
sixel_output_new(&output, &sixelWriter, nullptr, nullptr);
sixel_dither_t* dither = sixel_dither_get(SIXEL_BUILTIN_XTERM256);
sixel_encode(pixels, qImage.width(), qImage.height(), 3, dither, output);
sixel_dither_unref(dither);
sixel_output_destroy(output);
return EXIT_SUCCESS;
#else
errorlog()("Feature was disabled at compile-time.");
return EXIT_FAILURE;
#endif
}

int ContourApp::configAction()
{
withOutput(parameters(), "contour.generate.config.to", [](auto& _stream) {
Expand Down Expand Up @@ -273,6 +375,24 @@ crispy::cli::Command ContourApp::parameterDefinition() const
"Shows the license, and project URL of the used projects and Contour." },
CLI::Command { "parser-table", "Dumps parser table" },
CLI::Command { "list-debug-tags", "Lists all available debug tags and exits." },
#if defined(CONTOUR_COMMAND_IMAGE)
CLI::Command {
"image",
"Sends an image to the terminal to render it at the current cursor position.",
CLI::OptionList {
// might make sense to allow options in the future,
// such as:
// - grid cell dimensions
// - image alignment policy
// - image resize policy
CLI::Option { "lines", CLI::Value { 0u }, "Number of grid lines this image shall span." },
CLI::Option {
"columns", CLI::Value { 0u }, "Number of grid columns this image shall span." },
},
CLI::CommandList {},
CLI::CommandSelect::Explicit,
CLI::Verbatim { "PATH", "Path to image to be sent." } },
#endif
CLI::Command {
"generate",
"Generation utilities.",
Expand Down
1 change: 1 addition & 0 deletions src/contour/ContourApp.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class ContourApp: public crispy::App
int terminfoAction();
int configAction();
int integrationAction();
int imageAction();
};

} // namespace contour
3 changes: 2 additions & 1 deletion src/crispy/App.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ unsigned screenWidth()
#if !defined(_WIN32)
auto ws = winsize {};
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1)
return ws.ws_col;
if (ws.ws_col != 0)
return ws.ws_col;
#endif

return DefaultWidth;
Expand Down
5 changes: 2 additions & 3 deletions src/crispy/CLI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -816,13 +816,12 @@ namespace // {{{ helpers
auto const& verbatim = _command.verbatim.value();
auto const leftSize =
static_cast<unsigned>(leftPadding.size() + 2 + verbatim.placeholder.size());
assert(columnWidth > leftSize);
auto const actualRightPaddingSize = columnWidth - leftSize;
auto const actualRightPaddingSize = columnWidth > leftSize ? columnWidth - leftSize : 2;
auto const left = leftPadding + stylize("[", HelpElement::Braces)
+ stylize(verbatim.placeholder, HelpElement::Verbatim)
+ stylize("]", HelpElement::Braces) + spaces(actualRightPaddingSize);

_os << left;

auto cursor = columnWidth + 1;
_os << stylize(wordWrapped(verbatim.helpText, columnWidth, _margin, &cursor),
HelpElement::HelpText);
Expand Down
7 changes: 7 additions & 0 deletions src/terminal/primitives.h
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,13 @@ constexpr ColumnOffset& operator-=(ColumnOffset& a, ColumnCount b) noexcept
a.value -= b.value;
return a;
}

constexpr ImageSize operator/(ImageSize viewSize, PageSize pageSize) noexcept
{
return ImageSize { Width::cast_from(unbox<int>(viewSize.width) / unbox<int>(pageSize.columns)),
Height::cast_from(unbox<int>(viewSize.height) / unbox<int>(pageSize.lines)) };
}

// }}}

enum class ScreenType
Expand Down

0 comments on commit c3d9774

Please sign in to comment.