From ba55befc63547c056cb60e36a1c0a6488da1563d Mon Sep 17 00:00:00 2001 From: Mario Fetka Date: Tue, 14 Jul 2026 18:16:21 +0200 Subject: [PATCH] Add URL drag and drop and desktop-aware theming --- CMakeLists.txt | 2 +- README | 5 +- po/fr.po | 6 ++ po/it.po | 6 ++ po/nl.po | 6 ++ po/prozgui.pot | 9 +++ po/pt_BR.po | 6 ++ po/ro.po | 6 ++ src/main.cpp | 204 ++++++++++++++++++++++++++++++++++++++++++++++++- 9 files changed, 245 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1828c0c..9101990 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) -project(prozgui VERSION 2.2.2 LANGUAGES C CXX) +project(prozgui VERSION 2.3.0 LANGUAGES C CXX) include(GNUInstallDirs) find_package(Threads REQUIRED) diff --git a/README b/README index 5c9fd90..3684f64 100644 --- a/README +++ b/README @@ -11,6 +11,10 @@ file downloading speeds. Features: Supports FTP Supports HTTP including redirection. +Supports dragging URLs, newline-separated URL lists, and local URL-list files +onto the main window. +Selects a suitable FLTK appearance for GNOME/MATE and KDE automatically; +set FLTK_SCHEME to override the automatic choice. Proxy servers supported. Resume Supported. Improved error checking and correction. @@ -93,4 +97,3 @@ We sincerely hope that you will like the download accelerator.. Enjoy! Kalum Somaratana (Grendel) - diff --git a/po/fr.po b/po/fr.po index cae28ea..7623140 100644 --- a/po/fr.po +++ b/po/fr.po @@ -532,3 +532,9 @@ msgstr "Version de l'interface %s" msgid "libprozilla version %s" msgstr "Version de libprozilla %s" +msgid "Drop URLs or URL list files here" +msgstr "Déposez ici des URL ou des fichiers de listes d’URL" + +#, c-format +msgid "Started %d download(s); skipped %d invalid, duplicate, or unreadable item(s)." +msgstr "%d téléchargement(s) démarré(s) ; %d élément(s) invalide(s), en double ou illisible(s) ignoré(s)." diff --git a/po/it.po b/po/it.po index 6eb00f4..a1c8336 100644 --- a/po/it.po +++ b/po/it.po @@ -515,3 +515,9 @@ msgstr "Versione GUI %s" msgid "libprozilla version %s" msgstr "Versione libprozilla %s" +msgid "Drop URLs or URL list files here" +msgstr "Trascina qui URL o file con elenchi di URL" + +#, c-format +msgid "Started %d download(s); skipped %d invalid, duplicate, or unreadable item(s)." +msgstr "Avviati %d download; ignorati %d elementi non validi, duplicati o illeggibili." diff --git a/po/nl.po b/po/nl.po index d6afaa1..2e38746 100644 --- a/po/nl.po +++ b/po/nl.po @@ -431,3 +431,9 @@ msgstr "GUI-versie %s" msgid "libprozilla version %s" msgstr "libprozilla-versie %s" +msgid "Drop URLs or URL list files here" +msgstr "Sleep URL's of bestanden met URL-lijsten hierheen" + +#, c-format +msgid "Started %d download(s); skipped %d invalid, duplicate, or unreadable item(s)." +msgstr "%d download(s) gestart; %d ongeldig(e), dubbel(e) of onleesbaar(e) item(s) overgeslagen." diff --git a/po/prozgui.pot b/po/prozgui.pot index 71a0194..3ce2b20 100644 --- a/po/prozgui.pot +++ b/po/prozgui.pot @@ -555,6 +555,15 @@ msgstr "" msgid "Prozilla - Download Accelerator" msgstr "" +#: src/main.cpp +msgid "Drop URLs or URL list files here" +msgstr "" + +#: src/main.cpp +#, c-format +msgid "Started %d download(s); skipped %d invalid, duplicate, or unreadable item(s)." +msgstr "" + #: src/options.cpp:130 #, c-format msgid "%s does not seem to be a valid directory" diff --git a/po/pt_BR.po b/po/pt_BR.po index b2e0678..ad93068 100644 --- a/po/pt_BR.po +++ b/po/pt_BR.po @@ -534,3 +534,9 @@ msgstr "Versão da interface %s" msgid "libprozilla version %s" msgstr "Versão da libprozilla %s" +msgid "Drop URLs or URL list files here" +msgstr "Solte URLs ou arquivos de listas de URLs aqui" + +#, c-format +msgid "Started %d download(s); skipped %d invalid, duplicate, or unreadable item(s)." +msgstr "%d download(s) iniciado(s); %d item(ns) inválido(s), duplicado(s) ou ilegível(is) ignorado(s)." diff --git a/po/ro.po b/po/ro.po index b42c0ad..21b6146 100644 --- a/po/ro.po +++ b/po/ro.po @@ -454,3 +454,9 @@ msgstr "Versiune interfață %s" msgid "libprozilla version %s" msgstr "Versiune libprozilla %s" +msgid "Drop URLs or URL list files here" +msgstr "Plasați aici URL-uri sau fișiere cu liste de URL-uri" + +#, c-format +msgid "Started %d download(s); skipped %d invalid, duplicate, or unreadable item(s)." +msgstr "S-au pornit %d descărcări; s-au omis %d elemente nevalide, duplicate sau ilizibile." diff --git a/src/main.cpp b/src/main.cpp index 9516e01..41ab7f6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -70,6 +76,36 @@ void open_new_dl_win(urlinfo * url_data, boolean ftpsearch); void menu_download_start(); void menu_download_stop(); void menu_download_remove(); +void import_dropped_text(const char *text); + +void apply_desktop_scheme() +{ + const char *requested_scheme = getenv("FLTK_SCHEME"); + if (requested_scheme && *requested_scheme) + { + Fl::scheme(requested_scheme); + return; + } + + const char *desktop = getenv("XDG_CURRENT_DESKTOP"); + if (!desktop) + desktop = getenv("XDG_SESSION_DESKTOP"); + if (!desktop) + return; + + std::string name(desktop); + for (std::string::size_type i = 0; i < name.size(); ++i) + name[i] = static_cast(std::tolower(static_cast(name[i]))); + + if (name.find("gnome") != std::string::npos || + name.find("mate") != std::string::npos || + name.find("cinnamon") != std::string::npos || + name.find("xfce") != std::string::npos) + Fl::scheme("gtk+"); + else if (name.find("kde") != std::string::npos || + name.find("plasma") != std::string::npos) + Fl::scheme("gleam"); +} class Main_Window:public Fl_Window { public: @@ -85,11 +121,17 @@ Main_Window::Main_Window(int w, int h, const char *l):Fl_Window(w, h, l) int Main_Window::handle(int e) { - - if (e == FL_PASTE) + switch (e) { - // printf("%s\n", Fl::event_text()); + case FL_DND_ENTER: + case FL_DND_DRAG: + case FL_DND_RELEASE: return 1; + case FL_PASTE: + import_dropped_text(Fl::event_text()); + return 1; + default: + break; } if (Fl_Window::handle(e)) @@ -209,6 +251,154 @@ void open_new_dl_win(urlinfo * url_data, boolean ftpsearch) } +namespace { + +std::string trim(const std::string &value) +{ + const std::string::size_type first = value.find_first_not_of(" \t\r\n"); + if (first == std::string::npos) + return std::string(); + const std::string::size_type last = value.find_last_not_of(" \t\r\n"); + return value.substr(first, last - first + 1); +} + +int hex_value(char value) +{ + if (value >= '0' && value <= '9') + return value - '0'; + value = static_cast(std::tolower(static_cast(value))); + return value >= 'a' && value <= 'f' ? value - 'a' + 10 : -1; +} + +bool file_uri_to_path(const std::string &uri, std::string *path) +{ + if (uri.compare(0, 7, "file://") != 0) + return false; + + std::string encoded = uri.substr(7); + if (encoded.compare(0, 10, "localhost/") == 0) + encoded.erase(0, 9); + else if (encoded.empty() || encoded[0] != '/') + return false; + + path->clear(); + for (std::string::size_type i = 0; i < encoded.size(); ++i) + { + if (encoded[i] == '%' && i + 2 < encoded.size()) + { + const int high = hex_value(encoded[i + 1]); + const int low = hex_value(encoded[i + 2]); + if (high < 0 || low < 0) + return false; + path->push_back(static_cast((high << 4) | low)); + i += 2; + } + else + path->push_back(encoded[i]); + } + return path->find('\0') == std::string::npos; +} + +bool read_url_list(const std::string &path, std::string *contents) +{ + std::error_code error; + const std::filesystem::path file(path); + if (!std::filesystem::is_regular_file(file, error) || error) + return false; + const std::uintmax_t size = std::filesystem::file_size(file, error); + if (error || size > 1024 * 1024) + return false; + + std::ifstream stream(file, std::ios::binary); + if (!stream) + return false; + std::ostringstream buffer; + buffer << stream.rdbuf(); + *contents = buffer.str(); + return static_cast(stream) || stream.eof(); +} + +bool start_dropped_url(const std::string &url) +{ + urlinfo *url_data = static_cast(calloc(1, sizeof(urlinfo))); + if (!url_data) + return false; + + if (proz_parse_url(url.c_str(), url_data, 0) != URLOK || + strlen(url_data->file) == 0) + { + proz_free_url(url_data, 0); + free(url_data); + return false; + } + + for (int i = 0; i < browser->size(); ++i) + { + if (strcmp(url_data->url, browser->text(i + 1)) == 0) + { + proz_free_url(url_data, 0); + free(url_data); + return false; + } + } + + open_new_dl_win(url_data, rt.use_ftpsearch); + return true; +} + +void import_url_lines(const std::string &text, int *started, int *skipped) +{ + std::istringstream lines(text); + std::string line; + while (std::getline(lines, line)) + { + line = trim(line); + if (line.empty() || line[0] == '#') + continue; + if (start_dropped_url(line)) + ++*started; + else + ++*skipped; + } +} + +} // namespace + +void import_dropped_text(const char *text) +{ + if (!text || !*text) + return; + + int started = 0; + int skipped = 0; + std::istringstream dropped(text); + std::string item; + while (std::getline(dropped, item)) + { + item = trim(item); + if (item.empty() || item[0] == '#') + continue; + + std::string path; + std::string contents; + if (file_uri_to_path(item, &path)) + { + if (read_url_list(path, &contents)) + import_url_lines(contents, &started, &skipped); + else + ++skipped; + } + else if (start_dropped_url(item)) + ++started; + else + ++skipped; + } + + if (skipped > 0) + fl_message(_("Started %d download(s); skipped %d invalid, duplicate, or unreadable item(s)."), + started, skipped); +} + void show_prefs() @@ -431,6 +621,8 @@ int main(int argc, char **argv) bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); + apply_desktop_scheme(); + set_defaults(&argc, &argv); load_prefs(); set_runtime_values(); @@ -473,6 +665,12 @@ int main(int argc, char **argv) menubar->menu(menuitems); } + Fl_Box *drop_hint = new Fl_Box(20, 55, 460, 165, + _("Drop URLs or URL list files here")); + drop_hint->box(FL_DOWN_BOX); + drop_hint->labelsize(16); + drop_hint->labelcolor(FL_DARK3); + /*The browser window */ browser = new Fl_Hold_Browser(2, 250, 495, 80); browser->callback(browser_cb);