Hello community, here is the log from the commit of package nwg-launchers for openSUSE:Factory checked in at 2020-09-14 12:33:08 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/nwg-launchers (Old) and /work/SRC/openSUSE:Factory/.nwg-launchers.new.4249 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "nwg-launchers" Mon Sep 14 12:33:08 2020 rev:2 rq:834219 version:0.3.3 Changes: -------- --- /work/SRC/openSUSE:Factory/nwg-launchers/nwg-launchers.changes 2020-08-31 16:53:27.256447627 +0200 +++ /work/SRC/openSUSE:Factory/.nwg-launchers.new.4249/nwg-launchers.changes 2020-09-14 12:35:09.121359737 +0200 @@ -1,0 +2,9 @@ +Mon Sep 14 07:29:34 UTC 2020 - Michael Vetter <[email protected]> + +- Update to 0.3.3: + * [nwggrid] support for applications installed with flatpak + * fixes to improper displays geometry detection on X11 + * [nwggrid] new pin/unpin implementation + * code optimization + +------------------------------------------------------------------- Old: ---- v0.3.2.tar.gz New: ---- v0.3.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ nwg-launchers.spec ++++++ --- /var/tmp/diff_new_pack.kweArE/_old 2020-09-14 12:35:13.929362803 +0200 +++ /var/tmp/diff_new_pack.kweArE/_new 2020-09-14 12:35:13.933362805 +0200 @@ -1,5 +1,5 @@ # -# spec file for package new-launchers +# spec file for package nwg-launchers # # Copyright (c) 2020 SUSE LLC # @@ -17,7 +17,7 @@ Name: nwg-launchers -Version: 0.3.2 +Version: 0.3.3 Release: 0 Summary: GTK launchers and menu for sway and i3 License: GPL-3.0-or-later ++++++ v0.3.2.tar.gz -> v0.3.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/CONTRIBUTING.md new/nwg-launchers-0.3.3/CONTRIBUTING.md --- old/nwg-launchers-0.3.2/CONTRIBUTING.md 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/CONTRIBUTING.md 2020-09-08 10:28:55.000000000 +0200 @@ -9,6 +9,8 @@ 2. new features 3. optimization, refactoring, e.t.c. +Please test your changes not in sway only, but also in i3 and Openbox. + Before submitting a major PR, it makes sense to open an issue, and discuss the changes you're planning on. ### Code formatting diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/README.md new/nwg-launchers-0.3.3/README.md --- old/nwg-launchers-0.3.2/README.md 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/README.md 2020-09-08 10:28:55.000000000 +0200 @@ -241,6 +241,18 @@ for_window [title="~nwg"] border none ``` +## Openbox Note + +To start nwgdmenu from a key binding, use the `-run` argument, e.g.: + +```xml +<keybind key="W-D"> + <action name="Execute"> + <command>nwgdmenu -run</command> + </action> +</keybind> +``` + ## Tips & tricks ### Hide unwanted icons in nwggrid diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/bar/bar.cc new/nwg-launchers-0.3.3/bar/bar.cc --- old/nwg-launchers-0.3.2/bar/bar.cc 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/bar/bar.cc 2020-09-08 10:28:55.000000000 +0200 @@ -16,7 +16,6 @@ #include "on_event.h" #include "bar.h" -int image_size {72}; // button image size in pixels RGBA background = {0.0, 0.0, 0.0, 0.9}; std::string wm {""}; // detected or forced window manager name const char* const HELP_MESSAGE = @@ -44,27 +43,7 @@ gettimeofday(&tp, NULL); long int start_ms = tp.tv_sec * 1000 + tp.tv_usec / 1000; - pid_t pid = getpid(); - std::string mypid = std::to_string(pid); - - std::string pid_file = "/var/run/user/" + std::to_string(getuid()) + "/nwgbar.pid"; - - int saved_pid {}; - if (std::ifstream(pid_file)) { - try { - saved_pid = std::stoi(read_file_to_string(pid_file)); - if (kill(saved_pid, 0) != -1) { // found running instance! - kill(saved_pid, 9); - save_string_to_file(mypid, pid_file); - std::exit(0); - } - } catch (...) { - std::cerr << "\nError reading pid file\n\n"; - } - } - save_string_to_file(mypid, pid_file); - - std::string lang (""); + create_pid_file_or_kill_pid("nwgbar"); InputParser input(argc, argv); if(input.cmdOptionExists("-h")){ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/common/nwg_classes.cc new/nwg-launchers-0.3.3/common/nwg_classes.cc --- old/nwg-launchers-0.3.2/common/nwg_classes.cc 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/common/nwg_classes.cc 2020-09-08 10:28:55.000000000 +0200 @@ -72,15 +72,7 @@ std::cerr << "Your screen does not support alpha channels!\n"; } _SUPPORTS_ALPHA = (bool)visual; -} - -void CommonWindow::quit() { - auto toplevel = dynamic_cast<Gtk::Window*>(this->get_toplevel()); - if (!toplevel) { - std::cerr << "ERROR: Toplevel widget is not a window\n"; - std::exit(EXIT_FAILURE); - } - toplevel->get_application()->quit(); + gtk_widget_set_visual(GTK_WIDGET(gobj()), visual->gobj()); } AppBox::AppBox() { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/common/nwg_classes.h new/nwg-launchers-0.3.3/common/nwg_classes.h --- old/nwg-launchers-0.3.2/common/nwg_classes.h 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/common/nwg_classes.h 2020-09-08 10:28:55.000000000 +0200 @@ -39,8 +39,6 @@ virtual ~CommonWindow(); void check_screen(); - void quit(); - protected: bool on_draw(const ::Cairo::RefPtr< ::Cairo::Context>& cr) override; void on_screen_changed(const Glib::RefPtr<Gdk::Screen>& previous_screen) override; @@ -78,6 +76,9 @@ std::string icon; std::string comment; std::string mime_type; + // We need this field to mask unwanted .desktop entries by their copies in ./local/share/applications + // with the `NoDisplay=true` line added. + // See https://wiki.archlinux.org/index.php/desktop_entries#Hide_desktop_entries bool no_display {false}; }; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/common/nwg_tools.cc new/nwg-launchers-0.3.3/common/nwg_tools.cc --- old/nwg-launchers-0.3.2/common/nwg_tools.cc 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/common/nwg_tools.cc 2020-09-08 10:28:55.000000000 +0200 @@ -10,12 +10,21 @@ * */ #include <stdlib.h> +#include <unistd.h> +#include <stdlib.h> +#include <signal.h> #include <iostream> #include <fstream> #include "nwgconfig.h" -#include <nwg_tools.h> +#include "nwg_tools.h" + +// extern variables from nwg_tools.h +int image_size = 72; + +// stores the name of the pid_file, for use in atexit +static std::string pid_file{}; /* * Returns config dir @@ -45,20 +54,21 @@ * Returns window manager name * */ std::string detect_wm() { - /* Actually we only need to check if we're on sway or not, + /* Actually we only need to check if we're on sway, i3 or other WM, * but let's try to find a WM name if possible. If not, let it be just "other" */ - const char *env_var[2] = {"DESKTOP_SESSION", "SWAYSOCK"}; - char *env_val[2]; std::string wm_name{"other"}; - for(int i=0; i<2; i++) { + for(auto env : {"DESKTOP_SESSION", "SWAYSOCK", "I3SOCK"}) { // get environment values - env_val[i] = getenv(env_var[i]); - if (env_val[i] != NULL) { - std::string s(env_val[i]); + char *env_val = getenv(env); + if (env_val != NULL) { + std::string s(env_val); if (s.find("sway") != std::string::npos) { wm_name = "sway"; break; + } else if (s.find("i3") != std::string::npos) { + wm_name = "i3"; + break; } else { // is the value a full path or just a name? if (s.find("/") == std::string::npos) { @@ -99,6 +109,22 @@ return geo; } catch (...) { } + } else if (wm == "i3") { + try { + auto jsonString = get_output("i3-msg -t get_workspaces"); + auto jsonObj = string_to_json(jsonString); + for (auto&& entry : jsonObj) { + if (entry.at("focused")) { + auto&& rect = entry.at("rect"); + geo.x = rect.at("x"); + geo.y = rect.at("y"); + geo.width = rect.at("width"); + geo.height = rect.at("height"); + break; + } + } + } + catch (...) { } } // it's going to fail until the window is actually open @@ -146,20 +172,15 @@ * Returns current locale * */ std::string get_locale() { - std::string l {"en"}; - if (getenv("LANG") != NULL) { - char* env_val = getenv("LANG"); - std::string loc(env_val); - if (!loc.empty()) { - if (loc.find("_") != std::string::npos) { - int idx = loc.find_first_of("_"); - l = loc.substr(0, idx); - } else { - l = loc; - } + std::string loc = getenv("LANG"); + if (!loc.empty()) { + auto idx = loc.find_first_of("_"); + if (idx != std::string::npos) { + loc.resize(idx); } + return loc; } - return l; + return "en"; } /* @@ -231,7 +252,7 @@ /* * Sets RGBA background according to hex strings * */ -void set_background(const std::string_view string) { +void set_background(std::string_view string) { std::string hex_string {"0x"}; unsigned long int rgba; std::stringstream ss; @@ -253,11 +274,11 @@ background.blue = ((rgba >> 8) & 0xff) / 255.0; background.alpha = ((rgba) & 0xff) / 255.0; } else { - std::cerr << "ERROR: invalid color value. Should be RRGGBB or RRGGBBAA"; + std::cerr << "ERROR: invalid color value. Should be RRGGBB or RRGGBBAA\n"; } } catch (...) { - std::cerr << "Error parsing RGB(A) value \n"; + std::cerr << "Error parsing RGB(A) value\n"; } } @@ -277,3 +298,76 @@ } return result; } + + +/* + * Remove pid_file created by create_pid_file_or_kill_pid. + * This function will be run before exiting. + * */ +static void clean_pid_file(void) { + unlink(pid_file.c_str()); +} + +/* + * Signal handler to exit normally with SIGTERM + * */ +static void exit_normal(int sig) { + if (sig == SIGTERM) { + std::cerr << "Received SIGTERM, exiting...\n"; + } + + std::exit(1); +} + +/* + * Creates PID file for the new instance, + * or kills the other cmd instance. + * + * If it creates a PID file, it also sets up a signal handler to exit + * normally if it receives SIGTERM; and registers an atexit() action + * to run when exiting normally. + * + * This allows for behavior where using the shortcut to open one + * of the launchers closes the currently running one. + * */ +void create_pid_file_or_kill_pid(std::string cmd) { + std::string myuid = std::to_string(getuid()); + + char *runtime_dir_tmp = getenv("XDG_RUNTIME_DIR"); + std::string runtime_dir; + if (runtime_dir_tmp) { + runtime_dir = runtime_dir_tmp; + } else { + runtime_dir = "/var/run/user/" + myuid; + } + + pid_file = runtime_dir + "/" + cmd + ".pid"; + + auto pid_read = std::ifstream(pid_file); + // set to not throw exceptions + pid_read.exceptions(std::ifstream::goodbit); + if (pid_read.is_open()) { + // opening file worked - file exists + pid_t saved_pid; + pid_read >> saved_pid; + + if (saved_pid > 0 && kill(saved_pid, 0) == 0) { + // found running instance + // PID file will be deleted by process's atexit routine + int rv = kill(saved_pid, SIGTERM); + + // exit with status dependent on kill success + std::exit(rv == 0 ? 0 : 1); + } + } + + std::string mypid = std::to_string(getpid()); + save_string_to_file(mypid, pid_file); + + // register function to clean pid file + atexit(clean_pid_file); + // register signal handler for SIGTERM + struct sigaction act {}; + act.sa_handler = exit_normal; + sigaction(SIGTERM, &act, nullptr); +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/common/nwg_tools.h new/nwg-launchers-0.3.3/common/nwg_tools.h --- old/nwg-launchers-0.3.2/common/nwg_tools.h 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/common/nwg_tools.h 2020-09-08 10:28:55.000000000 +0200 @@ -21,10 +21,12 @@ #include <nlohmann/json.hpp> -#include <nwg_classes.h> +#include "nwg_classes.h" namespace ns = nlohmann; +extern int image_size; // button image size in pixels + std::string get_config_dir(std::string); std::string detect_wm(void); @@ -42,6 +44,7 @@ std::string get_output(const std::string&); -extern int image_size; -Gtk::Image* app_image(const Gtk::IconTheme& theme, const std::string& icon); +Gtk::Image* app_image(const Gtk::IconTheme&, const std::string&); Geometry display_geometry(const std::string&, Glib::RefPtr<Gdk::Display>, Glib::RefPtr<Gdk::Window>); + +void create_pid_file_or_kill_pid(std::string); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/dmenu/dmenu.cc new/nwg-launchers-0.3.3/dmenu/dmenu.cc --- old/nwg-launchers-0.3.2/dmenu/dmenu.cc 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/dmenu/dmenu.cc 2020-09-08 10:28:55.000000000 +0200 @@ -8,7 +8,6 @@ * */ #include <sys/time.h> -#include <unistd.h> #include <charconv> @@ -22,8 +21,6 @@ #define STR_EXPAND(x) #x #define STR(x) STR_EXPAND(x) -int image_size {72}; // make linker happy - std::string h_align {""}; // horizontal alignment std::string v_align {""}; // vertical alignment RGBA background = {0.0, 0.0, 0.0, 0.3}; @@ -67,25 +64,7 @@ case_sensitive = false; } - pid_t pid = getpid(); - std::string mypid = std::to_string(pid); - - std::string pid_file = "/var/run/user/" + std::to_string(getuid()) + "/nwgdmenu.pid"; - - int saved_pid {}; - if (std::ifstream(pid_file)) { - try { - saved_pid = std::stoi(read_file_to_string(pid_file)); - if (kill(saved_pid, 0) != -1) { // found running instance! - kill(saved_pid, 9); - save_string_to_file(mypid, pid_file); - std::exit(0); - } - } catch (...) { - std::cerr << "\nError reading pid file\n\n"; - } - } - save_string_to_file(mypid, pid_file); + create_pid_file_or_kill_pid("nwgdmenu"); InputParser input(argc, argv); if (input.cmdOptionExists("-h")){ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/grid/grid.cc new/nwg-launchers-0.3.3/grid/grid.cc --- old/nwg-launchers-0.3.2/grid/grid.cc 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/grid/grid.cc 2020-09-08 10:28:55.000000000 +0200 @@ -22,8 +22,6 @@ std::string wm {""}; // detected or forced window manager name int num_col = 6; // number of grid columns -int image_size = 72; // button image size in pixels -Gtk::Label *description; std::string pinned_file {}; std::vector<std::string> pinned; // list of commands of pinned icons @@ -53,25 +51,7 @@ gettimeofday(&tp, NULL); long int start_ms = tp.tv_sec * 1000 + tp.tv_usec / 1000; - pid_t pid = getpid(); - std::string mypid = std::to_string(pid); - - std::string pid_file = "/var/run/user/" + std::to_string(getuid()) + "/nwggrid.pid"; - - int saved_pid {}; - if (std::ifstream(pid_file)) { - try { - saved_pid = std::stoi(read_file_to_string(pid_file)); - if (kill(saved_pid, 0) != -1) { // found running instance! - kill(saved_pid, 9); - save_string_to_file(mypid, pid_file); - std::exit(0); - } - } catch (...) { - std::cerr << "\nError reading pid file\n\n"; - } - } - save_string_to_file(mypid, pid_file); + create_pid_file_or_kill_pid("nwggrid"); std::string lang (""); @@ -214,18 +194,24 @@ std::cout << "Locale: " << lang << "\n"; /* get all applications dirs */ - std::vector<std::string> app_dirs = get_app_dirs(); + auto app_dirs = get_app_dirs(); /* get a list of paths to all *.desktop entries */ - std::vector<std::string> entries = list_entries(app_dirs); + auto entries = list_entries(app_dirs); std::cout << entries.size() << " .desktop entries found, "; /* create the vector of DesktopEntry structs */ std::vector<DesktopEntry> desktop_entries {}; - int hidden {0}; + std::size_t hidden = 0; for (auto& entry_ : entries) { // string path -> DesktopEntry - auto entry = desktop_entry(std::move(entry_), lang); + auto maybe_entry = desktop_entry(std::move(entry_), lang); + // We need hidden desktop entries! + //if (!maybe_entry) { + // hidden++; + // continue; // the entry is NoDisplay, discard it and continue + //} + auto& entry = *maybe_entry; if (entry.no_display) { hidden++; } @@ -265,6 +251,7 @@ std::cerr << "ERROR: Failed to load icon theme\n"; } auto& icon_theme_ref = *icon_theme.get(); + icon_theme_ref.add_resource_path(DATA_DIR_STR "/icon-missing.svg"); if (std::filesystem::is_regular_file(css_file)) { provider->load_from_path(css_file); @@ -301,180 +288,71 @@ window.move(x, y); } - Gtk::Box outer_box(Gtk::ORIENTATION_VERTICAL); - outer_box.set_spacing(15); - - // hbox for searchbox - Gtk::HBox hbox_header; - - hbox_header.pack_start(window.searchbox, Gtk::PACK_EXPAND_PADDING); - - outer_box.pack_start(hbox_header, Gtk::PACK_SHRINK, Gtk::PACK_EXPAND_PADDING); - - Gtk::ScrolledWindow scrolled_window; - scrolled_window.set_propagate_natural_height(true); - scrolled_window.set_propagate_natural_width(true); - scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS); - - /* Create buttons for all desktop entries */ - /* @Siborgium: We can not std::move them here, it breaks favourites: (de.exec == entry.exec) is always false */ - for (auto& entry : desktop_entries) { - // Ignore .desktop entries with NoDisplay=true - if (!entry.no_display) { - if (std::find(pinned.begin(), pinned.end(), entry.exec) == pinned.end()) { - auto& ab = window.all_boxes.emplace_back(entry.name, - entry.exec, - entry.comment, - false); - Gtk::Image* image = app_image(icon_theme_ref, entry.icon); - ab.set_image_position(Gtk::POS_TOP); - ab.set_image(*image); - } + /* Create buttons for pinned entries */ + for (auto& pin : pinned) { + auto find = [&pin](auto& container) { + return std::find_if(container.begin(), container.end(), [&pin](auto& e) { + return pin == e.exec; + }); + }; + auto iter = find(desktop_entries); + if (iter != desktop_entries.end()) { + auto& entry = *iter; + auto found = find(favourites) != favourites.end(); + // 0 -> Common + // 1 -> Favorite + auto fav_tag = GridBox::FavTag{ found }; + auto& ab = window.emplace_box(std::move(entry.name), + std::move(entry.exec), + std::move(entry.comment), + fav_tag, + GridBox::Pinned); + Gtk::Image* image = app_image(icon_theme_ref, entry.icon); + ab.set_image_position(Gtk::POS_TOP); + ab.set_image(*image); } } - window.label_desc.set_text(std::to_string(window.all_boxes.size())); /* Create buttons for favourites */ - if (favs && favourites.size() > 0) { - for (auto& entry : favourites) { - for (auto& de : desktop_entries) { - if (de.exec == entry.exec && !de.no_display) { - - // avoid adding twice the same exec w/ another name - bool already_added {false}; - for (auto& app_box : window.fav_boxes) { - if (app_box.exec == de.exec) { - already_added = true; - break; - } - } - if (already_added) { - continue; - } - - auto& ab = window.fav_boxes.emplace_back(std::move(de.name), - std::move(de.exec), - std::move(de.comment), - false); - - Gtk::Image* image = app_image(icon_theme_ref, de.icon); - ab.set_image_position(Gtk::POS_TOP); - ab.set_image(*image); + for (auto& entry : favourites) { + for (auto& de : desktop_entries) { + if (entry.exec == de.exec) { + auto already_added = window.has_fav_with_exec(entry.exec); + if (already_added) { + continue; } - } - } - } - /* Create buttons for pinned entries */ - if (pins && pinned.size() > 0) { - for(auto& entry : desktop_entries) { - if (!entry.no_display) { - if (std::find(pinned.begin(), pinned.end(), entry.exec) != pinned.end()) { - auto& ab = window.pinned_boxes.emplace_back(std::move(entry.name), - std::move(entry.exec), - std::move(entry.comment), - true); - Gtk::Image* image = app_image(icon_theme_ref, entry.icon); - ab.set_image_position(Gtk::POS_TOP); - ab.set_image(*image); - } - } - } - } + auto& ab = window.emplace_box(std::move(de.name), + std::move(de.exec), + std::move(de.comment), + GridBox::Favorite, + GridBox::Unpinned); - int column = 0; - int row = 0; - if (pins && pinned.size() > 0) { - window.pinned_grid.freeze_child_notify(); - for (auto& box : window.pinned_boxes) { - window.pinned_grid.attach(box, column, row, 1, 1); - if (column < num_col - 1) { - column++; - } else { - column = 0; - row++; - } - } - window.pinned_grid.thaw_child_notify(); - } - - column = 0; - row = 0; - if (favs && favourites.size() > 0) { - window.favs_grid.freeze_child_notify(); - for (auto& box : window.fav_boxes) { - window.favs_grid.attach(box, column, row, 1, 1); - if (column < num_col - 1) { - column++; - } else { - column = 0; - row++; + Gtk::Image* image = app_image(icon_theme_ref, de.icon); + ab.set_image_position(Gtk::POS_TOP); + ab.set_image(*image); } } - window.favs_grid.thaw_child_notify(); } - column = 0; - row = 0; - for (auto& box : window.all_boxes) { - window.apps_grid.freeze_child_notify(); - window.apps_grid.attach(box, column, row, 1, 1); - if (column < num_col - 1) { - column++; - } else { - column = 0; - row++; + /* Create buttons for the rest of entries */ + for (auto& entry : desktop_entries) { + // if it's empty, it was probably moved from during the previous steps + // there should be some better way, but it works + if (!entry.exec.empty() && !entry.no_display) { + auto& ab = window.emplace_box(std::move(entry.name), + std::move(entry.exec), + std::move(entry.comment), + GridBox::Common, + GridBox::Unpinned); + Gtk::Image* image = app_image(icon_theme_ref, entry.icon); + ab.set_image_position(Gtk::POS_TOP); + ab.set_image(*image); } - window.apps_grid.thaw_child_notify(); - } - - Gtk::VBox inner_vbox; - - Gtk::HBox pinned_hbox; - pinned_hbox.pack_start(window.pinned_grid, true, false, 0); - inner_vbox.pack_start(pinned_hbox, false, false, 5); - if (pins && pinned.size() > 0) { - inner_vbox.pack_start(window.separator1, false, true, 0); } - Gtk::HBox favs_hbox; - favs_hbox.pack_start(window.favs_grid, true, false, 0); - inner_vbox.pack_start(favs_hbox, false, false, 5); - if (favs && favourites.size() > 0) { - inner_vbox.pack_start(window.separator, false, true, 0); - } - - Gtk::HBox apps_hbox; - apps_hbox.pack_start(window.apps_grid, Gtk::PACK_EXPAND_PADDING); - inner_vbox.pack_start(apps_hbox, true, true, 0); - - scrolled_window.add(inner_vbox); - - outer_box.pack_start(scrolled_window, Gtk::PACK_EXPAND_WIDGET); - scrolled_window.show_all_children(); - - outer_box.pack_start(window.label_desc, Gtk::PACK_SHRINK); - - window.add(outer_box); - window.show_all_children(); - // Set keyboard focus to the first visible button - if (favs && favourites.size() > 0) { - auto* first = window.favs_grid.get_child_at(0, 0); - if (first) { - first -> grab_focus(); - } - } else if (pins && pinned.size() > 0) { - auto* first = window.pinned_grid.get_child_at(0, 0); - if (first) { - first -> grab_focus(); - } - } else { - auto* first = window.apps_grid.get_child_at(0, 0); - if (first) { - first -> grab_focus(); - } - } + window.build_grids(); gettimeofday(&tp, NULL); long int end_ms = tp.tv_sec * 1000 + tp.tv_usec / 1000; diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/grid/grid.h new/nwg-launchers-0.3.3/grid/grid.h --- old/nwg-launchers-0.3.2/grid/grid.h 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/grid/grid.h 2020-09-08 10:28:55.000000000 +0200 @@ -6,14 +6,10 @@ * License: GPL3 * */ -#include <sys/stat.h> -#include <sys/types.h> -#include <sys/file.h> -#include <fcntl.h> - #include <iostream> #include <fstream> #include <filesystem> +#include <optional> #include <gtkmm.h> #include <glibmm/ustring.h> @@ -30,7 +26,6 @@ extern std::string wm; extern int num_col; -extern Gtk::Label *description; extern std::string pinned_file; extern std::vector<std::string> pinned; @@ -39,46 +34,85 @@ class GridBox : public AppBox { public: - /* name, exec, comment, pinned */ - GridBox(Glib::ustring, Glib::ustring, Glib::ustring, bool); + enum FavTag: bool { + Common = 0, + Favorite = 1, + }; + enum PinTag: bool { + Unpinned = 0, + Pinned = 1, + }; + + /* name, exec, comment, favorite, pinned */ + GridBox(Glib::ustring, Glib::ustring, Glib::ustring, FavTag, PinTag); bool on_button_press_event(GdkEventButton*) override; bool on_focus_in_event(GdkEventFocus*) override; void on_enter() override; void on_activate() override; - bool pinned; -}; -class GridSearch : public Gtk::SearchEntry { -public: - GridSearch(); - void prepare_to_insertion(); + FavTag favorite; + PinTag pinned; }; class MainWindow : public CommonWindow { public: MainWindow(); - GridSearch searchbox; // Search apps - Gtk::Label label_desc; // To display .desktop entry Comment field at the bottom - Gtk::Grid apps_grid; // All application buttons grid - Gtk::Grid favs_grid; // Favourites grid above - Gtk::Grid pinned_grid; // Pinned entries grid above - Gtk::Separator separator; // between favs and all apps - Gtk::Separator separator1; // below pinned - std::list<GridBox> all_boxes {}; // attached to apps_grid unfiltered view - std::list<GridBox*> filtered_boxes {}; // attached to apps_grid filtered view - std::list<GridBox> fav_boxes {}; // attached to favs_grid - std::list<GridBox> pinned_boxes {}; // attached to pinned_grid - - private: + Gtk::SearchEntry searchbox; // Search apps + Gtk::Label description; // To display .desktop entry Comment field at the bottom + Gtk::FlowBox apps_grid; // All application buttons grid + Gtk::FlowBox favs_grid; // Favourites grid above + Gtk::FlowBox pinned_grid; // Pinned entries grid above + Gtk::Separator separator; // between favs and all apps + Gtk::Separator separator1; // below pinned + Gtk::VBox outer_vbox; + Gtk::VBox inner_vbox; + Gtk::HBox hbox_header; + Gtk::HBox pinned_hbox; + Gtk::HBox favs_hbox; + Gtk::HBox apps_hbox; + Gtk::ScrolledWindow scrolled_window; + + template <typename ... Args> + GridBox& emplace_box(Args&& ... args); // emplace box + + bool has_fav_with_exec(const std::string&) const; + void build_grids(); + void toggle_pinned(GridBox& box); + void set_description(const Glib::ustring&); + protected: //Override default signal handler: - bool on_key_press_event(GdkEventKey* event) override; - bool on_button_press_event(GdkEventButton* event) override; + bool on_key_press_event(GdkEventKey*) override; + bool on_delete_event(GdkEventAny*) override; + bool on_button_press_event(GdkEventButton*) override; + private: + std::list<GridBox> all_boxes {}; // stores all applications buttons + std::vector<GridBox*> apps_boxes {}; // boxes attached to apps_grid + std::vector<GridBox*> filtered_boxes {}; // filtered boxes from + std::vector<GridBox*> fav_boxes {}; // attached to favs_grid + std::vector<GridBox*> pinned_boxes {}; // attached to pinned_grid + bool pins_changed = false; + bool is_filtered = false; + + void focus_first_box(); void filter_view(); - void rebuild_grid(bool filtered); + void refresh_separators(); }; +template <typename ... Args> +GridBox& MainWindow::emplace_box(Args&& ... args) { + auto& ab = this -> all_boxes.emplace_back(std::forward<Args>(args)...); + if (ab.pinned) { + pinned_boxes.push_back(&ab); + } else if (ab.favorite) { + fav_boxes.push_back(&ab); + } else { + apps_boxes.push_back(&ab); + } + return ab; +} + struct CacheEntry { std::string exec; int clicks; @@ -88,13 +122,11 @@ /* * Function declarations * */ -std::string get_cache_path(void); -std::string get_pinned_path(void); -void add_and_save_pinned(const std::string&); -void remove_and_save_pinned(const std::string&); -std::vector<std::string> get_app_dirs(void); -std::vector<std::string> list_entries(const std::vector<std::string>&); -DesktopEntry desktop_entry(std::string&&, const std::string&); -ns::json get_cache(const std::string&); -std::vector<std::string> get_pinned(const std::string&); -std::vector<CacheEntry> get_favourites(ns::json&&, int); +ns::json get_cache(const std::string&); +std::string get_cache_path(void); +std::string get_pinned_path(void); +std::vector<std::string> get_app_dirs(void); +std::vector<std::string> list_entries(const std::vector<std::string>&); +std::vector<std::string> get_pinned(const std::string&); +std::vector<CacheEntry> get_favourites(ns::json&&, int); +std::optional<DesktopEntry> desktop_entry(std::string&&, const std::string&); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/grid/grid_classes.cc new/nwg-launchers-0.3.3/grid/grid_classes.cc --- old/nwg-launchers-0.3.2/grid/grid_classes.cc 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/grid/grid_classes.cc 2020-09-08 10:28:55.000000000 +0200 @@ -17,22 +17,36 @@ #include "nwg_tools.h" #include "grid.h" -MainWindow::MainWindow(): CommonWindow("~nwggrid", "~nwggrid") { +int by_name(Gtk::FlowBoxChild* a_, Gtk::FlowBoxChild* b_) { + auto a = dynamic_cast<GridBox*>(a_->get_child()); + auto b = dynamic_cast<GridBox*>(b_->get_child()); + return a->name.compare(b->name); +} + +MainWindow::MainWindow() + : CommonWindow("~nwggrid", "~nwggrid") +{ searchbox .signal_search_changed() .connect(sigc::mem_fun(*this, &MainWindow::filter_view)); - apps_grid.set_column_spacing(5); - apps_grid.set_row_spacing(5); - apps_grid.set_column_homogeneous(true); - favs_grid.set_column_spacing(5); - favs_grid.set_row_spacing(5); - favs_grid.set_column_homogeneous(true); - pinned_grid.set_column_spacing(5); - pinned_grid.set_row_spacing(5); - pinned_grid.set_column_homogeneous(true); - label_desc.set_text(""); - label_desc.set_name("description"); - description = &label_desc; + searchbox.set_placeholder_text("Type to search"); + searchbox.set_sensitive(true); + searchbox.set_name("searchbox"); + + auto setup_grid = [](auto& grid) { + grid.set_column_spacing(5); + grid.set_row_spacing(5); + grid.set_homogeneous(true); + grid.set_halign(Gtk::ALIGN_CENTER); + grid.set_selection_mode(Gtk::SELECTION_NONE); + }; + setup_grid(apps_grid); + setup_grid(favs_grid); + setup_grid(pinned_grid); + apps_grid.set_sort_func(&by_name); + + description.set_text(""); + description.set_name("description"); separator.set_orientation(Gtk::ORIENTATION_HORIZONTAL); separator.set_name("separator"); separator1.set_orientation(Gtk::ORIENTATION_HORIZONTAL); @@ -47,12 +61,40 @@ set_type_hint(Gdk::WINDOW_TYPE_HINT_SPLASHSCREEN); set_decorated(false); } + outer_vbox.set_spacing(15); + hbox_header.pack_start(searchbox, Gtk::PACK_EXPAND_PADDING, 0); + outer_vbox.pack_start(hbox_header, Gtk::PACK_SHRINK, 0); + + scrolled_window.set_propagate_natural_height(true); + scrolled_window.set_propagate_natural_width(true); + scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS); + scrolled_window.add(inner_vbox); + + pinned_hbox.pack_start(pinned_grid, Gtk::PACK_EXPAND_WIDGET, 0); + inner_vbox.set_halign(Gtk::ALIGN_CENTER); + inner_vbox.pack_start(pinned_hbox, Gtk::PACK_SHRINK, 5); + inner_vbox.pack_start(separator1, false, true, 0); + + favs_hbox.pack_start(favs_grid, true, false, 0); + inner_vbox.pack_start(favs_hbox, false, false, 5); + inner_vbox.pack_start(separator, false, true, 0); + + apps_hbox.pack_start(apps_grid, Gtk::PACK_EXPAND_PADDING); + inner_vbox.pack_start(apps_hbox, Gtk::PACK_SHRINK); + + outer_vbox.pack_start(scrolled_window, Gtk::PACK_EXPAND_WIDGET); + scrolled_window.show_all_children(); + + outer_vbox.pack_start(description, Gtk::PACK_SHRINK); + + this -> add(outer_vbox); + this -> show_all_children(); } bool MainWindow::on_button_press_event(GdkEventButton *event) { (void) event; // suppress warning - this->quit(); + this->close(); return true; } @@ -60,7 +102,7 @@ auto key_val = key_event -> keyval; switch (key_val) { case GDK_KEY_Escape: - this->quit(); + this->close(); break; case GDK_KEY_Delete: this -> searchbox.set_text(""); @@ -81,92 +123,190 @@ // its contents become selected // and the incoming character will overwrite them // so we make sure to drop the selection - this -> searchbox.prepare_to_insertion(); + this -> searchbox.select_region(0, 0); + this -> searchbox.set_position(-1); + } } // Delegate handling to the base class return Gtk::Window::on_key_press_event(key_event); } +/* + * In order to keep Gtk FlowBox content properly haligned, we have to maintain + * `max_children_per_line` equal to the total number of children + * */ +inline auto refresh_max_children_per_line = [](auto& flowbox, auto& container) { + auto size = container.size(); + decltype(size) cols = num_col; + if (auto min = std::min(cols, size)) { + flowbox.set_min_children_per_line(std::min(size, cols)); + flowbox.set_max_children_per_line(std::min(size, cols)); + } +}; +/* + * Populate grid with widgets from container + * */ +inline auto build_grid = [](auto& grid, auto& container) { + for (auto child : container) { + grid.add(*child); + child->get_parent()->set_can_focus(false); // FlowBoxChild shouldn't consume focus + } + refresh_max_children_per_line(grid, container); +}; + void MainWindow::filter_view() { + auto clean_grid = [](auto& grid) { + grid.foreach([&grid](auto& child) { + grid.remove(child); + dynamic_cast<Gtk::FlowBoxChild*>(&child)->remove(); + }); + }; auto search_phrase = searchbox.get_text(); - if (search_phrase.size() > 0) { - this -> filtered_boxes.clear(); - + is_filtered = search_phrase.size() > 0; + filtered_boxes.clear(); + apps_grid.freeze_child_notify(); + if (is_filtered) { auto phrase = search_phrase.casefold(); - for (auto& box : this -> all_boxes) { - if (box.name.casefold().find(phrase) != Glib::ustring::npos || - box.exec.casefold().find(phrase) != Glib::ustring::npos || - box.comment.casefold().find(phrase) != Glib::ustring::npos) - { - this -> filtered_boxes.emplace_back(&box); + auto matches = [&phrase](auto& str) { + return str.casefold().find(phrase) != Glib::ustring::npos; + }; + for (auto* box : apps_boxes) { + if (matches(box->name) || matches(box->exec) || matches(box->comment)) { + filtered_boxes.push_back(box); } } - this -> favs_grid.hide(); - this -> separator.hide(); - this -> rebuild_grid(true); + clean_grid(apps_grid); + build_grid(apps_grid, filtered_boxes); } else { - this -> favs_grid.show(); - this -> separator.show(); - this -> rebuild_grid(false); + clean_grid(apps_grid); + build_grid(apps_grid, apps_boxes); } - // set focus to the first icon search results - auto* first = this -> apps_grid.get_child_at(0, 0); - if (first) { - first -> grab_focus(); + this -> refresh_separators(); + this -> focus_first_box(); + apps_grid.thaw_child_notify(); +} + +void MainWindow::refresh_separators() { + auto set_shown = [](auto c, auto& s) { if (c) s.show(); else s.hide(); }; + auto p = !pinned_boxes.empty(); + auto f = !fav_boxes.empty(); + auto a1 = !filtered_boxes.empty() && is_filtered; + auto a2 = !apps_boxes.empty() && !is_filtered; + auto a = a1 || a2; + set_shown(p && f, separator1); + set_shown(f && a, separator); + if (p && a) { + separator.show(); } } -void MainWindow::rebuild_grid(bool filtered) { - int column = 0; - int row = 0; - +void MainWindow::build_grids() { + this -> pinned_grid.freeze_child_notify(); + this -> favs_grid.freeze_child_notify(); this -> apps_grid.freeze_child_notify(); - for (Gtk::Widget *widget : this -> apps_grid.get_children()) { - this -> apps_grid.remove(*widget); - widget -> unset_state_flags(Gtk::STATE_FLAG_PRELIGHT); - } - int cnt = 0; - if (filtered) { - for (auto& box : this -> filtered_boxes) { - this -> apps_grid.attach(*box, column, row, 1, 1); - if (column < num_col - 1) { - column++; - } else { - column = 0; - row++; - } - cnt++; - } - // Set keyboard focus to the first visible button - if (this -> fav_boxes.size() > 0) { - fav_boxes.front().grab_focus(); + + build_grid(this->pinned_grid, this->pinned_boxes); + build_grid(this->favs_grid, this->fav_boxes); + build_grid(this->apps_grid, this->apps_boxes); + + this -> pinned_grid.show_all_children(); + this -> favs_grid.show_all_children(); + this -> apps_grid.show_all_children(); + + this -> pinned_grid.thaw_child_notify(); + this -> favs_grid.thaw_child_notify(); + this -> apps_grid.thaw_child_notify(); + + this -> focus_first_box(); + this -> refresh_separators(); +} + +void MainWindow::focus_first_box() { + std::array containers{ &filtered_boxes, &pinned_boxes, &fav_boxes, &apps_boxes }; + for (auto container : containers) { + if (container->size() > 0) { + container->front()->grab_focus(); + return; } + } +} + +void MainWindow::set_description(const Glib::ustring& text) { + this->description.set_text(text); +} + +void MainWindow::toggle_pinned(GridBox& box) { + // pins changed, we'll need to update the cache + this->pins_changed = true; + + // disable prelight + box.unset_state_flags(Gtk::STATE_FLAG_PRELIGHT); + + auto is_pinned = box.pinned == GridBox::Pinned; + box.pinned = GridBox::PinTag{ !is_pinned }; + + auto* from_grid = &this->apps_grid; + auto* from = &this->apps_boxes; + + if (box.favorite) { + from_grid = &this->favs_grid; + from = &this->fav_boxes; + } + + auto* to = &this->pinned_boxes; + auto* to_grid = &this->pinned_grid; + if (is_pinned) { + std::swap(from, to); + std::swap(from_grid, to_grid); + } + auto to_remove = std::remove(from->begin(), from->end(), &box);; + from->erase(to_remove); + to->push_back(&box); + refresh_max_children_per_line(*from_grid, *from); + refresh_max_children_per_line(*to_grid, *to); + + // get_parent is important + // FlowBox { ... FlowBoxChild { box } ... } + // box is not child to FlowBox + // but its parent, FlowBoxChild is + // so we need to reparent FlowBoxChild, not the box itself + box.get_parent()->reparent(*to_grid); + // refresh filter if needed + if (is_filtered) { + this->filter_view(); } else { - for (auto& box : this -> all_boxes) { - this -> apps_grid.attach(box, column, row, 1, 1); - if (column < num_col - 1) { - column++; - } else { - column = 0; - row++; - } - cnt++; - } - // Set keyboard focus to the first visible button - if (this -> all_boxes.size() > 0) { - all_boxes.front().grab_focus(); + this->refresh_separators(); + } +} + +/* + * Saves pinned cache file + * */ +bool MainWindow::on_delete_event(GdkEventAny* event) { + if (this->pins_changed) { + std::ofstream out_file(pinned_file); + for (auto pin : this->pinned_boxes) { + out_file << pin->exec << '\n'; } } - this -> apps_grid.thaw_child_notify(); + return CommonWindow::on_delete_event(event); } -GridBox::GridBox(Glib::ustring name, Glib::ustring exec, Glib::ustring comment, bool pinned) - : AppBox(std::move(name), std::move(exec), std::move(comment)), pinned(pinned) {} +bool MainWindow::has_fav_with_exec(const std::string& exec) const { + auto pred = [&exec](auto* b) { return exec == b->exec; }; + return std::find_if(fav_boxes.begin(), fav_boxes.end(), pred) != fav_boxes.end(); +} + +// This constructor is not needed since C++20 +GridBox::GridBox(Glib::ustring name, Glib::ustring exec, Glib::ustring comment, GridBox::FavTag fav, GridBox::PinTag pinned) + : AppBox(std::move(name), std::move(exec), std::move(comment)), favorite(fav), pinned(pinned) {} bool GridBox::on_button_press_event(GdkEventButton* event) { - std::cout << event -> button << "\n"; - if (event -> button == 1) { + if (pins && event->button == 3) { + auto toplevel = dynamic_cast<MainWindow*>(this -> get_toplevel()); + toplevel->toggle_pinned(*this); + } else { int clicks = 0; try { clicks = cache[exec]; @@ -175,45 +315,30 @@ clicks = 1; } cache[exec] = clicks; - save_json(cache, cache_file); - this -> activate(); + save_json(cache, cache_file); - } else if (pins && event -> button == 3) { - if (pinned) { - remove_and_save_pinned(exec); - } else { - add_and_save_pinned(exec); - } + this -> activate(); } - return false; + return AppBox::on_button_press_event(event); } bool GridBox::on_focus_in_event(GdkEventFocus* event) { (void) event; // suppress warning - description -> set_text(comment); + auto toplevel = dynamic_cast<MainWindow*>(this->get_toplevel()); + toplevel->set_description(comment); return true; } void GridBox::on_enter() { - description -> set_text(comment); + auto toplevel = dynamic_cast<MainWindow*>(this->get_toplevel()); + toplevel->set_description(comment); return AppBox::on_enter(); } void GridBox::on_activate() { - exec.append(" &"); - std::system(exec.data()); + auto cmd = exec + " &"; + std::system(cmd.data()); auto toplevel = dynamic_cast<MainWindow*>(this->get_toplevel()); - toplevel->quit(); -} - -GridSearch::GridSearch() { - set_placeholder_text("Type to search"); - set_sensitive(true); - set_name("searchbox"); -} - -void GridSearch::prepare_to_insertion() { - select_region(0, 0); - set_position(-1); + toplevel->close(); } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/grid/grid_tools.cc new/nwg-launchers-0.3.3/grid/grid_tools.cc --- old/nwg-launchers-0.3.2/grid/grid_tools.cc 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/grid/grid_tools.cc 2020-09-08 10:28:55.000000000 +0200 @@ -8,6 +8,7 @@ #include <filesystem> #include <string_view> +#include <variant> #include "nwg_tools.h" #include "grid.h" @@ -55,56 +56,56 @@ } /* - * Adds pinned entry and saves pinned cache file + * Returns locations of .desktop files * */ -void add_and_save_pinned(const std::string& command) { - // Add if not yet pinned - if (std::find(pinned.begin(), pinned.end(), command) == pinned.end()) { - pinned.push_back(command); - std::ofstream out_file(pinned_file); - for (const auto &e : pinned) out_file << e << "\n"; - } -} +std::vector<std::string> get_app_dirs() { + std::vector<std::string> result; -/* - * Removes pinned entry and saves pinned cache file - * */ -void remove_and_save_pinned(const std::string& command) { - // Find the item index - bool found = false; - std::size_t idx; - for (std::size_t i = 0; i < pinned.size(); i++) { - if (pinned[i] == command) { - found = true; - idx = i; - break; + result.reserve(8); + auto append = [](auto&& str, auto&& suf) { + if (str.back() != '/') { + str.push_back('/'); + } + str.append(suf); + }; + + std::string home = getenv("HOME"); + + auto xdg_data_home = getenv("XDG_DATA_HOME"); + if (xdg_data_home) { + auto dirs = split_string(xdg_data_home, ":"); + for (auto& dir : dirs) { + auto& s = result.emplace_back(dir); + append(s, "applications"); } - } - - if (found) { - pinned.erase(pinned.begin() + idx); - std::ofstream out_file(pinned_file); - for (const auto &e : pinned) { - out_file << e << "\n"; + } else { + if (!home.empty()) { + auto& s = result.emplace_back(home); + append(s, ".local/share/applications"); } } -} + + const char* xdg_data_dirs = getenv("XDG_DATA_DIRS"); + if (!xdg_data_dirs) { + xdg_data_dirs = "/usr/local/share/:/usr/share/"; + } + auto dirs = split_string(xdg_data_dirs, ":"); + for (auto& dir : dirs) { + auto& s = result.emplace_back(dir); + append(s, "applications"); + } -/* - * Returns locations of .desktop files - * */ -std::vector<std::string> get_app_dirs() { - std::string homedir = getenv("HOME"); - std::vector<std::string> result = {homedir + "/.local/share/applications", "/usr/share/applications", - "/usr/local/share/applications"}; - - auto xdg_data_dirs = getenv("XDG_DATA_DIRS"); - if (xdg_data_dirs != NULL) { - auto dirs = split_string(xdg_data_dirs, ":"); - for (auto& dir : dirs) { - result.emplace_back(dir); + // Add flatpak dirs if not found in XDG_DATA_DIRS + std::string flatpak_data_dirs[] = { + home + "/.local/share/flatpak/exports/share/applications", + "/var/lib/flatpak/exports/share/applications" + }; + for (auto& fp_dir : flatpak_data_dirs) { + if (std::find(result.begin(), result.end(), fp_dir) == result.end()) { + result.emplace_back(fp_dir); } } + return result; } @@ -125,94 +126,100 @@ return desktop_paths; } +// desktop_entry helpers +template<typename> inline constexpr bool lazy_false_v = false; +template<typename ... Ts> struct visitor : Ts... { using Ts::operator()...; }; +template<typename ... Ts> visitor(Ts...) -> visitor<Ts...>; + /* * Parses .desktop file to DesktopEntry struct * */ -DesktopEntry desktop_entry(std::string&& path, const std::string& lang) { +std::optional<DesktopEntry> desktop_entry(std::string&& path, const std::string& lang) { + using namespace std::literals::string_view_literals; + DesktopEntry entry; std::ifstream file(path); std::string str; - std::string name {}; // Name= std::string name_ln {}; // localized: Name[ln]= std::string loc_name = "Name[" + lang + "]="; - std::string comment {}; // Comment= std::string comment_ln {}; // localized: Comment[ln]= std::string loc_comment = "Comment[" + lang + "]="; + struct nop_t { } nop; + struct cut_t { } cut; + struct Match { + std::string_view prefix; + std::string* dest; + std::variant<nop_t, cut_t> tag; + }; + struct Result { + bool found; + size_t index; + }; + Match matches[] = { + { "Name="sv, &entry.name, nop }, + { loc_name, &name_ln, nop }, + { "Exec="sv, &entry.exec, cut }, + { "Icon="sv, &entry.icon, nop }, + { "Comment="sv, &entry.comment, nop }, + { loc_comment, &comment_ln, nop }, + { "MimeType="sv, &entry.mime_type, nop }, + }; + + // Skip everything not related + constexpr auto header = "[Desktop Entry]"sv; while (std::getline(file, str)) { - auto view = std::string_view(str.data(), str.size()); - bool read_me = true; - if (view.find("[") == 0) { - read_me = (view.find("[Desktop Entry") != std::string_view::npos); - if (!read_me) { - break; - } else { - continue; - } + str.resize(header.size()); + if (str == header) { + break; } - if (read_me) { - // This is to resolve `Respect the NoDisplay setting in .desktop files #84`, - // see https://wiki.archlinux.org/index.php/desktop_entries#Hide_desktop_entries. - // The ~/.local/share/applications folder is going to be read first. Entries created from here won't be - // overwritten from e.g. /usr/share/applications, as duplicates are being skipped. - if (view.find("NoDisplay=true") == 0) { - entry.no_display = true; - } - - if (view.find(loc_name) == 0) { - if (auto idx = view.find_first_of("="); idx != std::string_view::npos) { - name_ln = view.substr(idx + 1); - } - } - if (view.find("Name=") == 0) { - if (auto idx = view.find_first_of("="); idx != std::string_view::npos) { - name = view.substr(idx + 1); - } - } - if (view.find("Exec=") == 0) { - if (auto idx = view.find_first_of("="); idx != std::string_view::npos) { - auto val = view.substr(idx + 1); - // strip ' %' and following - if (auto idx = val.find_first_of("%"); idx != std::string_view::npos) { - val = val.substr(0, idx - 1); + } + // Repeat until the next section + constexpr auto nodisplay = "NoDisplay=true"sv; + while (std::getline(file, str)) { + if (str[0] == '[') { // new section begins, break + break; + } + auto view = std::string_view{str}; + auto view_len = std::size(view); + auto try_strip_prefix = [&view, view_len](auto& prefix) { + auto len = std::min(view_len, std::size(prefix)); + return Result { + prefix == view.substr(0, len), + len + }; + }; + if (view == nodisplay) { + // @Siborgium: return std::nullopt won't do the job, as we DO NEED this object. + // See https://wiki.archlinux.org/index.php/desktop_entries#Hide_desktop_entries + entry.no_display = true; + } + for (auto& [prefix, dest, tag] : matches) { + auto [ok, pos] = try_strip_prefix(prefix); + if (ok) { + std::visit(visitor { + [dest, pos, &view](nop_t) { *dest = view.substr(pos); }, + [dest, pos, &view](cut_t) { + auto idx = view.find(" %", pos); + if (idx == std::string_view::npos) { + idx = std::size(view); + } + *dest = view.substr(pos, idx - pos); } - entry.exec = std::move(val); - } - } - if (view.find("Icon=") == 0) { - if (auto idx = view.find_first_of("="); idx != std::string_view::npos) { - entry.icon = view.substr(idx + 1); - } - } - if (view.find("Comment=") == 0) { - if (auto idx = view.find_first_of("="); idx != std::string_view::npos) { - comment = view.substr(idx + 1); - } - } - if (view.find(loc_comment) == 0) { - if (auto idx = view.find_first_of("="); idx != std::string_view::npos) { - comment_ln = view.substr(idx + 1); - } - } - if (view.find("MimeType=") == 0) { - if (auto idx = view.find_first_of("="); idx != std::string_view::npos) { - entry.mime_type = view.substr(idx + 1); - } + }, + tag); + break; } } } - if (name_ln.empty()) { - entry.name = std::move(name); - } else { + + if (!name_ln.empty()) { entry.name = std::move(name_ln); } - - if (comment_ln.empty()) { - entry.comment = std::move(comment); - } else { + if (!comment_ln.empty()) { entry.comment = std::move(comment_ln); } return entry; @@ -232,7 +239,7 @@ * */ std::vector<std::string> get_pinned(const std::string& pinned_file) { std::vector<std::string> lines; - std::ifstream in(pinned_file.c_str()); + std::ifstream in(pinned_file); if(!in) { std::cerr << "Could not find " << pinned_file << ", creating!" << std::endl; save_string_to_file("", pinned_file); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/nwg-launchers-0.3.2/meson.build new/nwg-launchers-0.3.3/meson.build --- old/nwg-launchers-0.3.2/meson.build 2020-08-26 00:36:02.000000000 +0200 +++ new/nwg-launchers-0.3.3/meson.build 2020-09-08 10:28:55.000000000 +0200 @@ -4,7 +4,7 @@ 'warning_level=3' ], license: 'GPL-3.0-or-later', - version: '0.3.2' + version: '0.3.3' ) compiler = meson.get_compiler('cpp')
