// 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 . #include "application.hpp" #include "image.hpp" #include "tmux.hpp" #include "util.hpp" #include "util/socket.hpp" #include "version.hpp" #include #include #include #include #include #include #include #include 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(); 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> 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(log_path); const auto main_logger = std::make_shared("main", sink); const auto terminal_logger = std::make_shared("terminal", sink); const auto cv_logger = std::make_shared("opencv", sink); const auto vips_logger = std::make_shared("vips", sink); const auto x11_logger = std::make_shared("X11", sink); const auto sixel_logger = std::make_shared("sixel", sink); const auto kitty_logger = std::make_shared("kitty", sink); const auto iterm2_logger = std::make_shared("iterm2", sink); const auto chafa_logger = std::make_shared("chafa", sink); const auto wayland_logger = std::make_shared("wayland", sink); const auto opengl_logger = std::make_shared("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; }