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')


Reply via email to