aboutsummaryrefslogtreecommitdiff
path: root/include/QuickMedia.hpp
blob: b5570da3da5cde64ea8b15b0e453ee14aa26aed3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#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 <vector>
#include <memory>
#include <mglpp/graphics/Font.hpp>
#include <mglpp/graphics/Texture.hpp>
#include <mglpp/window/Window.hpp>
#include <mglpp/graphics/Shader.hpp>
#include <unordered_set>
#include <json/value.h>
#include <future>
#include <thread>
#include <stack>

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 card_view = false;
        int fetched_page = 0;
        mgl::Text search_result_text;
        AsyncTask<FetchResult> fetch_future;
        AsyncTask<BodyItems> 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<Body> create_body(bool plain_text_list = false, bool prefer_card_view = false);
        std::unique_ptr<SearchBar> create_search_bar(const std::string &placeholder, int search_delay);
        void add_login_inputs(Tab *tab, std::vector<LoginInput> 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<bool()> 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<Tab> &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<Tab> &tabs, int selected_tab, TabAssociatedData &tab_associated_data, const Json::Value *json_chapters, Tabs &ui_tabs);
        using PageLoopSubmitHandler = std::function<void(const std::vector<Tab> &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<Tab> &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<PageType> 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<std::string> watched_videos;
        AsyncTask<BodyItems> search_suggestion_future;
        AsyncTask<std::string> autocomplete_future;
        AsyncTask<void, std::promise<int>> image_download_future;
        std::thread image_upscale_thead;
        MessageQueue<CopyOp> images_to_upscale_queue;
        std::vector<char> image_upscale_status;
        std::string downloading_chapter_url;
        bool image_download_cancel = false;
        std::future<int> 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<std::string> 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<VideoPlayer> video_player;
        bool use_youtube_dl = false;
        int video_max_height = 0;
        std::mutex login_inputs_mutex;
    };
}