aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md4
-rw-r--r--TODO4
-rw-r--r--include/DownloadUtils.hpp2
-rw-r--r--include/NetUtils.hpp1
-rw-r--r--include/QuickMedia.hpp3
-rw-r--r--include/Storage.hpp2
-rw-r--r--include/StringUtils.hpp1
-rw-r--r--input.conf1
-rw-r--r--plugins/FileManager.hpp19
-rw-r--r--plugins/Page.hpp2
-rw-r--r--src/Body.cpp12
-rw-r--r--src/DownloadUtils.cpp23
-rw-r--r--src/NetUtils.cpp28
-rw-r--r--src/QuickMedia.cpp663
-rw-r--r--src/Storage.cpp18
-rw-r--r--src/StringUtils.cpp10
-rw-r--r--src/VideoPlayer.cpp3
-rw-r--r--src/main.cpp4
-rw-r--r--src/plugins/FileManager.cpp36
-rw-r--r--src/plugins/MangaGeneric.cpp30
20 files changed, 740 insertions, 126 deletions
diff --git a/README.md b/README.md
index 5575541..72fb795 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,9 @@ Press `U` in matrix or in a 4chan thread to bring up the file manager to choose
Press `Ctrl + V` to upload media to room in matrix if the clipboard contains a valid absolute filepath.\
Press `Ctrl + D` to remove the file that was previously selected with `U` in a 4chan thread.\
Press `Ctrl + I` to reverse image search the selected image on 4chan or matrix.\
-Press `Ctrl+Alt+Arrow up` / `Ctrl+Alt+Arrow down` or `Ctrl+Alt+K` / `Ctrl+Alt+J` to view the room above/below the selected room in matrix.
+Press `Ctrl+Alt+Arrow up` / `Ctrl+Alt+Arrow down` or `Ctrl+Alt+K` / `Ctrl+Alt+J` to view the room above/below the selected room in matrix.\
+Press `Ctrl + S` to save the displaying image/video/audio (does currently not work for manga pages).\
+Press `Ctrl + Enter` to submit text, ignoring the selected item (when saving a file or selecting a server for matrix room directory).
In matrix you can select a message with enter to open the url in the message (or if there are multiple urls then a menu will appear for selecting which to open).
## Matrix commands
diff --git a/TODO b/TODO
index 9d40983..79aa6a9 100644
--- a/TODO
+++ b/TODO
@@ -23,7 +23,6 @@ Scrolling in images still messes up the |current| page sometimes, need a way to
Show filename at the bottom when viewing an image/video on 4chan.
Add ctrl+c keybinding to copy the url of the previewing image.
Add ctrl+c keybiding to copy the url to the currently selected post on 4chan.
-Add ctrl+s to save the previewing image/video (for images that would be a copy, since its already stored in cache and for videos youtube-dl would be used).
Use https://github.com/simdjson/simdjson as a json library in other parts than matrix.
Sanitize check: do not allow pasting more than 2gb of text.
Implement mentions in matrix with an autofill list, like on element. Also do the same with / commands.
@@ -131,4 +130,5 @@ Add client side 4chan file size limit (note, webm has different limit than image
Add client side 4chan max comment chars limit.
Use a directory icon and a file icon for non-media files in the file manager.
Dynamically fetch 4chan api key, if it ever changes in the future. Same for youtube.
-Set curl download limits everywhere (when saving to file, downloading to json, etc...). \ No newline at end of file
+Set curl download limits everywhere (when saving to file, downloading to json, etc...).
+In the downloader if we already have the url in thumbnail/video cache, then copy it to the destination instead of redownloading it. This would also fix downloading images when viewing a manga page. \ No newline at end of file
diff --git a/include/DownloadUtils.hpp b/include/DownloadUtils.hpp
index 0a68069..964e74b 100644
--- a/include/DownloadUtils.hpp
+++ b/include/DownloadUtils.hpp
@@ -27,5 +27,7 @@ namespace QuickMedia {
DownloadResult download_to_string_cache(const std::string &url, std::string &result, const std::vector<CommandArg> &additional_args, bool use_browser_useragent = false, DownloadErrorHandler error_handler = nullptr, Path cache_path = "");
// Note: This function saves the content to the file atomically
DownloadResult download_to_file(const std::string &url, const std::string &destination_filepath, const std::vector<CommandArg> &additional_args, bool use_browser_useragent = false);
+ // Returns false if there was an error trying to create the download process
+ bool download_async_gui(const std::string &url, bool use_youtube_dl, bool no_video);
DownloadResult download_to_json(const std::string &url, rapidjson::Document &result, const std::vector<CommandArg> &additional_args, bool use_browser_useragent = false, bool fail_on_error = true);
} \ No newline at end of file
diff --git a/include/NetUtils.hpp b/include/NetUtils.hpp
index e719c82..bacafc7 100644
--- a/include/NetUtils.hpp
+++ b/include/NetUtils.hpp
@@ -15,4 +15,5 @@ namespace QuickMedia {
std::vector<Range> extract_urls(const std::string &str);
std::vector<std::string> ranges_get_strings(const std::string &str, const std::vector<Range> &ranges);
void convert_utf8_to_utf32_ranges(const std::string &str, std::vector<Range> &ranges);
+ std::string header_extract_value(const std::string &header, const std::string &type);
} \ No newline at end of file
diff --git a/include/QuickMedia.hpp b/include/QuickMedia.hpp
index 30836aa..01baf76 100644
--- a/include/QuickMedia.hpp
+++ b/include/QuickMedia.hpp
@@ -100,7 +100,7 @@ namespace QuickMedia {
Json::Value load_video_history_json();
private:
- void init(Window parent_window);
+ void init(Window parent_window, std::string &program_path);
void load_plugin_by_name(std::vector<Tab> &tabs, const char *start_dir, int &start_tab_index, FileManagerMimeType fm_mime_type, FileSelectionHandler file_selection_handler);
// Returns true if the window was closed
bool handle_window_close();
@@ -119,6 +119,7 @@ namespace QuickMedia {
void chat_login_page();
bool chat_page(MatrixChatPage *matrix_chat_page, RoomData *current_room, std::vector<Tab> &room_tabs, int room_selected_tab);
void after_matrix_login_page();
+ void download_page(const char *url, bool download_use_youtube_dl);
enum class LoadImageResult {
OK,
diff --git a/include/Storage.hpp b/include/Storage.hpp
index 4dab9b3..93d13ff 100644
--- a/include/Storage.hpp
+++ b/include/Storage.hpp
@@ -36,4 +36,6 @@ namespace QuickMedia {
int rename_atomic(const char *oldpath, const char *newpath);
bool is_program_executable_by_name(const char *name);
+
+ std::string file_size_to_human_readable_string(size_t bytes);
} \ No newline at end of file
diff --git a/include/StringUtils.hpp b/include/StringUtils.hpp
index 6df16db..8a94d2f 100644
--- a/include/StringUtils.hpp
+++ b/include/StringUtils.hpp
@@ -17,4 +17,5 @@ namespace QuickMedia {
std::string strip(const std::string &str);
bool string_starts_with(const std::string &str, const char *sub);
bool string_ends_with(const std::string &str, const std::string &ends_with_str);
+ size_t str_find_case_insensitive(const std::string &str, size_t start_index, const char *substr, size_t substr_len);
} \ No newline at end of file
diff --git a/input.conf b/input.conf
index 249b43e..635a82c 100644
--- a/input.conf
+++ b/input.conf
@@ -1,4 +1,5 @@
Ctrl+c ignore
+Ctrl+s ignore
BS ignore
q ignore
WHEEL_UP ignore
diff --git a/plugins/FileManager.hpp b/plugins/FileManager.hpp
index 72ac75d..20ed49c 100644
--- a/plugins/FileManager.hpp
+++ b/plugins/FileManager.hpp
@@ -4,6 +4,8 @@
#include <filesystem>
namespace QuickMedia {
+ class FileManagerPage;
+
enum FileManagerMimeType {
FILE_MANAGER_MIME_TYPE_IMAGE = (1 << 0),
FILE_MANAGER_MIME_TYPE_VIDEO = (1 << 1),
@@ -12,20 +14,31 @@ namespace QuickMedia {
static const FileManagerMimeType FILE_MANAGER_MIME_TYPE_ALL = (FileManagerMimeType)(FILE_MANAGER_MIME_TYPE_IMAGE|FILE_MANAGER_MIME_TYPE_VIDEO|FILE_MANAGER_MIME_TYPE_OTHER);
// Return the tags to go to after selecting a file, or return an empty array to exit the program
- using FileSelectionHandler = std::function<std::vector<Tab>()>;
+ using FileSelectionHandler = std::function<std::vector<Tab>(FileManagerPage*, const std::filesystem::path&)>;
class FileManagerPage : public Page {
public:
- FileManagerPage(Program *program, FileManagerMimeType mime_type = FILE_MANAGER_MIME_TYPE_ALL, FileSelectionHandler selection_handler = nullptr) : Page(program), current_dir("/"), mime_type(mime_type), selection_handler(selection_handler) {}
- const char* get_title() const override { return current_dir.c_str(); }
+ FileManagerPage(Program *program, FileManagerMimeType mime_type = FILE_MANAGER_MIME_TYPE_ALL, FileSelectionHandler selection_handler = nullptr, bool allow_empty_match_submit = false, const std::string &title_prefix = "") :
+ Page(program), current_dir("/"), mime_type(mime_type), selection_handler(selection_handler), allow_empty_match_submit(allow_empty_match_submit), title_prefix(title_prefix)
+ {
+ title = title_prefix + current_dir.string();
+ }
+ const char* get_title() const override { return title.c_str(); }
PluginResult submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) override;
bool is_single_page() const override { return true; }
+ bool allow_submit_no_selection() const override { return allow_empty_match_submit; }
+ void on_navigate_to_page(Body*) override;
bool set_current_directory(const std::string &path);
PluginResult get_files_in_directory(BodyItems &result_items);
+
+ bool close = false;
private:
std::filesystem::path current_dir;
FileManagerMimeType mime_type;
FileSelectionHandler selection_handler;
+ bool allow_empty_match_submit;
+ std::string title;
+ std::string title_prefix;
};
} \ No newline at end of file
diff --git a/plugins/Page.hpp b/plugins/Page.hpp
index 426469a..78eb3c4 100644
--- a/plugins/Page.hpp
+++ b/plugins/Page.hpp
@@ -47,7 +47,7 @@ namespace QuickMedia {
virtual bool is_single_page() const { return false; }
virtual bool is_trackable() const { return false; }
virtual bool is_lazy_fetch_page() const { return false; }
- // Note: If submit is done without any selection, then the search term is sent as the |title|, not |url|
+ // Note: If submit is done without any selection, then the search term is sent as the |title|, not |url|. Submit will only be sent if the input text is not empty or if an item is selected
virtual bool allow_submit_no_selection() const { return false; }
// This is called both when first navigating to page and when going back to page
diff --git a/src/Body.cpp b/src/Body.cpp
index 98f08e8..9494e80 100644
--- a/src/Body.cpp
+++ b/src/Body.cpp
@@ -400,7 +400,7 @@ namespace QuickMedia {
}
bool Body::on_event(const sf::RenderWindow &window, const sf::Event &event, bool keyboard_navigation) {
- if(keyboard_navigation && event.type == sf::Event::KeyPressed) {
+ if(keyboard_navigation && event.type == sf::Event::KeyPressed && !event.key.alt) {
if(event.key.code == sf::Keyboard::Up || (event.key.control && event.key.code == sf::Keyboard::K)) {
bool top_reached = select_previous_item();
if(!top_reached && on_top_reached)
@@ -1323,16 +1323,6 @@ namespace QuickMedia {
return spacing_y;
}
- static size_t str_find_case_insensitive(const std::string &str, size_t start_index, const char *substr, size_t substr_len) {
- auto it = std::search(str.begin() + start_index, str.end(), substr, substr + substr_len,
- [](char c1, char c2) {
- return std::toupper(c1) == std::toupper(c2);
- });
- if(it == str.end())
- return std::string::npos;
- return it - str.begin();
- }
-
// TODO: Support utf-8 case insensitive find
static bool string_find_fuzzy_case_insensitive(const std::string &str, const std::string &substr) {
if(str.empty()) return false;
diff --git a/src/DownloadUtils.cpp b/src/DownloadUtils.cpp
index 4afba38..53f8bb0 100644
--- a/src/DownloadUtils.cpp
+++ b/src/DownloadUtils.cpp
@@ -2,7 +2,10 @@
#include "../include/Program.hpp"
#include "../include/Storage.hpp"
#include "../external/cppcodec/base64_url.hpp"
+#include <unistd.h>
+#include <limits.h>
#include <SFML/System/Clock.hpp>
+#include <SFML/Window/Event.hpp>
#include <rapidjson/document.h>
#include <rapidjson/filereadstream.h>
@@ -54,7 +57,8 @@ namespace QuickMedia {
args.push_back("-f");
for(const CommandArg &arg : additional_args) {
args.push_back(arg.option.c_str());
- args.push_back(arg.value.c_str());
+ if(!arg.value.empty())
+ args.push_back(arg.value.c_str());
}
if(use_browser_useragent) {
args.push_back("-H");
@@ -137,6 +141,20 @@ namespace QuickMedia {
return DownloadResult::OK;
}
+ bool download_async_gui(const std::string &url, bool use_youtube_dl, bool no_video) {
+ char quickmedia_path[PATH_MAX];
+ if(readlink("/proc/self/exe", quickmedia_path, sizeof(quickmedia_path)) == -1)
+ return false;
+
+ std::vector<const char*> args = { quickmedia_path, "download", "-u", url.c_str() };
+ if(use_youtube_dl)
+ args.push_back("--youtube-dl");
+ if(no_video)
+ args.push_back("--no-video");
+ args.push_back(nullptr);
+ return exec_program_async(args.data(), nullptr) == 0;
+ }
+
// TODO: Add timeout
DownloadResult download_to_json(const std::string &url, rapidjson::Document &result, const std::vector<CommandArg> &additional_args, bool use_browser_useragent, bool fail_on_error) {
sf::Clock timer;
@@ -146,7 +164,8 @@ namespace QuickMedia {
args.push_back("-f");
for(const CommandArg &arg : additional_args) {
args.push_back(arg.option.c_str());
- args.push_back(arg.value.c_str());
+ if(!arg.value.empty())
+ args.push_back(arg.value.c_str());
}
if(use_browser_useragent) {
args.push_back("-H");
diff --git a/src/NetUtils.cpp b/src/NetUtils.cpp
index 3539d46..dc7c2d2 100644
--- a/src/NetUtils.cpp
+++ b/src/NetUtils.cpp
@@ -4,6 +4,7 @@
#include <sstream>
#include <iomanip>
#include <assert.h>
+#include <string.h>
#include <unordered_set>
namespace QuickMedia {
@@ -1742,4 +1743,31 @@ namespace QuickMedia {
++num_codepoints;
}
}
+
+ std::string header_extract_value(const std::string &header, const std::string &type) {
+ std::string result;
+ string_split(header, '\n', [&type, &result](const char *str, size_t size) {
+ while(size > 0 && (*str == ' ' || *str == '\t')) { ++str; --size; }
+ if(size < type.size() || strncasecmp(str, type.c_str(), type.size()) != 0 || size == type.size())
+ return true;
+
+ str += type.size();
+ size -= type.size();
+
+ const void *colon_ptr = memchr(str, ':', size);
+ if(!colon_ptr)
+ return true;
+
+ const size_t colon_offset = (const char*)colon_ptr - str;
+ str += (colon_offset + 1);
+ size -= (colon_offset + 1);
+
+ while(size > 0 && (*str == ' ' || *str == '\t')) { ++str; --size; }
+ while(size > 0 && (str[size - 1] == ' ' || str[size - 1] == '\t' || str[size - 1] == '\r' || str[size - 1] == '\n')) { --size; }
+
+ result.assign(str, size);
+ return false;
+ });
+ return result;
+ }
} \ No newline at end of file
diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp
index 112ec77..92c259e 100644
--- a/src/QuickMedia.cpp
+++ b/src/QuickMedia.cpp
@@ -36,6 +36,8 @@
#include <string.h>
#include <signal.h>
#include <malloc.h>
+#include <unistd.h>
+#include <libgen.h>
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Window/Clipboard.hpp>
@@ -78,7 +80,8 @@ static const std::pair<const char*, const char*> valid_plugins[] = {
std::make_pair("pleroma", "pleroma_logo.png"),
std::make_pair("file-manager", nullptr),
std::make_pair("stdin", nullptr),
- std::make_pair("saucenao", nullptr)
+ std::make_pair("saucenao", nullptr),
+ std::make_pair("download", nullptr)
};
static const char* get_plugin_logo_name(const char *plugin_name) {
@@ -110,7 +113,7 @@ static const XRRModeInfo* get_mode_info(const XRRScreenResources *sr, RRMode id)
return nullptr;
}
-static void for_each_active_monitor_output(Display *display, std::function<void(const XRRModeInfo*)> callback_func) {
+static void for_each_active_monitor_output(Display *display, std::function<void(const XRRCrtcInfo*, const XRRModeInfo*)> callback_func) {
XRRScreenResources *screen_res = XRRGetScreenResources(display, DefaultRootWindow(display));
if(!screen_res)
return;
@@ -122,7 +125,7 @@ static void for_each_active_monitor_output(Display *display, std::function<void(
if(crt_info) {
const XRRModeInfo *mode_info = get_mode_info(screen_res, crt_info->mode);
if(mode_info)
- callback_func(mode_info);
+ callback_func(crt_info, mode_info);
XRRFreeCrtcInfo(crt_info);
}
}
@@ -135,7 +138,7 @@ static void for_each_active_monitor_output(Display *display, std::function<void(
static int get_monitor_max_hz(Display *display) {
unsigned long max_hz = 0;
- for_each_active_monitor_output(display, [&max_hz](const XRRModeInfo *mode_info) {
+ for_each_active_monitor_output(display, [&max_hz](const XRRCrtcInfo*, const XRRModeInfo *mode_info) {
unsigned long total = mode_info->hTotal * mode_info->vTotal;
if(total > 0)
max_hz = std::max(max_hz, (unsigned long)std::round((double)mode_info->dotClock / (double)total));
@@ -148,9 +151,9 @@ static int get_monitor_max_hz(Display *display) {
static int get_largest_monitor_height(Display *display) {
int max_height = 0;
- for_each_active_monitor_output(display, [&max_height](const XRRModeInfo *mode_info) {
+ for_each_active_monitor_output(display, [&max_height](const XRRCrtcInfo *crtc_info, const XRRModeInfo*) {
// Need to get the min of width or height because we want to get the smallest size for monitors in portrait mode, for mobile devices such as pinephone
- int width_or_height = std::min((int)mode_info->width, (int)mode_info->height);
+ int width_or_height = std::min((int)crtc_info->width, (int)crtc_info->height);
max_height = std::max(max_height, width_or_height);
});
@@ -350,6 +353,9 @@ namespace QuickMedia {
const char *start_dir = nullptr;
Window parent_window = None;
std::vector<Tab> tabs;
+ const char *url = nullptr;
+ bool download_use_youtube_dl = false;
+ std::string program_path = dirname(argv[0]);
for(int i = 1; i < argc; ++i) {
if(!plugin_name) {
@@ -380,6 +386,17 @@ namespace QuickMedia {
}
} else if(strcmp(argv[i], "--low-cpu-mode") == 0) {
low_cpu_mode = true;
+ } else if(strcmp(argv[i], "--youtube-dl") == 0) {
+ download_use_youtube_dl = true;
+ } else if(strcmp(argv[i], "-u") == 0) {
+ if(i < argc - 1) {
+ url = argv[i + 1];
+ ++i;
+ } else {
+ fprintf(stderr, "Missing url after -u argument\n");
+ usage();
+ return -1;
+ }
} else if(strcmp(argv[i], "-e") == 0) {
if(i < argc - 1) {
parent_window = strtol(argv[i + 1], nullptr, 0);
@@ -468,18 +485,30 @@ namespace QuickMedia {
FileManagerMimeType fm_mine_type = FILE_MANAGER_MIME_TYPE_ALL;
FileSelectionHandler file_selection_handler = nullptr;
- FileSelectionHandler saucenao_file_selection_handler = [this]() {
+ FileSelectionHandler saucenao_file_selection_handler = [this](FileManagerPage*, const std::filesystem::path&) {
std::vector<Tab> tabs;
tabs.push_back(Tab{create_body(), std::make_unique<SaucenaoPage>(this, selected_files[0], true), nullptr});
return tabs;
};
- init(parent_window);
+ init(parent_window, program_path);
+
+ if(strcmp(plugin_name, "download") == 0) {
+ if(!url) {
+ fprintf(stderr, "-u argument has to be set when using the download plugin\n");
+ usage();
+ return -1;
+ }
+ download_page(url, download_use_youtube_dl);
+ return exit_code;
+ }
+
if(strcmp(plugin_name, "saucenao") == 0) {
plugin_name = "file-manager";
fm_mine_type = FILE_MANAGER_MIME_TYPE_IMAGE;
file_selection_handler = std::move(saucenao_file_selection_handler);
}
+
load_plugin_by_name(tabs, start_dir, start_tab_index, fm_mine_type, std::move(file_selection_handler));
while(!tabs.empty() || matrix) {
@@ -512,7 +541,23 @@ namespace QuickMedia {
return exit_code;
}
- void Program::init(Window parent_window) {
+ static sf::Vector2i get_focused_monitor_center(Display *disp) {
+ int screen = DefaultScreen(disp);
+ int screen_center_x = DisplayWidth(disp, screen) / 2;
+ int screen_center_y = DisplayHeight(disp, screen) / 2;
+
+ sf::Vector2i focused_monitor_center(screen_center_x, screen_center_y);
+ auto mouse_pos = sf::Mouse::getPosition();
+ for_each_active_monitor_output(disp, [&focused_monitor_center, mouse_pos](const XRRCrtcInfo *crtc_info, const XRRModeInfo*){
+ if(sf::Rect<int>(crtc_info->x, crtc_info->y, crtc_info->width, crtc_info->height).contains(mouse_pos))
+ focused_monitor_center = sf::Vector2i(crtc_info->x + crtc_info->width/2, crtc_info->y + crtc_info->height/2);
+ });
+
+ return focused_monitor_center;
+ }
+
+ void Program::init(Window parent_window, std::string &program_path) {
+ XInitThreads();
disp = XOpenDisplay(NULL);
if (!disp)
throw std::runtime_error("Failed to open display to X11 server");
@@ -520,11 +565,10 @@ namespace QuickMedia {
wm_delete_window_atom = XInternAtom(disp, "WM_DELETE_WINDOW", False);
int screen = DefaultScreen(disp);
- int screen_center_x = (DisplayWidth(disp, screen) - window_size.x) / 2;
- int screen_center_y = (DisplayHeight(disp, screen) - window_size.y) / 2;
+ sf::Vector2i focused_monitor_center = get_focused_monitor_center(disp);
x11_window = XCreateWindow(disp, parent_window ? parent_window : DefaultRootWindow(disp),
- screen_center_x, screen_center_y, window_size.x, window_size.y, 0,
+ focused_monitor_center.x - window_size.x * 0.5f, focused_monitor_center.y - window_size.y * 0.5f, window_size.x, window_size.y, 0,
DefaultDepth(disp, screen),
InputOutput,
DefaultVisual(disp, screen),
@@ -532,15 +576,35 @@ namespace QuickMedia {
if(!x11_window)
throw std::runtime_error("Failed to create window");
+ if(strcmp(plugin_name, "download") == 0) {
+ XSizeHints *size_hints = XAllocSizeHints();
+ if(size_hints) {
+ size_hints->width = window_size.x;
+ size_hints->min_width = window_size.x;
+ size_hints->max_width = window_size.x;
+
+ size_hints->height = window_size.y;
+ size_hints->min_height = window_size.y;
+ size_hints->max_height = window_size.y;
+ size_hints->flags = PSize | PMinSize | PMaxSize;
+
+ XSetWMNormalHints(disp, x11_window, size_hints);
+ XFree(size_hints);
+ }
+ }
+
XStoreName(disp, x11_window, "QuickMedia");
XMapWindow(disp, x11_window);
XFlush(disp);
window.create(x11_window);
+ if(program_path.back() != '/')
+ program_path += '/';
+
resources_root = "/usr/share/quickmedia/";
- if(get_file_type("../../../images/manganelo_logo.png") == FileType::REGULAR) {
- resources_root = "../../../";
+ if(get_file_type(program_path + "../../../images/manganelo_logo.png") == FileType::REGULAR) {
+ resources_root = program_path + "../../../";
}
set_resource_loader_root_path(resources_root.c_str());
@@ -1430,7 +1494,9 @@ namespace QuickMedia {
std::function<void(const std::string&)> submit_handler = [this, &submit_handler, &after_submit_handler, &tabs, &tab_associated_data, &ui_tabs, &loop_running, &redraw](const std::string &search_text) {
const int selected_tab = ui_tabs.get_selected();
auto selected_item = tabs[selected_tab].body->get_selected_shared();
- if(!selected_item && !tabs[selected_tab].page->allow_submit_no_selection())
+ if(tabs[selected_tab].page->allow_submit_no_selection() && (sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) || sf::Keyboard::isKeyPressed(sf::Keyboard::RControl)))
+ selected_item = nullptr;
+ if(!selected_item && (!tabs[selected_tab].page->allow_submit_no_selection() || search_text.empty()))
return;
hide_virtual_keyboard();
@@ -1604,38 +1670,38 @@ namespace QuickMedia {
for(size_t i = 0; i < tabs.size(); ++i) {
Tab &tab = tabs[i];
+
tab.body->body_item_select_callback = [&submit_handler](BodyItem *body_item) {
submit_handler(body_item->get_title());
};
- TabAssociatedData &associated_data = tab_associated_data[i];
- if(!tab.search_bar)
- continue;
-
- // tab.search_bar->autocomplete_search_delay = current_plugin->get_autocomplete_delay();
- // tab.search_bar->onAutocompleteRequestCallback = [this, &tabs, &selected_tab, &autocomplete_text](const std::string &text) {
- // if(tabs[selected_tab].body == body && !current_plugin->search_is_filter())
- // autocomplete_text = text;
- // };
-
- tab.search_bar->onTextUpdateCallback = [&associated_data, &tabs, i](const std::string &text) {
- if(!tabs[i].page->search_is_filter()) {
- associated_data.update_search_text = text;
- associated_data.search_text_updated = true;
- } else {
- tabs[i].body->filter_search_fuzzy(text);
- tabs[i].body->select_first_item();
- }
- associated_data.typing = false;
- };
+ tab.body->on_bottom_reached = on_bottom_reached;
- tab.search_bar->onTextSubmitCallback = [&submit_handler, &associated_data](const std::string &search_text) {
- if(associated_data.typing)
- return;
- submit_handler(search_text);
- };
+ TabAssociatedData &associated_data = tab_associated_data[i];
+ if(tab.search_bar) {
+ // tab.search_bar->autocomplete_search_delay = current_plugin->get_autocomplete_delay();
+ // tab.search_bar->onAutocompleteRequestCallback = [this, &tabs, &selected_tab, &autocomplete_text](const std::string &text) {
+ // if(tabs[selected_tab].body == body && !current_plugin->search_is_filter())
+ // autocomplete_text = text;
+ // };
+
+ tab.search_bar->onTextUpdateCallback = [&associated_data, &tabs, i](const std::string &text) {
+ if(!tabs[i].page->search_is_filter()) {
+ associated_data.update_search_text = text;
+ associated_data.search_text_updated = true;
+ } else {
+ tabs[i].body->filter_search_fuzzy(text);
+ tabs[i].body->select_first_item();
+ }
+ associated_data.typing = false;
+ };
- tab.body->on_bottom_reached = on_bottom_reached;
+ tab.search_bar->onTextSubmitCallback = [&submit_handler, &associated_data](const std::string &search_text) {
+ if(associated_data.typing)
+ return;
+ submit_handler(search_text);
+ };
+ }
}
sf::Event event;
@@ -2203,6 +2269,8 @@ namespace QuickMedia {
current_page = previous_page;
} else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::C && event.key.control) {
save_video_url_to_clipboard();
+ } else if(event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::S && event.key.control) {
+ download_async_gui(original_video_url, true, force_no_video);
}
}
handle_window_close();
@@ -2218,6 +2286,8 @@ namespace QuickMedia {
break;
} else if(pressed_keysym == XK_f && pressing_ctrl) {
window_set_fullscreen(disp, window.getSystemHandle(), WindowFullscreenState::TOGGLE);
+ } else if(pressed_keysym == XK_s && pressing_ctrl) {
+ download_async_gui(original_video_url, true, force_no_video);
} else if(pressed_keysym == XK_r && pressing_ctrl) {
if(!cursor_visible)
window.setMouseCursorVisible(true);
@@ -3112,7 +3182,7 @@ namespace QuickMedia {
} else if(event.key.code == sf::Keyboard::P) {
BodyItem *selected_item = thread_body->get_selected();
if(selected_item && !selected_item->url.empty()) {
- if(is_url_video(selected_item->url)) {
+ if(is_url_video(selected_item->url)) {
page_stack.push(PageType::IMAGE_BOARD_THREAD);
current_page = PageType::VIDEO_CONTENT;
watched_videos.clear();
@@ -3298,6 +3368,8 @@ namespace QuickMedia {
page_loop(saucenao_tabs);
redraw = true;
frame_skip_text_entry = true;
+ } else if(event.key.code == sf::Keyboard::S && event.key.control) {
+ download_async_gui(attached_image_url, false, false);
}
}
}
@@ -4505,15 +4577,6 @@ namespace QuickMedia {
}
};
- if(!matrix->is_initial_sync_finished()) {
- previous_messages_future = AsyncTask<Messages>([this, &current_room]() {
- Messages messages;
- if(matrix->get_previous_room_messages(current_room, messages, true) != PluginResult::OK)
- fprintf(stderr, "Failed to get previous matrix messages in room: %s\n", current_room->id.c_str());
- return messages;
- });
- }
-
sf::RectangleShape more_messages_below_rect;
more_messages_below_rect.setFillColor(sf::Color(128, 50, 50));
@@ -5546,4 +5609,502 @@ namespace QuickMedia {
matrix->stop_sync();
}
+
+ enum class DownloadUpdateStatus {
+ DOWNLOADING,
+ FINISHED,
+ ERROR
+ };
+
+ class Downloader {
+ public:
+ Downloader(const std::string &url, const std::string &output_filepath) : url(url), output_filepath(output_filepath) {}
+ virtual ~Downloader() = default;
+
+ virtual bool start() = 0;
+ virtual void stop(bool download_completed) = 0;
+ virtual DownloadUpdateStatus update() = 0;
+
+ virtual float get_progress() = 0;
+ virtual std::string get_progress_text() = 0;
+ virtual std::string get_download_speed_text() = 0;
+ protected:
+ std::string url;
+ std::string output_filepath;
+ };
+
+ class CurlDownloader : public Downloader {
+ public:
+ CurlDownloader(const std::string &url, const std::string &output_filepath) : Downloader(url, output_filepath) {
+ output_filepath_tmp = output_filepath;
+ output_filepath_tmp.append(".tmp");
+ read_program.pid = -1;
+ read_program.read_fd = -1;
+ progress_text = "0 bytes/Unknown";
+ download_speed_text = "Unknown/s";
+ }
+
+ bool start() override {
+ remove(output_filepath.c_str());
+ remove(output_filepath_tmp.data.c_str());
+
+ const char *args[] = { "curl",
+ "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive", "--compressed",
+ "-H", "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36",
+ "-g", "-s", "-L", "-f", "-o", output_filepath_tmp.data.c_str(),
+ "-D", "/dev/stdout",
+ "--", url.c_str(), nullptr };
+
+ if(exec_program_pipe(args, &read_program) != 0)
+ return false;
+
+ header_reader = AsyncTask<bool>([this]{
+ char tmp_buf[1024];
+ while(true) {
+ ssize_t bytes_available = read(read_program.read_fd, tmp_buf, sizeof(tmp_buf));
+ if(bytes_available == -1) {
+ return false;
+ } else if(bytes_available > 0 && content_length == (size_t)-1) {
+ header.append(tmp_buf, bytes_available);
+ if(header.find("\r\n\r\n") != std::string::npos) {
+ std::string content_length_str = header_extract_value(header, "content-length");
+ if(!content_length_str.empty()) {
+ errno = 0;
+ char *endptr;
+ const long content_length_tmp = strtol(content_length_str.c_str(), &endptr, 10);
+ if(endptr != content_length_str.c_str() && errno == 0) {
+ std::lock_guard<std::mutex> lock(content_length_mutex);
+ content_length = content_length_tmp;
+ }
+ }
+ }
+ }
+ }
+ return true;
+ });
+
+ return true;
+ }
+
+ void stop(bool download_completed) override {
+ if(read_program.pid != -1)
+ close(read_program.pid);
+ if(read_program.read_fd != -1)
+ kill(read_program.read_fd, SIGTERM);
+ if(!download_completed)
+ remove(output_filepath_tmp.data.c_str());
+ //header_reader.cancel();
+ }
+
+ DownloadUpdateStatus update() override {
+ int status = 0;
+ if(wait_program_non_blocking(read_program.pid, &status)) {
+ read_program.pid = -1;
+ if(status == 0 && rename_atomic(output_filepath_tmp.data.c_str(), output_filepath.c_str()) == 0) {
+ return DownloadUpdateStatus::FINISHED;
+ } else {
+ return DownloadUpdateStatus::ERROR;
+ }
+ }
+
+ if(header_reader.ready()) {
+ if(!header_reader.get())
+ return DownloadUpdateStatus::ERROR;
+ }
+
+ std::lock_guard<std::mutex> lock(content_length_mutex);
+ size_t output_file_size = 0;
+ file_get_size(output_filepath_tmp, &output_file_size);
+ size_t downloaded_size = std::min(output_file_size, content_length);
+
+ if(content_length == (size_t)-1) {
+ progress_text = std::to_string(output_file_size / 1024) + "/Unknown";
+ } else {
+ size_t percentage = 0;
+ if(output_file_size > 0)
+ percentage = (double)downloaded_size / (double)content_length * 100.0;
+ progress = (double)percentage / 100.0;
+ progress_text = file_size_to_human_readable_string(downloaded_size) + "/" + file_size_to_human_readable_string(content_length) + " (" + std::to_string(percentage) + "%)";
+ }
+
+ // TODO: Take into consideration time overflow?
+ size_t downloaded_diff = std::max(0lu, downloaded_size - downloaded_since_last_check);
+ download_speed_text = file_size_to_human_readable_string(downloaded_diff) + "/s";
+ downloaded_since_last_check = downloaded_size;
+
+ return DownloadUpdateStatus::DOWNLOADING;
+ }
+
+ float get_progress() override {
+ return progress;
+ }
+
+ std::string get_progress_text() override {
+ return progress_text;
+ }
+
+ std::string get_download_speed_text() override {
+ return download_speed_text;
+ }
+ private:
+ Path output_filepath_tmp;
+ ReadProgram read_program;
+ AsyncTask<bool> header_reader;
+ std::string header;
+ size_t content_length = (size_t)-1;
+ size_t downloaded_since_last_check = 0;
+ float progress = 0.0f;
+ std::mutex content_length_mutex;
+ std::string progress_text;
+ std::string download_speed_text;
+ };
+
+ class YoutubeDlDownloader : public Downloader {
+ public:
+ YoutubeDlDownloader(const std::string &url, const std::string &output_filepath, bool no_video) : Downloader(url, output_filepath), no_video(no_video) {
+ // youtube-dl requires a file extension for the file
+ if(this->output_filepath.find('.') == std::string::npos)
+ this->output_filepath += ".mkv";
+
+ read_program.pid = -1;
+ read_program.read_fd = -1;
+ progress_text = "0.0% of Unknown";
+ download_speed_text = "Unknown/s";
+ }
+
+ bool start() override {
+ remove(output_filepath.c_str());
+
+ std::vector<const char*> args = { "youtube-dl", "--no-warnings", "--no-continue", "--output", output_filepath.c_str(), "--newline" };
+ if(no_video)
+ args.push_back("-x");
+ args.insert(args.end(), { "--", url.c_str(), nullptr });
+
+ if(exec_program_pipe(args.data(), &read_program) != 0)
+ return false;
+
+ read_program_file = fdopen(read_program.read_fd, "rb");
+ if(!read_program_file) {
+ wait_program(read_program.pid);
+ return false;
+ }
+
+ youtube_dl_output_reader = AsyncTask<bool>([this]{
+ char line[128];
+ char progress_c[10];
+ char content_size_c[20];
+ char download_speed_c[20];
+
+ while(true) {
+ if(fgets(line, sizeof(line), read_program_file)) {
+ int len = strlen(line);
+ if(len > 0 && line[len - 1] == '\n') {
+ line[len - 1] = '\0';
+ --len;
+ }
+
+ if(sscanf(line, "[download] %10s of %20s at %20s", progress_c, content_size_c, download_speed_c) == 3) {
+ std::lock_guard<std::mutex> lock(progress_update_mutex);
+
+ if(strcmp(progress_c, "Unknown") != 0 && strcmp(content_size_c, "Unknown") != 0) {
+ std::string progress_str = progress_c;
+ progress_text = progress_str + " of " + content_size_c;
+ if(progress_str.back() == '%') {
+ errno = 0;
+ char *endptr;
+ const double progress_tmp = strtod(progress_str.c_str(), &endptr);
+ if(endptr != progress_str.c_str() && errno == 0)
+ progress = progress_tmp / 100.0;
+ }
+ }
+
+ if(strcmp(download_speed_c, "Unknown") == 0)
+ download_speed_text = "Unknown/s";
+ else
+ download_speed_text = download_speed_c;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ return true;
+ });
+
+ return true;
+ }
+
+ void stop(bool) override {
+ if(read_program_file)
+ fclose(read_program_file);
+ if(read_program.read_fd != -1)
+ kill(read_program.read_fd, SIGTERM);
+ // TODO: Remove the temporary files created by youtube-dl (if !download_completed)
+ //header_reader.cancel();
+ }
+
+ DownloadUpdateStatus update() override {
+ int status = 0;
+ if(wait_program_non_blocking(read_program.pid, &status)) {
+ read_program.pid = -1;
+ if(status == 0) {
+ return DownloadUpdateStatus::FINISHED;
+ } else {
+ return DownloadUpdateStatus::ERROR;
+ }
+ }
+
+ if(youtube_dl_output_reader.ready()) {
+ if(!youtube_dl_output_reader.get())
+ return DownloadUpdateStatus::ERROR;
+ }
+
+ return DownloadUpdateStatus::DOWNLOADING;
+ }
+
+ float get_progress() override {
+ std::lock_guard<std::mutex> lock(progress_update_mutex);
+ return progress;
+ }
+
+ std::string get_progress_text() override {
+ std::lock_guard<std::mutex> lock(progress_update_mutex);
+ return progress_text;
+ }
+
+ std::string get_download_speed_text() override {
+ std::lock_guard<std::mutex> lock(progress_update_mutex);
+ return download_speed_text;
+ }
+ private:
+ ReadProgram read_program;
+ FILE *read_program_file = nullptr;
+ AsyncTask<bool> youtube_dl_output_reader;
+ std::mutex progress_update_mutex;
+ float progress = 0.0f;
+ std::string progress_text;
+ std::string download_speed_text;
+ bool no_video;
+ };
+
+ static const char* get_filename(const char *path) {
+ const char *p = (const char*)memrchr(path, '/', strlen(path));
+ return p ? p + 1 : path;
+ }
+
+ class ConfirmationPage : public Page {
+ public:
+ ConfirmationPage(Program *program, FileManagerPage *file_manager_page, bool *file_overwrite, const std::string &title) : Page(program), file_manager_page(file_manager_page), file_overwrite(file_overwrite), title(title) {}
+ const char* get_title() const override { return title.c_str(); }
+ PluginResult submit(const std::string &title, const std::string&, std::vector<Tab>&) override {
+ if(title == "Yes") {
+ *file_overwrite = true;
+ file_manager_page->close = true;
+ } else {
+ *file_overwrite = false;
+ }
+ program->set_go_to_previous_page();
+ return PluginResult::OK;
+ }
+
+ static void add_items(BodyItems &items) {
+ items.push_back(BodyItem::create("No"));
+ items.push_back(BodyItem::create("Yes"));
+ }
+ private:
+ FileManagerPage *file_manager_page;
+ bool *file_overwrite;
+ std::string title;
+ };
+
+ void Program::download_page(const char *url, bool download_use_youtube_dl) {
+ bool file_overwrite = true;
+ FileSelectionHandler overwrite_confirm_handler = [this, &file_overwrite](FileManagerPage *file_manager_page, const std::filesystem::path &path) {
+ file_overwrite = true;
+ std::vector<Tab> tabs;
+ if(std::filesystem::exists(path)) {
+ auto body = create_body();
+ ConfirmationPage::add_items(body->items);
+ tabs.push_back(Tab{ std::move(body), std::make_unique<ConfirmationPage>(this, file_manager_page, &file_overwrite, "Are you sure you want to overwrite " + path.string() + "?"), nullptr });
+ }
+ return tabs;
+ };
+
+ auto file_manager_page = std::make_unique<FileManagerPage>(this, FILE_MANAGER_MIME_TYPE_ALL, std::move(overwrite_confirm_handler), true, "Where do you want to save the file? Current directory: ");
+ file_manager_page->set_current_directory(get_home_dir().data);
+ auto file_manager_body = create_body();
+ file_manager_page->get_files_in_directory(file_manager_body->items);
+ std::vector<Tab> file_manager_tabs;
+ file_manager_tabs.push_back(Tab{std::move(file_manager_body), std::move(file_manager_page), create_search_bar("Search...", SEARCH_DELAY_FILTER)});
+
+ selected_files.clear();
+ page_loop(file_manager_tabs);
+
+ if(!window.isOpen() || selected_files.empty() || !file_overwrite) {
+ exit_code = 1;
+ return;
+ }
+
+ sf::Vector2i focused_monitor_center = get_focused_monitor_center(disp);
+ window_size.x = std::floor(300.0f + 380.0f * get_ui_scale());
+ window_size.y = std::floor(50.0f + 130.0f * get_ui_scale());
+ window.setSize(sf::Vector2u(window_size.x, window_size.y));
+ XSizeHints *size_hints = XAllocSizeHints();
+ if(size_hints) {
+ size_hints->width = window_size.x;
+ size_hints->min_width = window_size.x;
+ size_hints->max_width = window_size.x;
+
+ size_hints->height = window_size.y;
+ size_hints->min_height = window_size.y;
+ size_hints->max_height = window_size.y;
+ size_hints->flags = PSize | PMinSize | PMaxSize;
+
+ XSetWMNormalHints(disp, x11_window, size_hints);
+ XFree(size_hints);
+ XFlush(disp);
+ }
+ window.setPosition(sf::Vector2i(focused_monitor_center.x - window_size.x * 0.5f, focused_monitor_center.y - window_size.y * 0.5f));
+
+ std::string output_filepath = selected_files[0];
+ std::string output_filepath_s = output_filepath;
+ char *output_dir = dirname(output_filepath_s.data());
+ if(create_directory_recursive(output_dir) != 0) {
+ show_notification("QuickMedia", std::string("Failed to download ") + url + " to " + output_filepath, Urgency::CRITICAL);
+ exit_code = 1;
+ return;
+ }
+
+ window.setFramerateLimit(monitor_hz);
+ window.clear(back_color);
+ window.display();
+
+ const float loading_bar_padding_x = std::floor(4.0f * get_ui_scale());
+ const float loading_bar_padding_y = std::floor(4.0f * get_ui_scale());
+ RoundedRectangle loading_bar_background(sf::Vector2f(1.0f, 1.0f), std::floor(10.0f * get_ui_scale()), sf::Color(21, 25, 30), &rounded_rectangle_shader);
+ RoundedRectangle loading_bar(sf::Vector2f(1.0f, 1.0f), std::floor(10.0f * get_ui_scale() - loading_bar_padding_y), sf::Color(0, 85, 119), &rounded_rectangle_shader);
+
+ const float padding_x = std::floor(30.0f * get_ui_scale());
+ const float spacing_y = std::floor(15.0f * get_ui_scale());
+ const float loading_bar_height = std::floor(20.0f * get_ui_scale());
+
+ sf::Text progress_text("0kb/Unknown", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(20.0f * get_ui_scale()));
+ sf::Text status_text("Downloading", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(20.0f * get_ui_scale()));
+ sf::Text filename_text(get_filename(output_filepath.c_str()), *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(14.0f * get_ui_scale()));
+ filename_text.setFillColor(sf::Color(179, 179, 179));
+ sf::Text download_speed_text("0 bytes/s", *FontLoader::get_font(FontLoader::FontType::LATIN), std::floor(14.0f * get_ui_scale()));
+ download_speed_text.setFillColor(sf::Color(179, 179, 179));
+
+ bool redraw = true;
+ sf::Event event;
+
+ std::unique_ptr<Downloader> downloader;
+ if(download_use_youtube_dl)
+ downloader = std::make_unique<YoutubeDlDownloader>(url, output_filepath, force_no_video);
+ else
+ downloader = std::make_unique<CurlDownloader>(url, output_filepath);
+
+ if(!downloader->start()) {
+ show_notification("QuickMedia", std::string("Failed to download ") + url + " to " + output_filepath, Urgency::CRITICAL);
+ exit_code = 1;
+ return;
+ }
+
+ sf::Clock frame_timer;
+ sf::Clock progress_update_timer;
+ bool download_completed = false;
+ float progress = 0.0f;
+ float ui_progress = 0.0f;
+
+ while(window.isOpen()) {
+ while (window.pollEvent(event)) {
+ if(event.type == sf::Event::Resized) {
+ window_size.x = event.size.width;
+ window_size.y = event.size.height;
+ sf::FloatRect visible_area(0, 0, window_size.x, window_size.y);
+ window.setView(sf::View(visible_area));
+ redraw = true;
+ }
+ }
+
+ if(handle_window_close()) {
+ show_notification("QuickMedia", "Download cancelled!");
+ downloader->stop(false);
+ exit_code = 1;
+ exit(exit_code);
+ }
+
+ if(progress_update_timer.getElapsedTime().asSeconds() >= 1.0f) {
+ progress_update_timer.restart();
+ DownloadUpdateStatus update_status = downloader->update();
+ switch(update_status) {
+ case DownloadUpdateStatus::DOWNLOADING:
+ break;
+ case DownloadUpdateStatus::FINISHED:
+ download_completed = true;
+ goto cleanup;
+ case DownloadUpdateStatus::ERROR:
+ goto cleanup;
+ }
+
+ progress = downloader->get_progress();
+ progress = std::max(0.0f, std::min(1.0f, progress));
+ progress_text.setString(downloader->get_progress_text());
+ download_speed_text.setString(downloader->get_download_speed_text());
+ redraw = true;
+ }
+
+ if(redraw) {
+ redraw = false;
+ loading_bar_background.set_size(sf::Vector2f(window_size.x - padding_x * 2.0f, loading_bar_height));
+ loading_bar_background.set_position(window_size * 0.5f - loading_bar_background.get_size() * 0.5f + sf::Vector2f(0.0f, download_speed_text.getLocalBounds().height * 0.5f));
+ loading_bar_background.set_position(sf::Vector2f(std::floor(loading_bar_background.get_position().x), std::floor(loading_bar_background.get_position().y)));
+ loading_bar.set_position(loading_bar_background.get_position() + sf::Vector2f(loading_bar_padding_x, loading_bar_padding_y));
+ filename_text.setPosition(
+ loading_bar_background.get_position() + sf::Vector2f(0.0f, -(filename_text.getLocalBounds().height + spacing_y)));
+ progress_text.setPosition(
+ filename_text.getPosition() + sf::Vector2f(loading_bar_background.get_size().x - progress_text.getLocalBounds().width, -(progress_text.getLocalBounds().height + spacing_y)));
+ status_text.setPosition(
+ filename_text.getPosition() + sf::Vector2f(0.0f, -(status_text.getLocalBounds().height + spacing_y)));
+ download_speed_text.setPosition(
+ loading_bar_background.get_position() + sf::Vector2f(0.0f, loading_bar_height + spacing_y));
+ }
+
+ const float progress_diff = std::abs(progress - ui_progress);
+ const float progress_move = frame_timer.getElapsedTime().asSeconds() * 500.0f * progress_diff;
+ if(progress_diff < progress_move) {
+ ui_progress = progress;
+ } else {
+ if(progress_diff > 0.0f)
+ ui_progress += progress_move;
+ else
+ ui_progress -= progress_move;
+ }
+
+ loading_bar.set_size(sf::Vector2f(
+ std::floor((loading_bar_background.get_size().x - loading_bar_padding_x) * ui_progress),
+ loading_bar_height - loading_bar_padding_y * 2.0f));
+
+ window.clear(sf::Color(33, 37, 44));
+ loading_bar_background.draw(window);
+ loading_bar.draw(window);
+ window.draw(progress_text);
+ window.draw(status_text);
+ window.draw(filename_text);
+ window.draw(download_speed_text);
+ window.display();
+ frame_timer.restart();
+ }
+
+ cleanup:
+ downloader->stop(download_completed);
+ if(download_completed) {
+ show_notification("QuickMedia", std::string("Download finished! Downloaded ") + url + " to " + output_filepath);
+ exit_code = 0;
+ } else {
+ show_notification("QuickMedia", std::string("Failed to download ") + url + " to " + output_filepath, Urgency::CRITICAL);
+ exit_code = 1;
+ }
+ exit(exit_code);
+ }
}
diff --git a/src/Storage.cpp b/src/Storage.cpp
index 6a98267..85a150c 100644
--- a/src/Storage.cpp
+++ b/src/Storage.cpp
@@ -259,4 +259,22 @@ namespace QuickMedia {
return false;
}
+
+ std::string file_size_to_human_readable_string(size_t bytes) {
+ double kb = (double)bytes / 1024.0;
+ double mb = (double)bytes / 1024.0 / 1024.0;
+ double gb = (double)bytes / 1024.0 / 1024.0 / 1024.0;
+ char result[32];
+
+ if(gb >= 1.0)
+ snprintf(result, sizeof(result), "%.1f GiB", gb);
+ else if(mb >= 1.0)
+ snprintf(result, sizeof(result), "%.1f MiB", mb);
+ else if(kb >= 1.0)
+ snprintf(result, sizeof(result), "%.1f KiB", kb);
+ else
+ snprintf(result, sizeof(result), "%zu bytes", bytes);
+
+ return result;
+ }
}
diff --git a/src/StringUtils.cpp b/src/StringUtils.cpp
index 84e2ce5..0345558 100644
--- a/src/StringUtils.cpp
+++ b/src/StringUtils.cpp
@@ -87,4 +87,14 @@ namespace QuickMedia {
size_t ends_len = ends_with_str.size();
return ends_len == 0 || (str.size() >= ends_len && memcmp(&str[str.size() - ends_len], ends_with_str.data(), ends_len) == 0);
}
+
+ size_t str_find_case_insensitive(const std::string &str, size_t start_index, const char *substr, size_t substr_len) {
+ auto it = std::search(str.begin() + start_index, str.end(), substr, substr + substr_len,
+ [](char c1, char c2) {
+ return std::toupper(c1) == std::toupper(c2);
+ });
+ if(it == str.end())
+ return std::string::npos;
+ return it - str.begin();
+ }
} \ No newline at end of file
diff --git a/src/VideoPlayer.cpp b/src/VideoPlayer.cpp
index 5f860d2..fd5ba79 100644
--- a/src/VideoPlayer.cpp
+++ b/src/VideoPlayer.cpp
@@ -208,9 +208,10 @@ namespace QuickMedia {
Window parent_window;
Window *child_window;
unsigned int num_children;
- if(XQueryTree(display, window, &root_window, &parent_window, &child_window, &num_children) != 0) {
+ if(XQueryTree(display, window, &root_window, &parent_window, &child_window, &num_children)) {
for(unsigned int i = 0; i < num_children; i++)
result.push_back(child_window[i]);
+ XFree(child_window);
}
return result;
}
diff --git a/src/main.cpp b/src/main.cpp
index 3383363..89d824d 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -1,12 +1,8 @@
#include "../include/QuickMedia.hpp"
-#include <X11/Xlib.h>
-#include <libgen.h>
#include <unistd.h>
int main(int argc, char **argv) {
- chdir(dirname(argv[0]));
setlocale(LC_ALL, "C"); // Sigh... stupid C
- XInitThreads();
QuickMedia::Program program;
return program.run(argc, argv);
}
diff --git a/src/plugins/FileManager.cpp b/src/plugins/FileManager.cpp
index 52f9f4e..e1f3b04 100644
--- a/src/plugins/FileManager.cpp
+++ b/src/plugins/FileManager.cpp
@@ -23,24 +23,6 @@ namespace QuickMedia {
return last_write_time;
}
- static std::string file_size_to_human_readable_string(size_t bytes) {
- double kb = (double)bytes / 1024.0;
- double mb = (double)bytes / 1024.0 / 1024.0;
- double gb = (double)bytes / 1024.0 / 1024.0 / 1024.0;
- char result[32];
-
- if(gb >= 1.0)
- snprintf(result, sizeof(result), "%.1f GiB", gb);
- else if(mb >= 1.0)
- snprintf(result, sizeof(result), "%.1f MiB", mb);
- else if(kb >= 1.0)
- snprintf(result, sizeof(result), "%.1f KiB", kb);
- else
- snprintf(result, sizeof(result), "%zu bytes", bytes);
-
- return result;
- }
-
PluginResult FileManagerPage::submit(const std::string &title, const std::string &url, std::vector<Tab> &result_tabs) {
(void)url;
@@ -53,14 +35,22 @@ namespace QuickMedia {
if(std::filesystem::is_regular_file(new_path)) {
program->select_file(new_path);
if(selection_handler)
- result_tabs = selection_handler();
+ result_tabs = selection_handler(this, new_path);
return PluginResult::OK;
}
- if(!std::filesystem::is_directory(new_path))
+ if(!std::filesystem::is_directory(new_path)) {
+ if(allow_empty_match_submit) {
+ program->select_file(new_path);
+ if(selection_handler)
+ result_tabs = selection_handler(this, new_path);
+ return PluginResult::OK;
+ }
return PluginResult::ERR;
+ }
current_dir = std::move(new_path);
+ this->title = title_prefix + current_dir.string();
BodyItems result_items;
PluginResult result = get_files_in_directory(result_items);
@@ -73,10 +63,16 @@ namespace QuickMedia {
return PluginResult::OK;
}
+ void FileManagerPage::on_navigate_to_page(Body*) {
+ if(close)
+ program->set_go_to_previous_page();
+ }
+
bool FileManagerPage::set_current_directory(const std::string &path) {
if(!std::filesystem::is_directory(path))
return false;
current_dir = path;
+ title = title_prefix + current_dir.string();
return true;
}
diff --git a/src/plugins/MangaGeneric.cpp b/src/plugins/MangaGeneric.cpp
index a6df1c1..2e88d60 100644
--- a/src/plugins/MangaGeneric.cpp
+++ b/src/plugins/MangaGeneric.cpp
@@ -121,34 +121,6 @@ namespace QuickMedia {
}, page_image_userdata);
}
- static size_t str_find_case_insensitive(const std::string &str, size_t start_index, const char *substr, size_t substr_len) {
- auto it = std::search(str.begin() + start_index, str.end(), substr, substr + substr_len,
- [](char c1, char c2) {
- return std::toupper(c1) == std::toupper(c2);
- });
- if(it == str.end())
- return std::string::npos;
- return it - str.begin();
- }
-
- static std::string header_extract_location(const std::string &headers) {
- size_t index = str_find_case_insensitive(headers, 0, "location:", 9);
- if(index != std::string::npos && (index == 0 || headers[index - 1] == '\n')) {
- index += 9;
- size_t end = headers.find('\r', index);
- size_t start = index;
- while(start < end) {
- char c = headers[start];
- if(c != ' ' && c != '\t')
- break;
- ++start;
- }
- if(end - start > 0)
- return headers.substr(start, end - start);
- }
- return "";
- }
-
MangaGenericSearchPage::MangaGenericSearchPage(Program *program, const char *service_name, const char *website_url, bool fail_on_http_error) :
Page(program), service_name(service_name), website_url(website_url ? website_url : ""), fail_on_http_error(fail_on_http_error)
{
@@ -228,7 +200,7 @@ namespace QuickMedia {
goto cleanup;
}
- target_url = header_extract_location(response_headers);
+ target_url = header_extract_value(response_headers, "location");
if(target_url.empty()) {
fprintf(stderr, "Failed to extract target location from %s HEAD\n", url.c_str());
result = -1;