#pragma once #include "Body.hpp" #include "Page.hpp" #include "Tab.hpp" #include "MessageQueue.hpp" #include "AsyncTask.hpp" #include "../plugins/Plugin.hpp" #include "../plugins/FileManager.hpp" #include #include #include #include #include #include #include #include #include #include #include typedef struct _XDisplay Display; namespace QuickMedia { class Matrix; class FileManager; class MangaImagesPage; class ImageBoardThreadPage; struct RoomData; class MatrixChatPage; class VideoPage; class Tabs; class VideoPlayer; class SearchBar; enum class ImageViewMode { SINGLE, SCROLL }; struct CopyOp { Path source; Path destination; }; enum class TaskResult { TRUE, FALSE, CANCEL }; enum class FetchType { SEARCH, LAZY }; struct FetchResult { BodyItems body_items; PluginResult result; }; struct TabAssociatedData { std::string update_search_text; bool search_text_updated = false; FetchStatus fetch_status = FetchStatus::NONE; bool lazy_fetch_finished = false; FetchType fetch_type; bool typing = false; bool fetching_next_page_running = false; bool fetching_next_page_failed = false; bool search_suggestion_submitted = false; bool search_text_empty = true; bool card_view = false; int fetched_page = 0; mgl::Text search_result_text; AsyncTask fetch_future; AsyncTask next_page_future; std::string body_item_url_before_refresh; }; struct LoginInput { std::string placeholder; SearchBarType type; }; class Program { public: Program(); ~Program(); int run(int argc, char **argv); std::unique_ptr create_body(bool plain_text_list = false, bool prefer_card_view = false); std::unique_ptr create_search_bar(const std::string &placeholder, int search_delay); void add_login_inputs(Tab *tab, std::vector login_inputs); bool load_manga_content_storage(const char *service_name, const std::string &manga_title, const std::string &manga_url, const std::string &manga_id); void select_file(const std::string &filepath); bool is_window_focused(); RoomData* get_current_chat_room(); void set_go_to_previous_page(); void set_pipe_selected_text(const std::string &text); TaskResult run_task_with_loading_screen(std::function callback); const char* get_plugin_name() const; void manga_get_watch_history(const char *plugin_name, BodyItems &history_items, bool local_thumbnail); void youtube_get_watch_history(BodyItems &history_items); void update_manga_history(const std::string &manga_id, const std::string &thumbnail_url); Json::Value load_history_json(); Json::Value load_recommended_json(const char *plugin_name); void fill_recommended_items_from_json(const char *plugin_name, const Json::Value &recommended_json, BodyItems &body_items); void save_recommendations_from_related_videos(const char *plugin_name, const std::string &video_url, const std::string &video_title, const BodyItems &related_media_body_items); void set_clipboard(const std::string &str); private: void init(mgl::WindowHandle parent_window, std::string &program_path); void load_plugin_by_name(std::vector &tabs, int &start_tab_index, FileManagerMimeType fm_mime_type, FileSelectionHandler file_selection_handler, std::string instance); void common_event_handler(mgl::Event &event); void handle_x11_events(); void base_event_handler(mgl::Event &event, PageType previous_page, Body *body, SearchBar *search_bar, bool handle_key_press = true, bool handle_searchbar = true); void event_idle_handler(const mgl::Event &event); void idle_active_handler(); void update_idle_state(); bool show_info_page(BodyItem *body_item, bool include_reverse_image_search); bool toggle_bookmark(BodyItem *body_item, const char *bookmark_name); void page_loop_render(mgl::Window &window, std::vector &tabs, int selected_tab, TabAssociatedData &tab_associated_data, const Json::Value *json_chapters, Tabs &ui_tabs); using PageLoopSubmitHandler = std::function &new_tabs)>; // Returns false if the page loop was escaped by user navigation (pressing escape) or if there was an error at startup bool page_loop(std::vector &tabs, int start_tab_index = 0, PageLoopSubmitHandler after_submit_handler = nullptr, bool go_to_previous_on_escape = true); void redirect_focus_to_video_player_window(mgl::WindowHandle video_player_window); void show_video_player_window(mgl::WindowHandle video_player_window); void video_page_download_video(const std::string &url, mgl::WindowHandle video_player_window = 0); bool video_download_if_non_streamable(std::string &video_url, std::string &audio_url, bool &is_audio_only, bool &has_embedded_audio, PageType previous_page); int video_get_max_height(); void video_content_page(Page *parent_page, VideoPage *video_page, std::string video_title, bool download_if_streaming_fails, Body *parent_body, int play_index, int *parent_body_page = nullptr, const std::string &parent_page_search = ""); void save_manga_progress(MangaImagesPage *images_page, Json::Value &json_chapters, Json::Value &json_chapter, int &latest_read); // Returns -1 to go to previous chapter, 0 to stay on same chapter and 1 to go to next chapter int image_page(MangaImagesPage *images_page, Body *chapters_body, bool &continue_left_off); // Returns -1 to go to previous chapter, 0 to stay on same chapter and 1 to go to next chapter int image_continuous_page(MangaImagesPage *images_page); void image_board_thread_page(ImageBoardThreadPage *thread_page, Body *thread_body); void chat_login_page(); bool chat_page(MatrixChatPage *matrix_chat_page, RoomData *current_room); void after_matrix_login_page(); void download_page(std::string url); // Returns the full path where the file should be saved, or an empty string if the operation was cancelled std::string file_save_page(const std::string &filename); enum class LoadImageResult { OK, FAILED, DOWNLOAD_IN_PROGRESS }; LoadImageResult load_image_by_index(int image_index, mgl::Texture &image_texture, std::string &error_message); void download_chapter_images_if_needed(MangaImagesPage *images_page); void select_episode(BodyItem *item, bool start_from_beginning); // Returns PageType::EXIT if empty PageType pop_page_stack(); private: enum class UpscaleImageAction { NO, LOW_RESOLUTION, FORCE }; Display *disp; mgl::Window window; Matrix *matrix = nullptr; bool is_login_sync = false; mgl::vec2i window_size; const char *plugin_name = nullptr; mgl::Texture plugin_logo; mgl::Texture loading_icon; mgl::Sprite load_sprite; mgl::Clock load_sprite_timer; PageType current_page; std::stack page_stack; int image_index; Path content_storage_file; Path content_cache_dir; std::string manga_id; std::string manga_id_base64; Json::Value content_storage_json; bool content_storage_file_modified = false; std::unordered_set watched_videos; AsyncTask search_suggestion_future; AsyncTask autocomplete_future; AsyncTask> image_download_future; std::thread image_upscale_thead; MessageQueue images_to_upscale_queue; std::vector image_upscale_status; std::string downloading_chapter_url; bool image_download_cancel = false; std::future num_manga_pages_future; int num_manga_pages = 0; int exit_code = 0; std::string resources_root; mgl::Shader circle_mask_shader; mgl::Shader rounded_rectangle_shader; mgl::Shader rounded_rectangle_mask_shader; bool no_video = false; bool force_no_video = false; UpscaleImageAction upscale_image_action = UpscaleImageAction::NO; // TODO: Save this to config file when switching modes ImageViewMode image_view_mode = ImageViewMode::SINGLE; std::vector selected_files; bool fit_image_to_window = false; RoomData *current_chat_room = nullptr; bool go_to_previous_page = false; bool running_task_with_loading_screen = false; mgl::Vertex gradient_points[4]; mgl::vec2f body_pos; mgl::vec2f body_size; bool show_room_side_panel = true; std::thread::id main_thread_id; mgl::Clock idle_timer; bool idle = true; bool low_cpu_mode = false; bool window_closed = false; std::string pipe_selected_text; std::filesystem::path file_manager_start_dir; std::string youtube_url; std::unique_ptr video_player; bool use_youtube_dl = false; int video_max_height = 0; std::mutex login_inputs_mutex; }; }