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
|
#pragma once
#include "Body.hpp"
#include "SearchBar.hpp"
#include "Page.hpp"
#include "Storage.hpp"
#include "Tab.hpp"
#include "MessageQueue.hpp"
#include "AsyncTask.hpp"
#include "../plugins/Plugin.hpp"
#include "../plugins/FileManager.hpp"
#include <vector>
#include <memory>
#include <SFML/Graphics/Font.hpp>
#include <SFML/Graphics/RenderWindow.hpp>
#include <SFML/Graphics/Shader.hpp>
#include <json/value.h>
#include <unordered_set>
#include <future>
#include <thread>
#include <stack>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
namespace QuickMedia {
class Matrix;
class FileManager;
class MangaImagesPage;
class ImageBoardThreadPage;
class RoomData;
class MatrixChatPage;
class VideoPage;
class Tabs;
class VideoPlayer;
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;
int fetched_page = 0;
sf::Text search_result_text;
AsyncTask<FetchResult> fetch_future;
AsyncTask<BodyItems> next_page_future;
};
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);
bool load_manga_content_storage(const char *service_name, const std::string &manga_title, 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);
void youtube_get_watch_history(BodyItems &history_items);
Json::Value load_video_history_json();
private:
void init(Window 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);
void common_event_handler(sf::Event &event);
void handle_x11_events();
void base_event_handler(sf::Event &event, PageType previous_page, Body *body, SearchBar *search_bar, bool handle_key_press = true, bool handle_searchbar = true);
void event_idle_handler(const sf::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(sf::RenderWindow &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);
void video_page_download_video(const std::string &url, sf::WindowHandle video_player_window = None);
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);
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 = "");
// 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);
void 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, sf::Texture &image_texture, sf::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;
sf::RenderWindow window;
Matrix *matrix = nullptr;
bool is_login_sync = false;
int monitor_hz;
sf::Vector2f window_size;
const char *plugin_name = nullptr;
sf::Texture plugin_logo;
sf::Texture loading_icon;
sf::Sprite load_sprite;
sf::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_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;
sf::Shader circle_mask_shader;
sf::Shader rounded_rectangle_shader;
sf::Shader rounded_rectangle_mask_shader;
bool no_video = false;
bool force_no_video = false;
bool use_system_mpv_config = 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;
sf::Text tab_text;
sf::Vertex gradient_points[4];
sf::Vector2f body_pos;
sf::Vector2f body_size;
bool show_room_side_panel = true;
std::thread::id main_thread_id;
sf::Clock idle_timer;
bool idle = true;
bool low_cpu_mode = false;
bool window_closed = false;
std::string pipe_selected_text;
Window x11_window = None;
Atom wm_delete_window_atom;
Atom net_wm_ping_atom;
XEvent xev;
std::filesystem::path file_manager_start_dir;
std::string youtube_url;
std::unique_ptr<VideoPlayer> video_player;
bool use_youtube_dl = false;
};
}
|