diff --git a/src/app_attachments.cpp b/src/app_attachments.cpp index e95ecce..78ad9ee 100644 --- a/src/app_attachments.cpp +++ b/src/app_attachments.cpp @@ -21,31 +21,27 @@ namespace telegram_tui { namespace { constexpr std::array kAttachmentTypes = { - AttachmentType::Photo, - AttachmentType::Video, - AttachmentType::Document, - AttachmentType::Audio, - AttachmentType::Voice, - AttachmentType::Animation, - AttachmentType::Sticker, + AttachmentType::Photo, AttachmentType::Video, AttachmentType::Document, + AttachmentType::Audio, AttachmentType::Voice, AttachmentType::Animation, + AttachmentType::Sticker, }; std::string attachment_type_label(AttachmentType type) { switch (type) { - case AttachmentType::Photo: - return "Photos"; - case AttachmentType::Video: - return "Videos"; - case AttachmentType::Document: - return "Documents"; - case AttachmentType::Audio: - return "Audio"; - case AttachmentType::Voice: - return "Voice"; - case AttachmentType::Animation: - return "GIFs"; - case AttachmentType::Sticker: - return "Stickers"; + case AttachmentType::Photo: + return "Photos"; + case AttachmentType::Video: + return "Videos"; + case AttachmentType::Document: + return "Documents"; + case AttachmentType::Audio: + return "Audio"; + case AttachmentType::Voice: + return "Voice"; + case AttachmentType::Animation: + return "GIFs"; + case AttachmentType::Sticker: + return "Stickers"; } return "Media"; } @@ -59,9 +55,9 @@ std::filesystem::path downloads_directory() { } std::string sanitize_filename(std::string value) { - for (char& ch : value) { - if (ch == '/' || ch == '\\' || ch == ':' || ch == '*' || ch == '?' || ch == '"' || ch == '<' || - ch == '>' || ch == '|') { + for (char &ch : value) { + if (ch == '/' || ch == '\\' || ch == ':' || ch == '*' || ch == '?' || ch == '"' || + ch == '<' || ch == '>' || ch == '|') { ch = '_'; } } @@ -72,7 +68,7 @@ std::string sanitize_filename(std::string value) { return value; } -std::filesystem::path preferred_attachment_filename(const AttachmentInfo& attachment) { +std::filesystem::path preferred_attachment_filename(const AttachmentInfo &attachment) { std::string filename = sanitize_filename(attachment.name); if (filename.empty() || filename == "." || filename == "..") { filename = "attachment"; @@ -80,7 +76,8 @@ std::filesystem::path preferred_attachment_filename(const AttachmentInfo& attach std::filesystem::path candidate(filename); if (!candidate.has_extension() && !attachment.local_path.empty()) { - const std::string extension = std::filesystem::path(attachment.local_path).extension().string(); + const std::string extension = + std::filesystem::path(attachment.local_path).extension().string(); if (!extension.empty()) { candidate += extension; } @@ -88,41 +85,44 @@ std::filesystem::path preferred_attachment_filename(const AttachmentInfo& attach return candidate; } -std::filesystem::path unique_destination_path( - const std::filesystem::path& directory, const std::filesystem::path& filename) { +std::filesystem::path unique_destination_path(const std::filesystem::path &directory, + const std::filesystem::path &filename) { const std::filesystem::path stem = filename.stem(); const std::filesystem::path extension = filename.extension(); std::filesystem::path candidate = directory / filename; for (int index = 1; std::filesystem::exists(candidate); ++index) { - candidate = directory / (stem.string() + " (" + std::to_string(index) + ")" + extension.string()); + candidate = directory / (stem.string() + " (" + std::to_string(index) + ")" + + extension.string()); } return candidate; } -std::string inline_preview_unavailable_message(const AttachmentInfo& attachment) { +std::string inline_preview_unavailable_message(const AttachmentInfo &attachment) { switch (attachment.type) { - case AttachmentType::Photo: - if (!attachment.is_downloaded || attachment.local_path.empty()) { - return "Photo is not downloaded yet."; - } - if (!std::filesystem::exists(attachment.local_path)) { - return "Downloaded photo file is missing on disk."; - } - return "No supported preview backend found. Install `kitten`, `img2sixel`, or `chafa`."; - case AttachmentType::Video: - if (!attachment.is_downloaded || attachment.local_path.empty()) { - return "Video is not downloaded yet."; - } - if (!std::filesystem::exists(attachment.local_path)) { - return "Downloaded video file is missing on disk."; - } - return "Video preview requires `ffmpegthumbnailer` or `ffmpeg`, plus `kitten`, `img2sixel`, or `chafa`."; - default: - return "Inline preview is only supported for photos and videos."; + case AttachmentType::Photo: + if (!attachment.is_downloaded || attachment.local_path.empty()) { + return "Photo is not downloaded yet."; + } + if (!std::filesystem::exists(attachment.local_path)) { + return "Downloaded photo file is missing on disk."; + } + return "No supported preview backend found. Install `kitten`, `img2sixel`, or " + "`chafa`."; + case AttachmentType::Video: + if (!attachment.is_downloaded || attachment.local_path.empty()) { + return "Video is not downloaded yet."; + } + if (!std::filesystem::exists(attachment.local_path)) { + return "Downloaded video file is missing on disk."; + } + return "Video preview requires `ffmpegthumbnailer` or `ffmpeg`, plus `kitten`, " + "`img2sixel`, or `chafa`."; + default: + return "Inline preview is only supported for photos and videos."; } } -int attachment_progress_percent(const AttachmentInfo& attachment) { +int attachment_progress_percent(const AttachmentInfo &attachment) { if (attachment.is_downloaded) { return 100; } @@ -133,7 +133,7 @@ int attachment_progress_percent(const AttachmentInfo& attachment) { return static_cast((downloaded * 100) / attachment.size_bytes); } -std::string attachment_progress_bar(const AttachmentInfo& attachment, int width) { +std::string attachment_progress_bar(const AttachmentInfo &attachment, int width) { if (width <= 2) { return {}; } @@ -142,12 +142,13 @@ std::string attachment_progress_bar(const AttachmentInfo& attachment, int width) if (attachment.is_downloaded) { filled = inner_width; } else if (attachment.size_bytes > 0 && attachment.downloaded_size > 0) { - filled = std::clamp((inner_width * attachment_progress_percent(attachment)) / 100, 0, inner_width); + filled = std::clamp((inner_width * attachment_progress_percent(attachment)) / 100, + 0, inner_width); } return "[" + std::string(filled, '#') + std::string(inner_width - filled, '.') + "]"; } -std::string shell_quote(const std::string& value) { +std::string shell_quote(const std::string &value) { std::string quoted = "'"; for (char ch : value) { if (ch == '\'') { @@ -160,8 +161,8 @@ std::string shell_quote(const std::string& value) { return quoted; } -std::string run_command_capture(const std::string& command) { - FILE* pipe = popen(command.c_str(), "r"); +std::string run_command_capture(const std::string &command) { + FILE *pipe = popen(command.c_str(), "r"); if (pipe == nullptr) { return {}; } @@ -178,7 +179,7 @@ std::string run_command_capture(const std::string& command) { return output; } -std::vector split_preserved_lines(const std::string& text, int fallback_wrap_width) { +std::vector split_preserved_lines(const std::string &text, int fallback_wrap_width) { std::vector lines; std::stringstream stream(text); std::string line; @@ -202,20 +203,21 @@ std::vector split_preserved_lines(const std::string& text, int fall return lines; } -bool command_exists(const char* command) { +bool command_exists(const char *command) { static std::map cache; const auto cached = cache.find(command); if (cached != cache.end()) { return cached->second; } - const std::string resolved = run_command_capture("command -v " + std::string(command) + " 2>/dev/null"); + const std::string resolved = + run_command_capture("command -v " + std::string(command) + " 2>/dev/null"); const bool exists = !trim_copy(resolved).empty(); cache[command] = exists; return exists; } -void write_terminal_output(const std::string& data) { +void write_terminal_output(const std::string &data) { if (data.empty()) { return; } @@ -223,11 +225,11 @@ void write_terminal_output(const std::string& data) { std::fflush(stdout); } -int run_command_to_terminal(const std::string& command) { +int run_command_to_terminal(const std::string &command) { return std::system(command.c_str()); } -bool spawn_detached_process(const std::vector& args) { +bool spawn_detached_process(const std::vector &args) { if (args.empty()) { return false; } @@ -256,10 +258,10 @@ bool spawn_detached_process(const std::vector& args) { } } - std::vector argv; + std::vector argv; argv.reserve(args.size() + 1); - for (const std::string& arg : args) { - argv.push_back(const_cast(arg.c_str())); + for (const std::string &arg : args) { + argv.push_back(const_cast(arg.c_str())); } argv.push_back(nullptr); execvp(argv.front(), argv.data()); @@ -275,7 +277,7 @@ bool spawn_detached_process(const std::vector& args) { return WIFEXITED(status) && WEXITSTATUS(status) == 0; } -bool open_with_system_app(const std::string& path) { +bool open_with_system_app(const std::string &path) { if (path.empty()) { return false; } @@ -298,7 +300,7 @@ bool open_with_system_app(const std::string& path) { std::string configured_image_protocol() { std::string value = trim_copy(get_env("TELEGRAM_TUI_IMAGE_PROTOCOL")); std::transform(value.begin(), value.end(), value.begin(), - [](unsigned char ch) { return static_cast(std::tolower(ch)); }); + [](unsigned char ch) { return static_cast(std::tolower(ch)); }); if (value == "kitty" || value == "sixels" || value == "symbols") { return value; } @@ -307,7 +309,7 @@ std::string configured_image_protocol() { std::string lower_copy(std::string value) { std::transform(value.begin(), value.end(), value.begin(), - [](unsigned char ch) { return static_cast(std::tolower(ch)); }); + [](unsigned char ch) { return static_cast(std::tolower(ch)); }); return value; } @@ -320,8 +322,8 @@ bool terminal_supports_sixels() { const std::string term = lower_copy(get_env("TERM")); const std::string term_program = lower_copy(get_env("TERM_PROGRAM")); return term == "foot" || term.find("xterm") != std::string::npos || - term.find("mlterm") != std::string::npos || term.find("wezterm") != std::string::npos || - term_program == "wezterm"; + term.find("mlterm") != std::string::npos || + term.find("wezterm") != std::string::npos || term_program == "wezterm"; } struct TerminalWindowSize { @@ -333,7 +335,7 @@ struct TerminalWindowSize { TerminalWindowSize terminal_window_size() { TerminalWindowSize size; - struct winsize ws {}; + struct winsize ws{}; if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) { size.cols = ws.ws_col; size.rows = ws.ws_row; @@ -374,7 +376,7 @@ std::string truncate_text(std::string text, int max_width) { return text; } -} // namespace +} // namespace void App::reset_attachment_viewer_send_preview() { attachment_viewer_send_on_enter_ = false; @@ -396,14 +398,15 @@ void App::sync_message_attachment_selection() { return; } - for (const auto& message : chat_it->second.messages) { + for (const auto &message : chat_it->second.messages) { if (message.id == message_attachment_message_id_ && message.has_attachment) { return; } } message_attachment_message_id_ = 0; - for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend(); ++it) { + for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend(); + ++it) { if (it->has_attachment) { message_attachment_message_id_ = it->id; return; @@ -428,7 +431,7 @@ bool App::move_message_attachment_selection(int delta) { } std::vector attachment_message_ids; - for (const auto& message : chat_it->second.messages) { + for (const auto &message : chat_it->second.messages) { if (message.has_attachment) { attachment_message_ids.push_back(message.id); } @@ -438,10 +441,8 @@ bool App::move_message_attachment_selection(int delta) { return false; } - auto selected = std::find( - attachment_message_ids.begin(), - attachment_message_ids.end(), - message_attachment_message_id_); + auto selected = std::find(attachment_message_ids.begin(), attachment_message_ids.end(), + message_attachment_message_id_); if (selected == attachment_message_ids.end()) { message_attachment_message_id_ = attachment_message_ids.back(); return true; @@ -449,11 +450,13 @@ bool App::move_message_attachment_selection(int delta) { const std::ptrdiff_t index = selected - attachment_message_ids.begin(); const std::ptrdiff_t next_index = index + (delta < 0 ? -1 : 1); - if (next_index < 0 || next_index >= static_cast(attachment_message_ids.size())) { + if (next_index < 0 || + next_index >= static_cast(attachment_message_ids.size())) { return false; } - message_attachment_message_id_ = attachment_message_ids[static_cast(next_index)]; + message_attachment_message_id_ = + attachment_message_ids[static_cast(next_index)]; return true; } @@ -468,7 +471,7 @@ std::optional App::selected_message_attachment() const { return std::nullopt; } - for (const auto& message : chat_it->second.messages) { + for (const auto &message : chat_it->second.messages) { if (message.id == message_attachment_message_id_ && message.has_attachment) { return message.attachment; } @@ -502,7 +505,8 @@ std::optional App::selected_attachment() const { const AttachmentType selected_type = kAttachmentTypes[category_index]; std::vector attachments; - for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend(); ++it) { + for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend(); + ++it) { if (it->has_attachment && it->attachment.type == selected_type) { attachments.push_back(it->attachment); } @@ -522,7 +526,8 @@ std::optional App::selected_attachment() const { return attachments[static_cast(selection_index)]; } -std::string App::render_attachment_preview(const AttachmentInfo& attachment, int width, int height) const { +std::string App::render_attachment_preview(const AttachmentInfo &attachment, int width, + int height) const { const std::string preview_path = attachment_preview_path(attachment); if (preview_path.empty()) { return inline_preview_unavailable_message(attachment); @@ -530,10 +535,11 @@ std::string App::render_attachment_preview(const AttachmentInfo& attachment, int const int render_width = std::max(10, width); const int render_height = std::max(4, height); - const std::string command = - "command -v chafa >/dev/null 2>&1 && chafa --format symbols --symbols all --colors none --size " + - std::to_string(render_width) + "x" + std::to_string(render_height) + " " + - shell_quote(preview_path) + " 2>/dev/null"; + const std::string command = "command -v chafa >/dev/null 2>&1 && chafa --format symbols " + "--symbols all --colors none --size " + + std::to_string(render_width) + "x" + + std::to_string(render_height) + " " + + shell_quote(preview_path) + " 2>/dev/null"; std::string preview = run_command_capture(command); if (!preview.empty()) { return preview; @@ -541,30 +547,33 @@ std::string App::render_attachment_preview(const AttachmentInfo& attachment, int return "No supported preview backend found. Install `kitten`, `img2sixel`, or `chafa`."; } -std::string App::build_attachment_preview_graphics(const AttachmentInfo& attachment, int width, int height) const { +std::string App::build_attachment_preview_graphics(const AttachmentInfo &attachment, int width, + int height) const { const std::string preview_path = attachment_preview_path(attachment); if (preview_path.empty()) { return {}; } const std::string forced_protocol = configured_image_protocol(); - const bool want_sixels = - forced_protocol == "sixels" || (forced_protocol.empty() && terminal_supports_sixels()); + const bool want_sixels = forced_protocol == "sixels" || + (forced_protocol.empty() && terminal_supports_sixels()); if (want_sixels) { if (command_exists("chafa")) { - return "chafa --format sixels --size " + std::to_string(std::max(4, width)) + "x" + - std::to_string(std::max(2, height)) + " " + - shell_quote(preview_path) + " >/dev/tty 2>/dev/null"; + return "chafa --format sixels --size " + + std::to_string(std::max(4, width)) + "x" + + std::to_string(std::max(2, height)) + " " + + shell_quote(preview_path) + " >/dev/tty 2>/dev/null"; } if (command_exists("img2sixel")) { - return "img2sixel -w " + std::to_string(approximate_pixel_width(width)) + "px -h " + - std::to_string(approximate_pixel_height(height)) + "px " + - shell_quote(preview_path) + " >/dev/tty 2>/dev/null"; + return "img2sixel -w " + std::to_string(approximate_pixel_width(width)) + + "px -h " + std::to_string(approximate_pixel_height(height)) + "px " + + shell_quote(preview_path) + " >/dev/tty 2>/dev/null"; } } - if ((forced_protocol == "kitty" || (forced_protocol.empty() && terminal_supports_kitty_graphics())) && - command_exists("kitten")) { + if ((forced_protocol == "kitty" || + (forced_protocol.empty() && terminal_supports_kitty_graphics())) && + command_exists("kitten")) { TerminalWindowSize size = terminal_window_size(); if (size.cols <= 0) { size.cols = COLS > 0 ? COLS : width; @@ -578,51 +587,56 @@ std::string App::build_attachment_preview_graphics(const AttachmentInfo& attachm if (size.height_pixels <= 0) { size.height_pixels = std::max(1, size.rows * 16); } - return "kitten icat --stdin=no --passthrough=none --transfer-mode=stream --align=left " - "--place " + std::to_string(std::max(1, width)) + "x" + std::to_string(std::max(1, height)) + - "@0x0 --use-window-size " + std::to_string(size.cols) + "," + std::to_string(size.rows) + "," + - std::to_string(size.width_pixels) + "," + std::to_string(size.height_pixels) + " " + - shell_quote(preview_path) + " >/dev/tty 2>/dev/null"; + return "kitten icat --stdin=no --passthrough=none --transfer-mode=stream " + "--align=left " + "--place " + + std::to_string(std::max(1, width)) + "x" + + std::to_string(std::max(1, height)) + "@0x0 --use-window-size " + + std::to_string(size.cols) + "," + std::to_string(size.rows) + "," + + std::to_string(size.width_pixels) + "," + + std::to_string(size.height_pixels) + " " + shell_quote(preview_path) + + " >/dev/tty 2>/dev/null"; } if (command_exists("chafa")) { return "chafa --size " + std::to_string(std::max(4, width)) + "x" + - std::to_string(std::max(2, height)) + " " + - shell_quote(preview_path) + " >/dev/tty 2>/dev/null"; + std::to_string(std::max(2, height)) + " " + shell_quote(preview_path) + + " >/dev/tty 2>/dev/null"; } return {}; } -void App::request_attachment_download(const AttachmentInfo& attachment, bool open_after_download) { +void App::request_attachment_download(const AttachmentInfo &attachment, bool open_after_download) { if (attachment.file_id == 0) { status_line_ = "Attachment file is unavailable."; return; } - pending_attachment_open_ = open_after_download ? std::optional(attachment) : std::nullopt; + pending_attachment_open_ = + open_after_download ? std::optional(attachment) : std::nullopt; if (open_after_download) { pending_attachment_download_.reset(); } td_.send({ - {"@type", "downloadFile"}, - {"file_id", attachment.file_id}, - {"priority", open_after_download ? 16 : 1}, - {"offset", 0}, - {"limit", 0}, - {"synchronous", false}, + {"@type", "downloadFile"}, + {"file_id", attachment.file_id}, + {"priority", open_after_download ? 16 : 1}, + {"offset", 0}, + {"limit", 0}, + {"synchronous", false}, }); if (open_after_download) { status_line_ = "Downloading attachment to open..."; } else if (pending_attachment_download_.has_value() && - pending_attachment_download_->file_id == attachment.file_id) { + pending_attachment_download_->file_id == attachment.file_id) { status_line_ = "Downloading attachment to ~/Downloads..."; } else { status_line_ = "Downloading attachment..."; } } -void App::open_attachment(const AttachmentInfo& attachment) { +void App::open_attachment(const AttachmentInfo &attachment) { reset_attachment_viewer_send_preview(); if (!attachment.is_downloaded || attachment.local_path.empty()) { request_attachment_download(attachment, true); @@ -639,7 +653,8 @@ void App::open_attachment(const AttachmentInfo& attachment) { if (open_with_system_app(attachment.local_path)) { status_line_ = "Video playback failed. Opened in the system app."; } else { - status_line_ = "No supported video player found. Install `mpv` or `ffplay`."; + status_line_ = + "No supported video player found. Install `mpv` or `ffplay`."; } return; } @@ -652,7 +667,8 @@ void App::open_attachment(const AttachmentInfo& attachment) { attachment_viewer_frame_index_ = 0; if (attachment.type == AttachmentType::Video) { attachment_viewer_animation_frames_ = attachment_animation_frames(attachment); - attachment_viewer_next_frame_at_ = std::chrono::steady_clock::now() + std::chrono::milliseconds(125); + attachment_viewer_next_frame_at_ = + std::chrono::steady_clock::now() + std::chrono::milliseconds(125); } if (!has_inline_attachment_preview(attachment, preview_width, preview_height)) { attachment_viewer_attachment_.reset(); @@ -660,7 +676,8 @@ void App::open_attachment(const AttachmentInfo& attachment) { attachment_viewer_is_animated_ = false; attachment_viewer_frame_index_ = 0; if (open_with_system_app(attachment.local_path)) { - status_line_ = "Inline preview unavailable. Opened attachment in the system app."; + status_line_ = + "Inline preview unavailable. Opened attachment in the system app."; } else { status_line_ = inline_preview_unavailable_message(attachment); } @@ -674,7 +691,7 @@ void App::open_attachment(const AttachmentInfo& attachment) { status_line_ = "Attachment preview."; } -void App::download_attachment(const AttachmentInfo& attachment) { +void App::download_attachment(const AttachmentInfo &attachment) { pending_attachment_open_.reset(); if (!attachment.is_downloaded || attachment.local_path.empty()) { pending_attachment_download_ = attachment; @@ -685,7 +702,7 @@ void App::download_attachment(const AttachmentInfo& attachment) { export_attachment_to_downloads(attachment); } -void App::delete_attachment(const AttachmentInfo& attachment) { +void App::delete_attachment(const AttachmentInfo &attachment) { if (!attachment.is_downloaded || attachment.local_path.empty()) { status_line_ = "Attachment is not downloaded."; return; @@ -695,7 +712,8 @@ void App::delete_attachment(const AttachmentInfo& attachment) { return; } - if (attachment_viewer_attachment_.has_value() && attachment_viewer_attachment_->file_id == attachment.file_id) { + if (attachment_viewer_attachment_.has_value() && + attachment_viewer_attachment_->file_id == attachment.file_id) { attachment_viewer_open_ = false; attachment_viewer_attachment_.reset(); attachment_viewer_animation_frames_.clear(); @@ -706,13 +724,13 @@ void App::delete_attachment(const AttachmentInfo& attachment) { } td_.send({ - {"@type", "deleteFile"}, - {"file_id", attachment.file_id}, + {"@type", "deleteFile"}, + {"file_id", attachment.file_id}, }); status_line_ = "Deleting cached attachment..."; } -bool App::export_attachment_to_downloads(const AttachmentInfo& attachment) { +bool App::export_attachment_to_downloads(const AttachmentInfo &attachment) { if (!attachment.is_downloaded || attachment.local_path.empty()) { status_line_ = "Attachment is not downloaded yet."; return false; @@ -726,20 +744,19 @@ bool App::export_attachment_to_downloads(const AttachmentInfo& attachment) { const std::filesystem::path target_name = preferred_attachment_filename(attachment); try { std::filesystem::create_directories(target_dir); - const std::filesystem::path target_path = unique_destination_path(target_dir, target_name); - std::filesystem::copy_file( - attachment.local_path, - target_path, - std::filesystem::copy_options::none); + const std::filesystem::path target_path = + unique_destination_path(target_dir, target_name); + std::filesystem::copy_file(attachment.local_path, target_path, + std::filesystem::copy_options::none); status_line_ = "Saved to " + target_path.string(); return true; - } catch (const std::exception& error) { + } catch (const std::exception &error) { status_line_ = std::string("Failed to save attachment: ") + error.what(); return false; } } -bool App::play_video_attachment(const AttachmentInfo& attachment) { +bool App::play_video_attachment(const AttachmentInfo &attachment) { if (attachment.local_path.empty() || !std::filesystem::exists(attachment.local_path)) { status_line_ = "Downloaded video file is missing on disk."; return false; @@ -747,27 +764,27 @@ bool App::play_video_attachment(const AttachmentInfo& attachment) { if (command_exists("mpv")) { if (spawn_detached_process({ - "mpv", - "--force-window=immediate", - "--keep-open=no", - "--quiet", - "--terminal=no", - "--input-terminal=no", - attachment.local_path, - })) { + "mpv", + "--force-window=immediate", + "--keep-open=no", + "--quiet", + "--terminal=no", + "--input-terminal=no", + attachment.local_path, + })) { status_line_ = "Opened video in mpv."; return true; } } if (command_exists("ffplay")) { if (spawn_detached_process({ - "ffplay", - "-autoexit", - "-loglevel", - "error", - "-nostats", - attachment.local_path, - })) { + "ffplay", + "-autoexit", + "-loglevel", + "error", + "-nostats", + attachment.local_path, + })) { status_line_ = "Opened video in ffplay."; return true; } @@ -775,28 +792,26 @@ bool App::play_video_attachment(const AttachmentInfo& attachment) { return false; } -void App::refresh_attachment_viewer_content(const AttachmentInfo& attachment) { +void App::refresh_attachment_viewer_content(const AttachmentInfo &attachment) { const int preview_width = COLS > 0 ? COLS - 8 : 72; const int preview_height = LINES > 0 ? LINES - 8 : 24; if (!attachment_viewer_is_animated_ && - !build_attachment_preview_graphics(attachment, preview_width, preview_height).empty()) { + !build_attachment_preview_graphics(attachment, preview_width, preview_height).empty()) { attachment_viewer_lines_.clear(); return; } - const std::string preview = render_attachment_preview( - attachment, - preview_width, - preview_height); - attachment_viewer_lines_ = split_preserved_lines( - preview, - std::max(10, (COLS > 0 ? COLS : 80) - 8)); + const std::string preview = + render_attachment_preview(attachment, preview_width, preview_height); + attachment_viewer_lines_ = + split_preserved_lines(preview, std::max(10, (COLS > 0 ? COLS : 80) - 8)); if (attachment_viewer_lines_.empty()) { attachment_viewer_lines_.push_back("Preview is empty."); } } -std::vector App::attachment_animation_frames(const AttachmentInfo& attachment) const { - if (attachment.type != AttachmentType::Video || !attachment.is_downloaded || attachment.local_path.empty()) { +std::vector App::attachment_animation_frames(const AttachmentInfo &attachment) const { + if (attachment.type != AttachmentType::Video || !attachment.is_downloaded || + attachment.local_path.empty()) { return {}; } if (!std::filesystem::exists(attachment.local_path) || !command_exists("ffmpeg")) { @@ -804,13 +819,14 @@ std::vector App::attachment_animation_frames(const AttachmentInfo& } const std::filesystem::path preview_dir = - files_dir_ / "previews" / (std::string("video-") + std::to_string(attachment.file_id)); + files_dir_ / "previews" / + (std::string("video-") + std::to_string(attachment.file_id)); std::vector frames; try { std::filesystem::create_directories(preview_dir); const auto source_time = std::filesystem::last_write_time(attachment.local_path); bool needs_regeneration = true; - for (const auto& entry : std::filesystem::directory_iterator(preview_dir)) { + for (const auto &entry : std::filesystem::directory_iterator(preview_dir)) { if (!entry.is_regular_file() || entry.path().extension() != ".jpg") { continue; } @@ -824,32 +840,32 @@ std::vector App::attachment_animation_frames(const AttachmentInfo& return frames; } - for (const auto& entry : std::filesystem::directory_iterator(preview_dir)) { + for (const auto &entry : std::filesystem::directory_iterator(preview_dir)) { if (entry.is_regular_file() && entry.path().extension() == ".jpg") { std::filesystem::remove(entry.path()); } } frames.clear(); - } catch (const std::exception&) { + } catch (const std::exception &) { return {}; } const std::string pattern = (preview_dir / "frame-%04d.jpg").string(); const std::string command = - "ffmpeg -y -i " + shell_quote(attachment.local_path) + - " -vf 'fps=6,scale=640:-1:force_original_aspect_ratio=decrease' -frames:v 48 " + - shell_quote(pattern) + " >/dev/null 2>&1"; + "ffmpeg -y -i " + shell_quote(attachment.local_path) + + " -vf 'fps=6,scale=640:-1:force_original_aspect_ratio=decrease' -frames:v 48 " + + shell_quote(pattern) + " >/dev/null 2>&1"; if (run_command_to_terminal(command) != 0) { return {}; } try { - for (const auto& entry : std::filesystem::directory_iterator(preview_dir)) { + for (const auto &entry : std::filesystem::directory_iterator(preview_dir)) { if (entry.is_regular_file() && entry.path().extension() == ".jpg") { frames.push_back(entry.path().string()); } } - } catch (const std::exception&) { + } catch (const std::exception &) { return {}; } std::sort(frames.begin(), frames.end()); @@ -857,8 +873,9 @@ std::vector App::attachment_animation_frames(const AttachmentInfo& } bool App::advance_attachment_animation() { - if (!attachment_viewer_open_ || !attachment_viewer_is_animated_ || attachment_viewer_animation_frames_.empty() || - !attachment_viewer_attachment_.has_value()) { + if (!attachment_viewer_open_ || !attachment_viewer_is_animated_ || + attachment_viewer_animation_frames_.empty() || + !attachment_viewer_attachment_.has_value()) { return false; } const auto now = std::chrono::steady_clock::now(); @@ -867,23 +884,24 @@ bool App::advance_attachment_animation() { } attachment_viewer_frame_index_ = - (attachment_viewer_frame_index_ + 1) % attachment_viewer_animation_frames_.size(); + (attachment_viewer_frame_index_ + 1) % attachment_viewer_animation_frames_.size(); attachment_viewer_next_frame_at_ = now + std::chrono::milliseconds(125); attachment_preview_signature_.clear(); refresh_attachment_viewer_content(*attachment_viewer_attachment_); return true; } -std::string App::attachment_preview_path(const AttachmentInfo& attachment) const { +std::string App::attachment_preview_path(const AttachmentInfo &attachment) const { if (!attachment.is_downloaded || attachment.local_path.empty()) { return {}; } if (!std::filesystem::exists(attachment.local_path)) { return {}; } - if (attachment_viewer_attachment_.has_value() && attachment_viewer_attachment_->file_id == attachment.file_id && - !attachment_viewer_animation_frames_.empty() && - attachment_viewer_frame_index_ < attachment_viewer_animation_frames_.size()) { + if (attachment_viewer_attachment_.has_value() && + attachment_viewer_attachment_->file_id == attachment.file_id && + !attachment_viewer_animation_frames_.empty() && + attachment_viewer_frame_index_ < attachment_viewer_animation_frames_.size()) { return attachment_viewer_animation_frames_[attachment_viewer_frame_index_]; } if (attachment.type == AttachmentType::Photo) { @@ -894,27 +912,30 @@ std::string App::attachment_preview_path(const AttachmentInfo& attachment) const } const std::filesystem::path preview_dir = files_dir_ / "previews"; - const std::filesystem::path preview_path = preview_dir / (std::to_string(attachment.file_id) + ".jpg"); + const std::filesystem::path preview_path = + preview_dir / (std::to_string(attachment.file_id) + ".jpg"); try { std::filesystem::create_directories(preview_dir); if (std::filesystem::exists(preview_path)) { - const auto source_time = std::filesystem::last_write_time(attachment.local_path); + const auto source_time = + std::filesystem::last_write_time(attachment.local_path); const auto preview_time = std::filesystem::last_write_time(preview_path); if (preview_time >= source_time) { return preview_path.string(); } } - } catch (const std::exception&) { + } catch (const std::exception &) { return {}; } std::string command; if (command_exists("ffmpegthumbnailer")) { command = "ffmpegthumbnailer -i " + shell_quote(attachment.local_path) + " -o " + - shell_quote(preview_path.string()) + " -s 0 -q 8 >/dev/null 2>&1"; + shell_quote(preview_path.string()) + " -s 0 -q 8 >/dev/null 2>&1"; } else if (command_exists("ffmpeg")) { command = "ffmpeg -y -ss 1 -i " + shell_quote(attachment.local_path) + - " -frames:v 1 -vf thumbnail " + shell_quote(preview_path.string()) + " >/dev/null 2>&1"; + " -frames:v 1 -vf thumbnail " + shell_quote(preview_path.string()) + + " >/dev/null 2>&1"; } else { return {}; } @@ -925,11 +946,13 @@ std::string App::attachment_preview_path(const AttachmentInfo& attachment) const return preview_path.string(); } -bool App::has_inline_attachment_preview(const AttachmentInfo& attachment, int width, int height) const { +bool App::has_inline_attachment_preview(const AttachmentInfo &attachment, int width, + int height) const { if (attachment_preview_path(attachment).empty()) { return false; } - return command_exists("chafa") || !build_attachment_preview_graphics(attachment, width, height).empty(); + return command_exists("chafa") || + !build_attachment_preview_graphics(attachment, width, height).empty(); } void App::clear_attachment_preview_graphics() { @@ -956,27 +979,28 @@ void App::render_attachment_preview_graphics(int top, int left, int width, int h return; } - const AttachmentInfo& attachment = *attachment_viewer_attachment_; + const AttachmentInfo &attachment = *attachment_viewer_attachment_; const std::string preview_path = attachment_preview_path(attachment); if (preview_path.empty() || attachment_viewer_is_animated_) { clear_attachment_preview_graphics(); return; } const std::string forced_protocol = configured_image_protocol(); - const bool want_kitty = - forced_protocol == "kitty" || (forced_protocol.empty() && terminal_supports_kitty_graphics()); - const std::string signature = - preview_path + "|" + std::to_string(width) + "x" + std::to_string(height) + "|" + - std::to_string(top) + ":" + std::to_string(left) + "|" + forced_protocol; - const bool want_sixels = - forced_protocol == "sixels" || (forced_protocol.empty() && terminal_supports_sixels()); + const bool want_kitty = forced_protocol == "kitty" || + (forced_protocol.empty() && terminal_supports_kitty_graphics()); + const std::string signature = preview_path + "|" + std::to_string(width) + "x" + + std::to_string(height) + "|" + std::to_string(top) + ":" + + std::to_string(left) + "|" + forced_protocol; + const bool want_sixels = forced_protocol == "sixels" || + (forced_protocol.empty() && terminal_supports_sixels()); const std::string command = build_attachment_preview_graphics(attachment, width, height); if (command.empty()) { clear_attachment_preview_graphics(); return; } - const bool same_signature = attachment_preview_signature_ == signature && attachment_preview_graphics_visible_; + const bool same_signature = + attachment_preview_signature_ == signature && attachment_preview_graphics_visible_; if (!same_signature) { clear_attachment_preview_graphics(); } @@ -997,18 +1021,23 @@ void App::render_attachment_preview_graphics(int top, int left, int width, int h } const std::string kitty_command = - "kitten icat --stdin=no --passthrough=none --transfer-mode=stream --align=left " - "--place " + std::to_string(std::max(1, width)) + "x" + std::to_string(std::max(1, height)) + - "@" + std::to_string(std::max(0, left)) + "x" + std::to_string(std::max(0, top)) + - " --use-window-size " + std::to_string(size.cols) + "," + std::to_string(size.rows) + "," + - std::to_string(size.width_pixels) + "," + std::to_string(size.height_pixels) + - " " + shell_quote(preview_path) + " >/dev/tty 2>/dev/null"; + "kitten icat --stdin=no --passthrough=none --transfer-mode=stream " + "--align=left " + "--place " + + std::to_string(std::max(1, width)) + "x" + + std::to_string(std::max(1, height)) + "@" + + std::to_string(std::max(0, left)) + "x" + std::to_string(std::max(0, top)) + + " --use-window-size " + std::to_string(size.cols) + "," + + std::to_string(size.rows) + "," + std::to_string(size.width_pixels) + "," + + std::to_string(size.height_pixels) + " " + shell_quote(preview_path) + + " >/dev/tty 2>/dev/null"; if (run_command_to_terminal(kitty_command) != 0) { attachment_preview_graphics_is_sixel_ = false; return; } } else { - write_terminal_output("\0337\033[" + std::to_string(top + 1) + ";" + std::to_string(left + 1) + "H"); + write_terminal_output("\0337\033[" + std::to_string(top + 1) + ";" + + std::to_string(left + 1) + "H"); if (run_command_to_terminal(command) != 0) { write_terminal_output("\0338"); attachment_preview_graphics_is_sixel_ = false; @@ -1023,177 +1052,178 @@ void App::render_attachment_preview_graphics(int top, int left, int width, int h void App::handle_attachments_menu_key(int ch) { switch (ch) { - case 27: - case 'm': - attachment_action_menu_open_ = false; - attachment_viewer_open_ = false; - attachment_viewer_attachment_.reset(); - attachment_viewer_animation_frames_.clear(); - attachment_viewer_is_animated_ = false; - attachment_viewer_frame_index_ = 0; - reset_attachment_viewer_send_preview(); - clear_attachment_preview_graphics(); - attachments_menu_open_ = false; - status_line_ = "Closed attachments."; - return; - case ' ': - if (!selected_attachment().has_value()) { - status_line_ = "No attachment selected."; - return; - } - attachment_action_index_ = 0; - attachment_action_menu_open_ = true; - status_line_ = "Attachment actions."; - return; - case KEY_LEFT: - attachment_category_index_ = - (attachment_category_index_ + static_cast(kAttachmentTypes.size()) - 1) % - static_cast(kAttachmentTypes.size()); - attachment_selection_index_ = 0; - return; - case KEY_RIGHT: - attachment_category_index_ = - (attachment_category_index_ + 1) % static_cast(kAttachmentTypes.size()); - attachment_selection_index_ = 0; - return; - case KEY_UP: - if (attachment_selection_index_ > 0) { - --attachment_selection_index_; - } - return; - case KEY_DOWN: - ++attachment_selection_index_; - return; - default: + case 27: + case 'm': + attachment_action_menu_open_ = false; + attachment_viewer_open_ = false; + attachment_viewer_attachment_.reset(); + attachment_viewer_animation_frames_.clear(); + attachment_viewer_is_animated_ = false; + attachment_viewer_frame_index_ = 0; + reset_attachment_viewer_send_preview(); + clear_attachment_preview_graphics(); + attachments_menu_open_ = false; + status_line_ = "Closed attachments."; + return; + case ' ': + if (!selected_attachment().has_value()) { + status_line_ = "No attachment selected."; return; + } + attachment_action_index_ = 0; + attachment_action_menu_open_ = true; + status_line_ = "Attachment actions."; + return; + case KEY_LEFT: + attachment_category_index_ = (attachment_category_index_ + + static_cast(kAttachmentTypes.size()) - 1) % + static_cast(kAttachmentTypes.size()); + attachment_selection_index_ = 0; + return; + case KEY_RIGHT: + attachment_category_index_ = (attachment_category_index_ + 1) % + static_cast(kAttachmentTypes.size()); + attachment_selection_index_ = 0; + return; + case KEY_UP: + if (attachment_selection_index_ > 0) { + --attachment_selection_index_; + } + return; + case KEY_DOWN: + ++attachment_selection_index_; + return; + default: + return; } } void App::handle_attachment_action_menu_key(int ch) { switch (ch) { - case 27: + case 27: + attachment_action_menu_open_ = false; + status_line_ = "Closed attachment actions."; + return; + case KEY_LEFT: + if (attachment_action_index_ > 0) { + --attachment_action_index_; + } + return; + case KEY_RIGHT: + if (attachment_action_index_ < 3) { + ++attachment_action_index_; + } + return; + case KEY_UP: + if (attachment_action_index_ > 0) { + --attachment_action_index_; + } + return; + case KEY_DOWN: + if (attachment_action_index_ < 3) { + ++attachment_action_index_; + } + return; + case '\n': + case KEY_ENTER: + case ' ': + if (attachment_action_index_ == 3) { attachment_action_menu_open_ = false; - status_line_ = "Closed attachment actions."; + status_line_ = "Cancelled attachment action."; return; - case KEY_LEFT: - if (attachment_action_index_ > 0) { - --attachment_action_index_; - } - return; - case KEY_RIGHT: - if (attachment_action_index_ < 3) { - ++attachment_action_index_; - } - return; - case KEY_UP: - if (attachment_action_index_ > 0) { - --attachment_action_index_; - } - return; - case KEY_DOWN: - if (attachment_action_index_ < 3) { - ++attachment_action_index_; - } - return; - case '\n': - case KEY_ENTER: - case ' ': - if (attachment_action_index_ == 3) { - attachment_action_menu_open_ = false; - status_line_ = "Cancelled attachment action."; - return; - } - if (const auto attachment = selected_attachment(); attachment.has_value()) { - attachment_action_menu_open_ = false; - if (attachment_action_index_ == 0) { - open_attachment(*attachment); - } else if (attachment_action_index_ == 1) { - download_attachment(*attachment); - } else { - delete_attachment(*attachment); - } + } + if (const auto attachment = selected_attachment(); attachment.has_value()) { + attachment_action_menu_open_ = false; + if (attachment_action_index_ == 0) { + open_attachment(*attachment); + } else if (attachment_action_index_ == 1) { + download_attachment(*attachment); } else { - attachment_action_menu_open_ = false; - status_line_ = "Attachment is no longer available."; + delete_attachment(*attachment); } - return; - default: - return; + } else { + attachment_action_menu_open_ = false; + status_line_ = "Attachment is no longer available."; + } + return; + default: + return; } } void App::handle_attachment_viewer_key(int ch) { if (attachment_viewer_send_on_enter_) { switch (ch) { - case '\n': - case KEY_ENTER: - if (!attachment_viewer_attachment_.has_value() || - attachment_viewer_attachment_->local_path.empty() || - attachment_viewer_send_chat_id_ == 0) { - attachment_viewer_open_ = false; - attachment_viewer_attachment_.reset(); - attachment_viewer_animation_frames_.clear(); - attachment_viewer_is_animated_ = false; - attachment_viewer_frame_index_ = 0; - reset_attachment_viewer_send_preview(); - clear_attachment_preview_graphics(); - status_line_ = "Clipboard image preview is no longer available."; - return; - } - { - const std::string local_path = attachment_viewer_attachment_->local_path; - const std::string caption = attachment_viewer_send_caption_; - const auto reply_to_message_id = attachment_viewer_send_reply_to_message_id_; - const std::int64_t chat_id = attachment_viewer_send_chat_id_; - attachment_viewer_open_ = false; - attachment_viewer_attachment_.reset(); - attachment_viewer_animation_frames_.clear(); - attachment_viewer_is_animated_ = false; - attachment_viewer_frame_index_ = 0; - reset_attachment_viewer_send_preview(); - clear_attachment_preview_graphics(); - send_photo_message(chat_id, local_path, caption, reply_to_message_id); - } + case '\n': + case KEY_ENTER: + if (!attachment_viewer_attachment_.has_value() || + attachment_viewer_attachment_->local_path.empty() || + attachment_viewer_send_chat_id_ == 0) { + attachment_viewer_open_ = false; + attachment_viewer_attachment_.reset(); + attachment_viewer_animation_frames_.clear(); + attachment_viewer_is_animated_ = false; + attachment_viewer_frame_index_ = 0; + reset_attachment_viewer_send_preview(); + clear_attachment_preview_graphics(); + status_line_ = "Clipboard image preview is no longer available."; return; - default: - break; + } + { + const std::string local_path = + attachment_viewer_attachment_->local_path; + const std::string caption = attachment_viewer_send_caption_; + const auto reply_to_message_id = + attachment_viewer_send_reply_to_message_id_; + const std::int64_t chat_id = attachment_viewer_send_chat_id_; + attachment_viewer_open_ = false; + attachment_viewer_attachment_.reset(); + attachment_viewer_animation_frames_.clear(); + attachment_viewer_is_animated_ = false; + attachment_viewer_frame_index_ = 0; + reset_attachment_viewer_send_preview(); + clear_attachment_preview_graphics(); + send_photo_message(chat_id, local_path, caption, + reply_to_message_id); + } + return; + default: + break; } } switch (ch) { - case 27: - case 'q': - case ' ': - { - const bool was_send_preview = attachment_viewer_send_on_enter_; - attachment_viewer_open_ = false; - attachment_viewer_attachment_.reset(); - attachment_viewer_animation_frames_.clear(); - attachment_viewer_is_animated_ = false; - attachment_viewer_frame_index_ = 0; - reset_attachment_viewer_send_preview(); - clear_attachment_preview_graphics(); - status_line_ = was_send_preview - ? "Cancelled clipboard image send." - : "Closed attachment preview."; - return; - } - case KEY_UP: - if (attachment_viewer_scroll_ > 0) { - --attachment_viewer_scroll_; - } - return; - case KEY_DOWN: - ++attachment_viewer_scroll_; - return; - case KEY_PPAGE: - attachment_viewer_scroll_ = std::max(0, attachment_viewer_scroll_ - 10); - return; - case KEY_NPAGE: - attachment_viewer_scroll_ += 10; - return; - default: - return; + case 27: + case 'q': + case ' ': { + const bool was_send_preview = attachment_viewer_send_on_enter_; + attachment_viewer_open_ = false; + attachment_viewer_attachment_.reset(); + attachment_viewer_animation_frames_.clear(); + attachment_viewer_is_animated_ = false; + attachment_viewer_frame_index_ = 0; + reset_attachment_viewer_send_preview(); + clear_attachment_preview_graphics(); + status_line_ = was_send_preview ? "Cancelled clipboard image send." + : "Closed attachment preview."; + return; + } + case KEY_UP: + if (attachment_viewer_scroll_ > 0) { + --attachment_viewer_scroll_; + } + return; + case KEY_DOWN: + ++attachment_viewer_scroll_; + return; + case KEY_PPAGE: + attachment_viewer_scroll_ = std::max(0, attachment_viewer_scroll_ - 10); + return; + case KEY_NPAGE: + attachment_viewer_scroll_ += 10; + return; + default: + return; } } @@ -1217,7 +1247,7 @@ void App::draw_attachments_menu(int height, int width) { const int top = std::max(1, (height - menu_height) / 2); const int left = std::max(1, (width - menu_width) / 2); - WINDOW* window = newwin(menu_height, menu_width, top, left); + WINDOW *window = newwin(menu_height, menu_width, top, left); if (window == nullptr) { return; } @@ -1231,8 +1261,9 @@ void App::draw_attachments_menu(int height, int width) { } const AttachmentType selected_type = kAttachmentTypes[attachment_category_index_]; - std::vector attachments; - for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend(); ++it) { + std::vector attachments; + for (auto it = chat_it->second.messages.rbegin(); it != chat_it->second.messages.rend(); + ++it) { if (it->has_attachment && it->attachment.type == selected_type) { attachments.push_back(&*it); } @@ -1269,7 +1300,8 @@ void App::draw_attachments_menu(int height, int width) { int date_width = std::min(16, std::max(8, content_width / 3)); int sender_width = std::max(8, content_width - status_width - size_width - date_width - 3); if (sender_width + status_width + size_width + date_width + 3 > content_width) { - date_width = std::max(8, content_width - sender_width - status_width - size_width - 3); + date_width = + std::max(8, content_width - sender_width - status_width - size_width - 3); } mvwaddnstr(window, 3, 2, "Sender", sender_width); @@ -1286,7 +1318,8 @@ void App::draw_attachments_menu(int height, int width) { } if (attachments.empty()) { - const std::string message = "No " + attachment_type_label(selected_type) + " in this chat."; + const std::string message = + "No " + attachment_type_label(selected_type) + " in this chat."; mvwaddnstr(window, list_top, 2, message.c_str(), menu_width - 4); } else { for (int row = 0; row < list_height; ++row) { @@ -1297,24 +1330,29 @@ void App::draw_attachments_menu(int height, int width) { continue; } - const MessageInfo& message = *attachments[static_cast(item_index)]; + const MessageInfo &message = + *attachments[static_cast(item_index)]; if (item_index == attachment_selection_index_) { wattron(window, A_REVERSE); } const std::string sender = truncate_text(message.sender, sender_width); const std::string dl_status = truncate_text( - message.attachment.is_downloading_active && !message.attachment.is_downloaded - ? attachment_progress_bar(message.attachment, std::max(10, status_width - 2)) - : (message.attachment.is_downloaded ? "Ready" : ""), - status_width); + message.attachment.is_downloading_active && + !message.attachment.is_downloaded + ? attachment_progress_bar(message.attachment, + std::max(10, status_width - 2)) + : (message.attachment.is_downloaded ? "Ready" : ""), + status_width); const std::string file_size = truncate_text( - format_file_size(message.attachment.size_bytes), - size_width); - const std::string sent = truncate_text(format_datetime(message.date), date_width); + format_file_size(message.attachment.size_bytes), size_width); + const std::string sent = + truncate_text(format_datetime(message.date), date_width); mvwaddnstr(window, y, 2, sender.c_str(), sender_width); mvwaddnstr(window, y, 3 + sender_width, dl_status.c_str(), status_width); - mvwaddnstr(window, y, 4 + sender_width + status_width, file_size.c_str(), size_width); - mvwaddnstr(window, y, 5 + sender_width + status_width + size_width, sent.c_str(), date_width); + mvwaddnstr(window, y, 4 + sender_width + status_width, file_size.c_str(), + size_width); + mvwaddnstr(window, y, 5 + sender_width + status_width + size_width, + sent.c_str(), date_width); if (item_index == attachment_selection_index_) { wattroff(window, A_REVERSE); } @@ -1325,7 +1363,8 @@ void App::draw_attachments_menu(int height, int width) { std::string title_line; std::string actions_line = "Left/Right type Up/Down move Space actions Esc close"; if (!attachments.empty()) { - const MessageInfo& selected = *attachments[static_cast(attachment_selection_index_)]; + const MessageInfo &selected = + *attachments[static_cast(attachment_selection_index_)]; title_line = truncate_text(selected.attachment.name, menu_width - 4); } else { title_line = truncate_text("No attachment selected.", menu_width - 4); @@ -1350,48 +1389,53 @@ void App::draw_attachment_action_menu(int height, int width) { const int top = std::max(1, (height - menu_height) / 2); const int left = std::max(1, (width - menu_width) / 2); - WINDOW* window = newwin(menu_height, menu_width, top, left); + WINDOW *window = newwin(menu_height, menu_width, top, left); if (window == nullptr) { return; } box(window, 0, 0); mvwprintw(window, 0, 2, " Attachment Action "); - mvwaddnstr(window, 1, 2, truncate_text(attachment->name, menu_width - 4).c_str(), menu_width - 4); + mvwaddnstr(window, 1, 2, truncate_text(attachment->name, menu_width - 4).c_str(), + menu_width - 4); mvwhline(window, 3, 1, ACS_HLINE, menu_width - 2); - const std::vector prompt_lines = wrap_text( - "What do you want to do with this file?", - std::max(10, menu_width - 8)); - for (std::size_t i = 0; i < prompt_lines.size() && static_cast(i) < menu_height - 9; ++i) { - mvwaddnstr(window, 5 + static_cast(i), 4, prompt_lines[i].c_str(), menu_width - 8); + const std::vector prompt_lines = + wrap_text("What do you want to do with this file?", std::max(10, menu_width - 8)); + for (std::size_t i = 0; i < prompt_lines.size() && static_cast(i) < menu_height - 9; + ++i) { + mvwaddnstr(window, 5 + static_cast(i), 4, prompt_lines[i].c_str(), + menu_width - 8); } mvwhline(window, menu_height - 5, 1, ACS_HLINE, menu_width - 2); - const std::array actions = {"Open", "Download", "Delete", "Cancel"}; + const std::array actions = {"Open", "Download", "Delete", "Cancel"}; std::vector labels; int total_label_width = 0; - for (const char* action : actions) { + for (const char *action : actions) { labels.push_back(std::string("[ ") + action + " ]"); total_label_width += static_cast(labels.back().size()); } const int button_y = menu_height - 3; const int inner_width = menu_width - 2; - const int gap = std::max(2, (inner_width - total_label_width) / (static_cast(labels.size()) + 1)); + const int gap = std::max(2, (inner_width - total_label_width) / + (static_cast(labels.size()) + 1)); int button_x = 1 + gap; for (std::size_t i = 0; i < actions.size(); ++i) { if (static_cast(i) == attachment_action_index_) { wattron(window, A_REVERSE | A_BOLD); } - mvwaddnstr(window, button_y, button_x, labels[i].c_str(), menu_width - button_x - 2); + mvwaddnstr(window, button_y, button_x, labels[i].c_str(), + menu_width - button_x - 2); if (static_cast(i) == attachment_action_index_) { wattroff(window, A_REVERSE | A_BOLD); } button_x += static_cast(labels[i].size()) + gap; } - mvwaddnstr(window, menu_height - 2, 2, "Left/Right move Enter select Esc close", menu_width - 4); + mvwaddnstr(window, menu_height - 2, 2, "Left/Right move Enter select Esc close", + menu_width - 4); wrefresh(window); delwin(window); } @@ -1402,7 +1446,7 @@ void App::draw_attachment_viewer(int height, int width) { const int top = std::max(1, (height - menu_height) / 2); const int left = std::max(1, (width - menu_width) / 2); - WINDOW* window = newwin(menu_height, menu_width, top, left); + WINDOW *window = newwin(menu_height, menu_width, top, left); if (window == nullptr) { return; } @@ -1414,7 +1458,8 @@ void App::draw_attachment_viewer(int height, int width) { const int content_top = 3; const int content_height = std::max(1, menu_height - 5); - const int max_scroll = std::max(0, static_cast(attachment_viewer_lines_.size()) - content_height); + const int max_scroll = + std::max(0, static_cast(attachment_viewer_lines_.size()) - content_height); if (attachment_viewer_scroll_ > max_scroll) { attachment_viewer_scroll_ = max_scroll; } @@ -1425,25 +1470,23 @@ void App::draw_attachment_viewer(int height, int width) { if (line_index >= static_cast(attachment_viewer_lines_.size())) { continue; } - mvwaddnstr( - window, - content_top + row, - 2, - attachment_viewer_lines_[static_cast(line_index)].c_str(), - menu_width - 4); + mvwaddnstr(window, content_top + row, 2, + attachment_viewer_lines_[static_cast(line_index)].c_str(), + menu_width - 4); } const std::string controls = attachment_viewer_send_on_enter_ - ? "Enter send Esc cancel Up/Down scroll" - : "Space/Esc close Up/Down scroll"; + ? "Enter send Esc cancel Up/Down scroll" + : "Space/Esc close Up/Down scroll"; mvwaddnstr(window, menu_height - 2, 2, controls.c_str(), menu_width - 4); wrefresh(window); if (attachment_viewer_attachment_.has_value()) { - render_attachment_preview_graphics(top + content_top, left + 2, menu_width - 4, content_height); + render_attachment_preview_graphics(top + content_top, left + 2, menu_width - 4, + content_height); } else { clear_attachment_preview_graphics(); } delwin(window); } -} // namespace telegram_tui +} // namespace telegram_tui diff --git a/src/app_state.cpp b/src/app_state.cpp index 4acbea2..57cb0e3 100644 --- a/src/app_state.cpp +++ b/src/app_state.cpp @@ -9,19 +9,20 @@ namespace telegram_tui { namespace { -std::string format_download_progress(std::int64_t downloaded_size, std::int64_t size_bytes, bool is_downloaded) { +std::string format_download_progress(std::int64_t downloaded_size, std::int64_t size_bytes, + bool is_downloaded) { if (is_downloaded) { return "100%"; } if (size_bytes > 0) { const auto downloaded = std::min(downloaded_size, size_bytes); return std::to_string(static_cast((downloaded * 100) / size_bytes)) + "% " + - format_file_size(downloaded) + "/" + format_file_size(size_bytes); + format_file_size(downloaded) + "/" + format_file_size(size_bytes); } return format_file_size(downloaded_size); } -} // namespace +} // namespace bool App::process_updates() { bool changed = false; @@ -36,7 +37,7 @@ bool App::process_updates() { return changed; } -void App::handle_td_object(const json& object) { +void App::handle_td_object(const json &object) { const std::string type = safe_string(object, "@type"); if (type == "updateAuthorizationState") { authorization_state_ = object.value("authorization_state", json::object()); @@ -48,7 +49,7 @@ void App::handle_td_object(const json& object) { return; } if (type == "updateChatOnlineMemberCount") { - ChatInfo& chat = chats_[safe_i64(object, "chat_id")]; + ChatInfo &chat = chats_[safe_i64(object, "chat_id")]; chat.id = safe_i64(object, "chat_id"); chat.online_member_count = safe_i32(object, "online_member_count"); chat.has_online_member_count = true; @@ -59,15 +60,19 @@ void App::handle_td_object(const json& object) { return; } if (type == "updateUserStatus") { - update_user_status(safe_i64(object, "user_id"), object.value("status", json::object())); + update_user_status(safe_i64(object, "user_id"), + object.value("status", json::object())); return; } if (type == "updateBasicGroup" || type == "basicGroup") { - upsert_basic_group(type == "basicGroup" ? object : object.value("basic_group", json::object())); + upsert_basic_group(type == "basicGroup" + ? object + : object.value("basic_group", json::object())); return; } if (type == "updateSupergroup" || type == "supergroup") { - upsert_supergroup(type == "supergroup" ? object : object.value("supergroup", json::object())); + upsert_supergroup( + type == "supergroup" ? object : object.value("supergroup", json::object())); return; } if (type == "updateNewChat") { @@ -75,27 +80,28 @@ void App::handle_td_object(const json& object) { return; } if (type == "updateChatTitle") { - ChatInfo& chat = chats_[safe_i64(object, "chat_id")]; + ChatInfo &chat = chats_[safe_i64(object, "chat_id")]; chat.id = safe_i64(object, "chat_id"); chat.title = safe_string(object, "title"); resort_chats(); return; } if (type == "updateChatPosition") { - ChatInfo& chat = chats_[safe_i64(object, "chat_id")]; + ChatInfo &chat = chats_[safe_i64(object, "chat_id")]; chat.id = safe_i64(object, "chat_id"); apply_chat_position(chat, object.value("position", json::object())); resort_chats(); return; } if (type == "updateChatLastMessage") { - ChatInfo& chat = chats_[safe_i64(object, "chat_id")]; + ChatInfo &chat = chats_[safe_i64(object, "chat_id")]; chat.id = safe_i64(object, "chat_id"); - chat.last_message_preview = preview_message(object.value("last_message", json::object())); + chat.last_message_preview = + preview_message(object.value("last_message", json::object())); if (object.contains("positions") && object.at("positions").is_array()) { chat.in_main_list = false; chat.main_order = 0; - for (const auto& position : object.at("positions")) { + for (const auto &position : object.at("positions")) { apply_chat_position(chat, position); } } @@ -103,14 +109,14 @@ void App::handle_td_object(const json& object) { return; } if (type == "updateChatReadInbox") { - ChatInfo& chat = chats_[safe_i64(object, "chat_id")]; + ChatInfo &chat = chats_[safe_i64(object, "chat_id")]; chat.id = safe_i64(object, "chat_id"); chat.unread_count = safe_i32(object, "unread_count"); chat.last_read_inbox_message_id = safe_i64(object, "last_read_inbox_message_id"); return; } if (type == "updateChatReadOutbox") { - ChatInfo& chat = chats_[safe_i64(object, "chat_id")]; + ChatInfo &chat = chats_[safe_i64(object, "chat_id")]; chat.id = safe_i64(object, "chat_id"); chat.last_read_outbox_message_id = safe_i64(object, "last_read_outbox_message_id"); return; @@ -136,11 +142,12 @@ void App::handle_td_object(const json& object) { if (chat_it == chats_.end()) { return; } - for (auto& message : chat_it->second.messages) { + for (auto &message : chat_it->second.messages) { if (message.id == message_id) { const json content = object.value("new_content", json::object()); message.text = content_to_text(content, true); - if (const auto attachment = parse_attachment(content); attachment.has_value()) { + if (const auto attachment = parse_attachment(content); + attachment.has_value()) { message.has_attachment = true; message.attachment = *attachment; } else { @@ -158,14 +165,14 @@ void App::handle_td_object(const json& object) { return; } std::set deleted; - for (const auto& id : object.value("message_ids", json::array())) { + for (const auto &id : object.value("message_ids", json::array())) { if (id.is_number_integer()) { deleted.insert(id.get()); } } std::vector kept; kept.reserve(chat_it->second.messages.size()); - for (const auto& message : chat_it->second.messages) { + for (const auto &message : chat_it->second.messages) { if (deleted.find(message.id) == deleted.end()) { kept.push_back(message); } @@ -178,8 +185,8 @@ void App::handle_td_object(const json& object) { return; } if (type == "updateOption" || type == "ok" || type == "userFullInfo" || - type == "updateHavePendingNotifications" || type == "updateUnreadMessageCount" || - type == "updateUnreadChatCount") { + type == "updateHavePendingNotifications" || type == "updateUnreadMessageCount" || + type == "updateUnreadChatCount") { return; } if (type == "error") { @@ -235,29 +242,29 @@ void App::handle_td_object(const json& object) { void App::request_more_chats() { td_.send({ - {"@type", "getChats"}, - {"chat_list", {{"@type", "chatListMain"}}}, - {"limit", kChatPageSize}, - {"@extra", "main_chats"}, + {"@type", "getChats"}, + {"chat_list", {{"@type", "chatListMain"}}}, + {"limit", kChatPageSize}, + {"@extra", "main_chats"}, }); status_line_ = "Loading chats..."; } bool App::request_chat_history(std::int64_t chat_id, bool force) { - ChatInfo& chat = chats_[chat_id]; + ChatInfo &chat = chats_[chat_id]; chat.id = chat_id; if (chat.history_loading && !force) { return false; } chat.history_loading = true; td_.send({ - {"@type", "getChatHistory"}, - {"chat_id", chat_id}, - {"from_message_id", 0}, - {"offset", 0}, - {"limit", kHistoryBatchSize}, - {"only_local", false}, - {"@extra", "history:" + std::to_string(chat_id)}, + {"@type", "getChatHistory"}, + {"chat_id", chat_id}, + {"from_message_id", 0}, + {"offset", 0}, + {"limit", kHistoryBatchSize}, + {"only_local", false}, + {"@extra", "history:" + std::to_string(chat_id)}, }); return true; } @@ -283,7 +290,7 @@ void App::mark_chat_messages_as_read(std::int64_t chat_id) { std::vector unread_message_ids; unread_message_ids.reserve(chat_it->second.messages.size()); - for (const auto& message : chat_it->second.messages) { + for (const auto &message : chat_it->second.messages) { if (message.is_outgoing || message.id == 0) { continue; } @@ -295,18 +302,18 @@ void App::mark_chat_messages_as_read(std::int64_t chat_id) { if (!unread_message_ids.empty()) { td_.send({ - {"@type", "viewMessages"}, - {"chat_id", chat_id}, - {"message_ids", unread_message_ids}, - {"source", {{"@type", "messageSourceChatHistory"}}}, - {"force_read", true}, + {"@type", "viewMessages"}, + {"chat_id", chat_id}, + {"message_ids", unread_message_ids}, + {"source", {{"@type", "messageSourceChatHistory"}}}, + {"force_read", true}, }); } td_.send({ - {"@type", "toggleChatIsMarkedAsUnread"}, - {"chat_id", chat_id}, - {"is_marked_as_unread", false}, + {"@type", "toggleChatIsMarkedAsUnread"}, + {"chat_id", chat_id}, + {"is_marked_as_unread", false}, }); } @@ -316,11 +323,11 @@ void App::mark_message_as_read(std::int64_t chat_id, std::int64_t message_id) { } td_.send({ - {"@type", "viewMessages"}, - {"chat_id", chat_id}, - {"message_ids", json::array({message_id})}, - {"source", {{"@type", "messageSourceChatHistory"}}}, - {"force_read", true}, + {"@type", "viewMessages"}, + {"chat_id", chat_id}, + {"message_ids", json::array({message_id})}, + {"source", {{"@type", "messageSourceChatHistory"}}}, + {"force_read", true}, }); } @@ -330,12 +337,12 @@ void App::set_open_chat(std::int64_t chat_id) { } const bool changed_chat = open_chat_id_ != chat_id; - ChatInfo& chat = chats_[chat_id]; + ChatInfo &chat = chats_[chat_id]; chat.id = chat_id; if (tdlib_open_chat_id_ != 0 && tdlib_open_chat_id_ != chat_id) { td_.send({ - {"@type", "closeChat"}, - {"chat_id", tdlib_open_chat_id_}, + {"@type", "closeChat"}, + {"chat_id", tdlib_open_chat_id_}, }); } @@ -347,8 +354,8 @@ void App::set_open_chat(std::int64_t chat_id) { if (tdlib_open_chat_id_ != chat_id) { td_.send({ - {"@type", "openChat"}, - {"chat_id", chat_id}, + {"@type", "openChat"}, + {"chat_id", chat_id}, }); tdlib_open_chat_id_ = chat_id; } @@ -358,25 +365,25 @@ void App::set_open_chat(std::int64_t chat_id) { if (changed_chat) { request_chat_details(chat_id); const bool should_request_history = - auto_reload_chat_history_ || !chat.history_loaded || chat.messages.empty(); + auto_reload_chat_history_ || !chat.history_loaded || chat.messages.empty(); const bool requested = should_request_history && - request_chat_history(chat_id, auto_reload_chat_history_); + request_chat_history(chat_id, auto_reload_chat_history_); if (requested) { status_line_ = auto_reload_chat_history_ && chat.history_loaded - ? "Reloading chat history..." - : "Loading chat history..."; + ? "Reloading chat history..." + : "Loading chat history..."; } } } -void App::sync_chat_ids_from_response(const json& response) { +void App::sync_chat_ids_from_response(const json &response) { if (!response.contains("chat_ids") || !response.at("chat_ids").is_array()) { status_line_ = "Chat list response missing chat ids."; return; } std::vector chat_ids; - for (const auto& entry : response.at("chat_ids")) { + for (const auto &entry : response.at("chat_ids")) { if (!entry.is_number_integer()) { continue; } @@ -384,8 +391,8 @@ void App::sync_chat_ids_from_response(const json& response) { chat_ids.push_back(chat_id); if (chats_.find(chat_id) == chats_.end()) { td_.send({ - {"@type", "getChat"}, - {"chat_id", chat_id}, + {"@type", "getChat"}, + {"chat_id", chat_id}, }); } } @@ -393,7 +400,8 @@ void App::sync_chat_ids_from_response(const json& response) { if (!chat_ids.empty()) { sorted_chat_ids_ = std::move(chat_ids); if (selected_chat_index_ >= static_cast(sorted_chat_ids_.size())) { - selected_chat_index_ = std::max(0, static_cast(sorted_chat_ids_.size()) - 1); + selected_chat_index_ = + std::max(0, static_cast(sorted_chat_ids_.size()) - 1); } if (!open_chat_id().has_value()) { const auto chat_id = highlighted_chat_id(); @@ -410,10 +418,11 @@ void App::sync_chat_ids_from_response(const json& response) { } void App::append_message(std::int64_t chat_id, MessageInfo message) { - ChatInfo& chat = chats_[chat_id]; + ChatInfo &chat = chats_[chat_id]; chat.id = chat_id; - auto existing = std::find_if(chat.messages.begin(), chat.messages.end(), - [&](const MessageInfo& item) { return item.id == message.id; }); + auto existing = + std::find_if(chat.messages.begin(), chat.messages.end(), + [&](const MessageInfo &item) { return item.id == message.id; }); if (existing != chat.messages.end()) { *existing = std::move(message); } else { @@ -421,18 +430,18 @@ void App::append_message(std::int64_t chat_id, MessageInfo message) { } std::sort(chat.messages.begin(), chat.messages.end(), - [](const MessageInfo& lhs, const MessageInfo& rhs) { - if (lhs.date != rhs.date) { - return lhs.date < rhs.date; - } - return lhs.id < rhs.id; - }); + [](const MessageInfo &lhs, const MessageInfo &rhs) { + if (lhs.date != rhs.date) { + return lhs.date < rhs.date; + } + return lhs.id < rhs.id; + }); if (kMaxMessagesPerChat > 0 && chat.messages.size() > kMaxMessagesPerChat) { - chat.messages.erase( - chat.messages.begin(), - chat.messages.begin() + - static_cast(chat.messages.size() - kMaxMessagesPerChat)); + chat.messages.erase(chat.messages.begin(), + chat.messages.begin() + + static_cast(chat.messages.size() - + kMaxMessagesPerChat)); } } @@ -446,20 +455,18 @@ void App::remove_message(std::int64_t chat_id, std::int64_t message_id) { return; } - auto& messages = chat_it->second.messages; + auto &messages = chat_it->second.messages; messages.erase( - std::remove_if( - messages.begin(), - messages.end(), - [&](const MessageInfo& item) { return item.id == message_id; }), - messages.end()); + std::remove_if(messages.begin(), messages.end(), + [&](const MessageInfo &item) { return item.id == message_id; }), + messages.end()); } -void App::merge_history(std::int64_t chat_id, const json& messages) { - ChatInfo& chat = chats_[chat_id]; +void App::merge_history(std::int64_t chat_id, const json &messages) { + ChatInfo &chat = chats_[chat_id]; chat.history_loading = false; chat.history_loaded = true; - for (const auto& message : messages) { + for (const auto &message : messages) { append_message(chat_id, parse_message(message)); } if (chat_id == open_chat_id_) { @@ -468,13 +475,14 @@ void App::merge_history(std::int64_t chat_id, const json& messages) { status_line_ = "History loaded."; } -void App::update_attachment_file(const json& file) { +void App::update_attachment_file(const json &file) { const std::int32_t file_id = safe_i32(file, "id"); if (file_id == 0) { return; } - const std::int64_t size_bytes = std::max(safe_i64(file, "size"), safe_i64(file, "expected_size")); + const std::int64_t size_bytes = + std::max(safe_i64(file, "size"), safe_i64(file, "expected_size")); const json local = file.value("local", json::object()); const std::string local_path = safe_string(local, "path"); const std::int64_t downloaded_size = safe_i64(local, "downloaded_size"); @@ -483,9 +491,9 @@ void App::update_attachment_file(const json& file) { const bool can_be_downloaded = local.value("can_be_downloaded", false); const bool can_be_deleted = local.value("can_be_deleted", false); - for (auto& [chat_id, chat] : chats_) { - (void) chat_id; - for (auto& message : chat.messages) { + for (auto &[chat_id, chat] : chats_) { + (void)chat_id; + for (auto &message : chat.messages) { if (!message.has_attachment || message.attachment.file_id != file_id) { continue; } @@ -502,7 +510,8 @@ void App::update_attachment_file(const json& file) { } if (pending_attachment_open_.has_value() && pending_attachment_open_->file_id == file_id) { - pending_attachment_open_->size_bytes = size_bytes > 0 ? size_bytes : pending_attachment_open_->size_bytes; + pending_attachment_open_->size_bytes = + size_bytes > 0 ? size_bytes : pending_attachment_open_->size_bytes; pending_attachment_open_->downloaded_size = downloaded_size; pending_attachment_open_->local_path = local_path; pending_attachment_open_->is_downloading_active = is_downloading_active; @@ -510,11 +519,11 @@ void App::update_attachment_file(const json& file) { pending_attachment_open_->can_be_downloaded = can_be_downloaded; pending_attachment_open_->can_be_deleted = can_be_deleted; if (!is_downloaded && is_downloading_active) { - status_line_ = "Downloading attachment to open " + - format_download_progress( - pending_attachment_open_->downloaded_size, - pending_attachment_open_->size_bytes, - pending_attachment_open_->is_downloaded); + status_line_ = + "Downloading attachment to open " + + format_download_progress(pending_attachment_open_->downloaded_size, + pending_attachment_open_->size_bytes, + pending_attachment_open_->is_downloaded); } if (is_downloaded && !local_path.empty()) { open_attachment(*pending_attachment_open_); @@ -522,9 +531,10 @@ void App::update_attachment_file(const json& file) { } } - if (pending_attachment_download_.has_value() && pending_attachment_download_->file_id == file_id) { + if (pending_attachment_download_.has_value() && + pending_attachment_download_->file_id == file_id) { pending_attachment_download_->size_bytes = - size_bytes > 0 ? size_bytes : pending_attachment_download_->size_bytes; + size_bytes > 0 ? size_bytes : pending_attachment_download_->size_bytes; pending_attachment_download_->downloaded_size = downloaded_size; pending_attachment_download_->local_path = local_path; pending_attachment_download_->is_downloading_active = is_downloading_active; @@ -533,10 +543,10 @@ void App::update_attachment_file(const json& file) { pending_attachment_download_->can_be_deleted = can_be_deleted; if (!is_downloaded && is_downloading_active) { status_line_ = "Downloading attachment to ~/Downloads " + - format_download_progress( - pending_attachment_download_->downloaded_size, - pending_attachment_download_->size_bytes, - pending_attachment_download_->is_downloaded); + format_download_progress( + pending_attachment_download_->downloaded_size, + pending_attachment_download_->size_bytes, + pending_attachment_download_->is_downloaded); } if (is_downloaded && !local_path.empty()) { export_attachment_to_downloads(*pending_attachment_download_); @@ -545,4 +555,4 @@ void App::update_attachment_file(const json& file) { } } -} // namespace telegram_tui +} // namespace telegram_tui diff --git a/src/json.h b/src/json.h index 54a5723..b099d9e 100644 --- a/src/json.h +++ b/src/json.h @@ -6,4 +6,4 @@ namespace telegram_tui { using json = nlohmann::json; -} // namespace telegram_tui +} // namespace telegram_tui diff --git a/src/main.cpp b/src/main.cpp index 767b04b..56125f0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -9,7 +9,7 @@ int main() { try { telegram_tui::App app; return app.run(); - } catch (const std::exception& error) { + } catch (const std::exception &error) { endwin(); std::fprintf(stderr, "fatal: %s\n", error.what()); return 1; diff --git a/src/models.cpp b/src/models.cpp index 94d34d6..968033c 100644 --- a/src/models.cpp +++ b/src/models.cpp @@ -22,4 +22,4 @@ std::string UserInfo::display_name() const { return "Unknown"; } -} // namespace telegram_tui +} // namespace telegram_tui diff --git a/src/models.h b/src/models.h index 2b4b64d..73f5b83 100644 --- a/src/models.h +++ b/src/models.h @@ -98,4 +98,4 @@ enum class InputMode { LastName, }; -} // namespace telegram_tui +} // namespace telegram_tui diff --git a/src/td_client.cpp b/src/td_client.cpp index 4a0736e..b074c8d 100644 --- a/src/td_client.cpp +++ b/src/td_client.cpp @@ -9,16 +9,16 @@ namespace telegram_tui { namespace { void configure_tdlib_logging() { - const char* stream_request = - R"({"@type":"setLogStream","log_stream":{"@type":"logStreamEmpty"}})"; - const char* verbosity_request = - R"({"@type":"setLogVerbosityLevel","new_verbosity_level":0})"; + const char *stream_request = + R"({"@type":"setLogStream","log_stream":{"@type":"logStreamEmpty"}})"; + const char *verbosity_request = + R"({"@type":"setLogVerbosityLevel","new_verbosity_level":0})"; td_json_client_execute(nullptr, stream_request); td_json_client_execute(nullptr, verbosity_request); } -} // namespace +} // namespace TdClient::TdClient() { configure_tdlib_logging(); @@ -31,14 +31,14 @@ TdClient::~TdClient() { } } -void TdClient::send(const json& request) { +void TdClient::send(const json &request) { const std::string payload = request.dump(); td_json_client_send(client_, payload.c_str()); } -std::optional TdClient::execute(const json& request) { +std::optional TdClient::execute(const json &request) { const std::string payload = request.dump(); - const char* raw = td_json_client_execute(client_, payload.c_str()); + const char *raw = td_json_client_execute(client_, payload.c_str()); if (raw == nullptr) { return std::nullopt; } @@ -51,7 +51,7 @@ std::optional TdClient::execute(const json& request) { } std::optional TdClient::receive(double timeout_seconds) { - const char* raw = td_json_client_receive(client_, timeout_seconds); + const char *raw = td_json_client_receive(client_, timeout_seconds); if (raw == nullptr) { return std::nullopt; } @@ -63,4 +63,4 @@ std::optional TdClient::receive(double timeout_seconds) { } } -} // namespace telegram_tui +} // namespace telegram_tui diff --git a/src/td_client.h b/src/td_client.h index 05e5260..9d5437d 100644 --- a/src/td_client.h +++ b/src/td_client.h @@ -7,19 +7,19 @@ namespace telegram_tui { class TdClient { - public: + public: TdClient(); ~TdClient(); - TdClient(const TdClient&) = delete; - TdClient& operator=(const TdClient&) = delete; + TdClient(const TdClient &) = delete; + TdClient &operator=(const TdClient &) = delete; - void send(const json& request); - [[nodiscard]] std::optional execute(const json& request); + void send(const json &request); + [[nodiscard]] std::optional execute(const json &request); [[nodiscard]] std::optional receive(double timeout_seconds); - private: - void* client_ = nullptr; + private: + void *client_ = nullptr; }; -} // namespace telegram_tui +} // namespace telegram_tui diff --git a/src/util.cpp b/src/util.cpp index 4592c0c..bd0fc6c 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -33,7 +33,8 @@ bool is_utf8_continuation(unsigned char ch) { return (ch & 0xC0U) == 0x80U; } -std::uint32_t decode_utf8_codepoint(const std::string& text, std::size_t offset, std::size_t* size_out = nullptr) { +std::uint32_t decode_utf8_codepoint(const std::string &text, std::size_t offset, + std::size_t *size_out = nullptr) { if (offset >= text.size()) { if (size_out != nullptr) { *size_out = 0; @@ -62,23 +63,23 @@ std::uint32_t decode_utf8_codepoint(const std::string& text, std::size_t offset, } if (size == 2) { return ((lead & 0x1FU) << 6) | - (static_cast(text[offset + 1]) & 0x3FU); + (static_cast(text[offset + 1]) & 0x3FU); } if (size == 3) { return ((lead & 0x0FU) << 12) | - ((static_cast(text[offset + 1]) & 0x3FU) << 6) | - (static_cast(text[offset + 2]) & 0x3FU); + ((static_cast(text[offset + 1]) & 0x3FU) << 6) | + (static_cast(text[offset + 2]) & 0x3FU); } return ((lead & 0x07U) << 18) | - ((static_cast(text[offset + 1]) & 0x3FU) << 12) | - ((static_cast(text[offset + 2]) & 0x3FU) << 6) | - (static_cast(text[offset + 3]) & 0x3FU); + ((static_cast(text[offset + 1]) & 0x3FU) << 12) | + ((static_cast(text[offset + 2]) & 0x3FU) << 6) | + (static_cast(text[offset + 3]) & 0x3FU); } -} // namespace +} // namespace -std::string get_env(const char* name) { - const char* value = std::getenv(name); +std::string get_env(const char *name) { + const char *value = std::getenv(name); return value == nullptr ? std::string() : std::string(value); } @@ -94,7 +95,7 @@ std::string trim_copy(std::string value) { } std::string single_line(std::string text) { - for (char& c : text) { + for (char &c : text) { if (c == '\n' || c == '\r' || c == '\t') { c = ' '; } @@ -102,16 +103,16 @@ std::string single_line(std::string text) { return trim_copy(std::move(text)); } -bool is_decimal_number(const std::string& value) { - return !value.empty() && - std::all_of(value.begin(), value.end(), [](unsigned char ch) { return std::isdigit(ch); }); +bool is_decimal_number(const std::string &value) { + return !value.empty() && std::all_of(value.begin(), value.end(), + [](unsigned char ch) { return std::isdigit(ch); }); } std::filesystem::path data_root() { - if (const char* xdg = std::getenv("XDG_DATA_HOME"); xdg != nullptr && *xdg != '\0') { + if (const char *xdg = std::getenv("XDG_DATA_HOME"); xdg != nullptr && *xdg != '\0') { return std::filesystem::path(xdg) / "telegram-tui"; } - if (const char* home = std::getenv("HOME"); home != nullptr && *home != '\0') { + if (const char *home = std::getenv("HOME"); home != nullptr && *home != '\0') { return std::filesystem::path(home) / ".local" / "share" / "telegram-tui"; } return std::filesystem::current_path() / ".telegram-tui-data"; @@ -130,16 +131,16 @@ StoredConfig load_app_config() { return {}; } return StoredConfig{ - safe_string(config, "api_id"), - safe_string(config, "api_hash"), - config.value("auto_reload_chat_history", false), + safe_string(config, "api_id"), + safe_string(config, "api_hash"), + config.value("auto_reload_chat_history", false), }; - } catch (const json::exception&) { + } catch (const json::exception &) { return {}; } } -bool save_app_config(const StoredConfig& config) { +bool save_app_config(const StoredConfig &config) { const std::filesystem::path path = data_root() / "config.json"; try { if (path.has_parent_path()) { @@ -161,26 +162,26 @@ bool save_app_config(const StoredConfig& config) { } output << document.dump(2) << '\n'; return static_cast(output); - } catch (const std::exception&) { + } catch (const std::exception &) { return false; } } -std::string safe_string(const json& object, const char* key) { +std::string safe_string(const json &object, const char *key) { if (!object.contains(key) || !object.at(key).is_string()) { return {}; } return object.at(key).get(); } -std::int64_t safe_i64(const json& object, const char* key) { +std::int64_t safe_i64(const json &object, const char *key) { if (!object.contains(key) || !object.at(key).is_number_integer()) { return 0; } return object.at(key).get(); } -std::int32_t safe_i32(const json& object, const char* key) { +std::int32_t safe_i32(const json &object, const char *key) { if (!object.contains(key) || !object.at(key).is_number_integer()) { return 0; } @@ -228,7 +229,7 @@ std::string format_file_size(std::int64_t size_bytes) { return "?"; } - static constexpr const char* units[] = {"B", "KB", "MB", "GB", "TB"}; + static constexpr const char *units[] = {"B", "KB", "MB", "GB", "TB"}; double size = static_cast(size_bytes); std::size_t unit_index = 0; while (size >= 1024.0 && unit_index + 1 < std::size(units)) { @@ -243,7 +244,7 @@ std::string format_file_size(std::int64_t size_bytes) { return stream.str(); } -std::vector wrap_text(const std::string& text, int width) { +std::vector wrap_text(const std::string &text, int width) { if (width <= 1) { return {text}; } @@ -286,7 +287,7 @@ std::vector wrap_text(const std::string& text, int width) { return lines; } -std::size_t utf8_byte_index_from_utf16_offset(const std::string& text, std::size_t utf16_offset) { +std::size_t utf8_byte_index_from_utf16_offset(const std::string &text, std::size_t utf16_offset) { std::size_t byte_index = 0; std::size_t utf16_units = 0; while (byte_index < text.size() && utf16_units < utf16_offset) { @@ -305,7 +306,7 @@ std::size_t utf8_byte_index_from_utf16_offset(const std::string& text, std::size return byte_index; } -std::size_t utf8_prev_index(const std::string& text, std::size_t byte_index) { +std::size_t utf8_prev_index(const std::string &text, std::size_t byte_index) { if (byte_index == 0 || text.empty()) { return 0; } @@ -317,7 +318,7 @@ std::size_t utf8_prev_index(const std::string& text, std::size_t byte_index) { return index; } -std::size_t utf8_next_index(const std::string& text, std::size_t byte_index) { +std::size_t utf8_next_index(const std::string &text, std::size_t byte_index) { if (byte_index >= text.size()) { return text.size(); } @@ -327,7 +328,7 @@ std::size_t utf8_next_index(const std::string& text, std::size_t byte_index) { return std::min(text.size(), byte_index + std::max(1, size)); } -int utf8_display_width(const std::string& text, std::size_t byte_limit) { +int utf8_display_width(const std::string &text, std::size_t byte_limit) { const std::size_t limit = std::min(byte_limit, text.size()); int width = 0; std::size_t index = 0; @@ -351,7 +352,7 @@ int utf8_display_width(const std::string& text, std::size_t byte_limit) { return width; } -void pop_utf8_back(std::string& text) { +void pop_utf8_back(std::string &text) { if (text.empty()) { return; } @@ -363,4 +364,4 @@ void pop_utf8_back(std::string& text) { text.erase(start); } -} // namespace telegram_tui +} // namespace telegram_tui diff --git a/src/util.h b/src/util.h index 2c9a806..34c7111 100644 --- a/src/util.h +++ b/src/util.h @@ -15,25 +15,27 @@ struct StoredConfig { bool auto_reload_chat_history = false; }; -[[nodiscard]] std::string get_env(const char* name); +[[nodiscard]] std::string get_env(const char *name); [[nodiscard]] std::string trim_copy(std::string value); [[nodiscard]] std::string single_line(std::string text); -[[nodiscard]] bool is_decimal_number(const std::string& value); +[[nodiscard]] bool is_decimal_number(const std::string &value); [[nodiscard]] std::filesystem::path data_root(); [[nodiscard]] StoredConfig load_app_config(); -bool save_app_config(const StoredConfig& config); -[[nodiscard]] std::string safe_string(const json& object, const char* key); -[[nodiscard]] std::int64_t safe_i64(const json& object, const char* key); -[[nodiscard]] std::int32_t safe_i32(const json& object, const char* key); +bool save_app_config(const StoredConfig &config); +[[nodiscard]] std::string safe_string(const json &object, const char *key); +[[nodiscard]] std::int64_t safe_i64(const json &object, const char *key); +[[nodiscard]] std::int32_t safe_i32(const json &object, const char *key); [[nodiscard]] std::string format_time(std::int32_t unix_time); [[nodiscard]] std::string format_date(std::int32_t unix_time); [[nodiscard]] std::string format_datetime(std::int32_t unix_time); [[nodiscard]] std::string format_file_size(std::int64_t size_bytes); -[[nodiscard]] std::vector wrap_text(const std::string& text, int width); -[[nodiscard]] std::size_t utf8_byte_index_from_utf16_offset(const std::string& text, std::size_t utf16_offset); -[[nodiscard]] std::size_t utf8_prev_index(const std::string& text, std::size_t byte_index); -[[nodiscard]] std::size_t utf8_next_index(const std::string& text, std::size_t byte_index); -[[nodiscard]] int utf8_display_width(const std::string& text, std::size_t byte_limit = std::string::npos); -void pop_utf8_back(std::string& text); +[[nodiscard]] std::vector wrap_text(const std::string &text, int width); +[[nodiscard]] std::size_t utf8_byte_index_from_utf16_offset(const std::string &text, + std::size_t utf16_offset); +[[nodiscard]] std::size_t utf8_prev_index(const std::string &text, std::size_t byte_index); +[[nodiscard]] std::size_t utf8_next_index(const std::string &text, std::size_t byte_index); +[[nodiscard]] int utf8_display_width(const std::string &text, + std::size_t byte_limit = std::string::npos); +void pop_utf8_back(std::string &text); -} // namespace telegram_tui +} // namespace telegram_tui