commit 0975feac72d28558cf49a47385c10e790e982d73
Author: Wojciech Madry <[email protected]>
Date:   Fri Jan 17 18:47:56 2025 +0100

    [dmenu][patches][sort_by_popularity]
    
    Added patch for dmenu.
    sort_by_popularity sorts items by popularity

diff --git 
a/tools.suckless.org/dmenu/patches/sort_by_popularity/dmenu-sort_by_popularity-20250117-86f0b51.diff
 
b/tools.suckless.org/dmenu/patches/sort_by_popularity/dmenu-sort_by_popularity-20250117-86f0b51.diff
new file mode 100644
index 00000000..a1d0224e
--- /dev/null
+++ 
b/tools.suckless.org/dmenu/patches/sort_by_popularity/dmenu-sort_by_popularity-20250117-86f0b51.diff
@@ -0,0 +1,280 @@
+From e2cbe709046b733d2a770beb315ef5511abe9a19 Mon Sep 17 00:00:00 2001
+From: Wojciech Madry <[email protected]>
+Date: Fri, 17 Jan 2025 17:11:48 +0100
+Subject: [PATCH] Sort matches by popularity
+
+Patch will sort all matched instances by popularity.
+Each time you open any program, the popularity is increased by 1.
+The popularity is stored in the .cache folder.
+---
+ dmenu.c | 186 +++++++++++++++++++++++++++++++++++++++++++++++++-------
+ 1 file changed, 165 insertions(+), 21 deletions(-)
+
+diff --git a/dmenu.c b/dmenu.c
+index 804da64..cb26068 100644
+--- a/dmenu.c
++++ b/dmenu.c
+@@ -35,11 +35,13 @@ struct item {
+ 
+ static char text[BUFSIZ] = "";
+ static char *embed;
++static char *popcache = NULL;
+ static int bh, mw, mh;
+ static int inputw = 0, promptw;
+ static int lrpad; /* sum of left and right padding */
+ static size_t cursor;
+ static struct item *items = NULL;
++static struct item *popitems = NULL;
+ static struct item *matches, *matchend;
+ static struct item *prev, *curr, *next, *sel;
+ static int mon = -1, screen;
+@@ -105,7 +107,13 @@ cleanup(void)
+               free(scheme[i]);
+       for (i = 0; items && items[i].text; ++i)
+               free(items[i].text);
++      for (i = 0; popitems && popitems[i].text; ++i)
++              free(popitems[i].text);
+       free(items);
++      free(popitems);
++      if(popcache != NULL)
++              free(popcache);
++
+       drw_free(drw);
+       XSync(dpy, False);
+       XCloseDisplay(dpy);
+@@ -226,6 +234,57 @@ grabkeyboard(void)
+       die("cannot grab keyboard");
+ }
+ 
++static void
++sortitemsbypop(struct item* first, struct item* last)
++{
++      struct item* item = NULL;
++      struct item* pop = NULL;
++      size_t idx = 0;
++      for (pop = popitems; pop && pop->text; ++pop) {
++              for (item = first; item && item->text && (item <= last || last 
== NULL); item++) {
++                      if(strcmp(item->text, pop->text) == 0) {
++                              char* lhs = first[idx].text;
++                              first[idx].text = item->text;
++                              item->text = lhs;
++                              ++idx;
++                              break;
++                      }
++              }
++      }
++}
++
++static void
++incpop(struct item* sel) {
++      if(!(sel && sel->text))
++              return;
++      struct item* pop = NULL;
++      int found = 0;
++      FILE *out;
++      out = fopen(popcache, "w");
++      if (out == NULL) {
++              printf("Cannot open file '%s'", popcache);
++              return;
++      }
++      char decimal[16] = {'++ for (pop = popitems; pop && pop->text; ++pop) {
++              if(found == 0 && strcmp(pop->text, sel->text) == 0) {
++                      pop->out += 1;
++                      found = 1;
++              }
++              fputs(pop->text, out);
++              fputs(" ", out);
++              sprintf(decimal, "%i", MIN(pop->out, 999));
++              fputs(decimal, out);
++              fputs("
", out);
++      }
++      if(found == 0) {
++              fputs(sel->text, out);
++              fputs(" 1", out);
++              fputs("
", out);
++      }
++      fclose(out);
++}
++
+ static void
+ match(void)
+ {
+@@ -234,17 +293,16 @@ match(void)
+ 
+       char buf[sizeof text], *s;
+       int i, tokc = 0;
+-      size_t len, textsize;
+-      struct item *item, *lprefix, *lsubstr, *prefixend, *substrend;
++      size_t textsize;
++      struct item *item, *others, *othersend;
+ 
+       strcpy(buf, text);
+       /* separate input text into tokens to be matched individually */
+       for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " "))
+               if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof 
*tokv)))
+                       die("cannot realloc %zu bytes:", tokn * sizeof *tokv);
+-      len = tokc ? strlen(tokv[0]) : 0;
+ 
+-      matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL;
++      matches = others = matchend = othersend = NULL;
+       textsize = strlen(text) + 1;
+       for (item = items; item && item->text; item++) {
+               for (i = 0; i < tokc; i++)
+@@ -252,29 +310,20 @@ match(void)
+                               break;
+               if (i != tokc) /* not all tokens match */
+                       continue;
+-              /* exact matches go first, then prefixes, then substrings */
++              /* exact matches go first, then others */
+               if (!tokc || !fstrncmp(text, item->text, textsize))
+                       appenditem(item, &matches, &matchend);
+-              else if (!fstrncmp(tokv[0], item->text, len))
+-                      appenditem(item, &lprefix, &prefixend);
+               else
+-                      appenditem(item, &lsubstr, &substrend);
+-      }
+-      if (lprefix) {
+-              if (matches) {
+-                      matchend->right = lprefix;
+-                      lprefix->left = matchend;
+-              } else
+-                      matches = lprefix;
+-              matchend = prefixend;
++                      appenditem(item, &others, &othersend);
+       }
+-      if (lsubstr) {
++      if (others) {
++              sortitemsbypop(others, othersend);
+               if (matches) {
+-                      matchend->right = lsubstr;
+-                      lsubstr->left = matchend;
++                      matchend->right = others;
++                      others->left = matchend;
+               } else
+-                      matches = lsubstr;
+-              matchend = substrend;
++                      matches = others;
++              matchend = othersend;
+       }
+       curr = sel = matches;
+       calcoffsets();
+@@ -489,6 +538,7 @@ insert:
+               break;
+       case XK_Return:
+       case XK_KP_Enter:
++              incpop(sel);
+               puts((sel && !(ev->state & ShiftMask)) ? sel->text : text);
+               if (!(ev->state & ControlMask)) {
+                       cleanup();
+@@ -711,6 +761,98 @@ setup(void)
+       drawmenu();
+ }
+ 
++static void
++itemize(struct item* item, const char* line, const ssize_t size)
++{
++      const size_t UNDEF = size + 1;
++      size_t firstchar = UNDEF, lastchar = UNDEF;
++      size_t firstnum = UNDEF;
++      size_t i;
++      int afterspace = 0;
++      for (i = 0 ; i < size ; ++i) {
++              const char c = line[i];
++              if (c == ' ') {
++                      if (firstchar != UNDEF)
++                              afterspace = 1;
++                      continue;
++              }
++              if (afterspace == 1 && (c >= '0' && c <= '9')) {
++                      firstnum = i;
++                      break;
++              }
++              if (firstchar == UNDEF)
++                      firstchar = i;
++              lastchar = i;
++      }
++      size_t len = lastchar - firstchar + 2;
++      item->text = (char*)malloc(sizeof(char) * len);
++      memcpy(item->text, line + firstchar, len);
++      item->text[len - 1] = '++
++      item->out = 0;
++      if (firstnum != UNDEF)
++              item->out = atoi(line + firstnum);
++}
++
++static int
++compareitembyoutrev(const void* lhs, const void* rhs)
++{
++      return ((struct item*)rhs)->out - ((struct item*)lhs)->out;
++}
++
++static void
++loadpopitems(void)
++{
++      const char* xdg_cache_home = getenv("XDG_CACHE_HOME");
++      const char* home = getenv("HOME");
++      char* cache = NULL;
++      const char* CACHE_FILENAME = "/dmenu_pop.txt";
++      if(xdg_cache_home != NULL) {
++              size_t xdglen = strlen(xdg_cache_home);
++              cache = (char*)malloc(xdglen + 1);
++              cache[xdglen] = '++             strcpy(cache, xdg_cache_home);
++      } else {
++              const char* cachefolder = "/.cache";
++              size_t hclen = strlen(home) + strlen(cachefolder) + 1;
++              cache = (char*)malloc(hclen + 1);
++              cache[hclen - 1] = '++          strcpy(cache, home);
++              strcpy(cache + strlen(home), cachefolder);
++      }
++      const size_t cache_size = strlen(cache) + strlen(CACHE_FILENAME) + 1;
++      popcache = (char*)malloc(sizeof(char) * cache_size);
++      popcache[cache_size - 1] = '++  strcpy(popcache, cache);
++      strcpy(popcache + strlen(cache), CACHE_FILENAME);
++      free(cache);
++
++      FILE * fp;
++      char * line = NULL;
++      size_t i, itemsiz = 0, linesiz = 0;
++      ssize_t len;
++
++      fp = fopen(popcache, "r");
++      if (fp == NULL)
++              return;
++
++      for (i = 0; (len = getline(&line, &linesiz, fp)) != -1; i++) {
++              if (i + 1 >= itemsiz) {
++                      itemsiz += 256;
++                      if (!(popitems = realloc(popitems, itemsiz * 
sizeof(*popitems))))
++                              die("cannot realloc %zu bytes:", itemsiz * 
sizeof(*popitems));
++              }
++              itemize((struct item*)&popitems[i], line, len);
++      }
++      fclose(fp);
++      if (line)
++              free(line);
++
++      if (popitems)
++              popitems[i].text = NULL;
++      qsort(popitems, i, sizeof(struct item), compareitembyoutrev);
++}
++
+ static void
+ usage(void)
+ {
+@@ -788,6 +930,8 @@ main(int argc, char *argv[])
+               readstdin();
+               grabkeyboard();
+       }
++      loadpopitems();
++      sortitemsbypop(items, NULL);
+       setup();
+       run();
+ 
+-- 
+2.48.1
+
diff --git a/tools.suckless.org/dmenu/patches/sort_by_popularity/index.md 
b/tools.suckless.org/dmenu/patches/sort_by_popularity/index.md
new file mode 100644
index 00000000..435497a7
--- /dev/null
+++ b/tools.suckless.org/dmenu/patches/sort_by_popularity/index.md
@@ -0,0 +1,42 @@
+sort_by_popularity
+=============
+
+The list of programs is sorted by popularity.
+
+Each time you run the program, its popularity will increase by 1.
+
+The popularity cache file is stored in: `{CACHE_PATH}/dmenu_pop.txt`.
+
+Requirements
+------------
+
+* One of the following system env **shall** be set `XDG_CACHE_HOME` or `HOME`
+* Folder `$XDG_CACHE_HOME` or `$HOME/.cache` **shall** exist
+
+How it works
+------------
+
+The order of programs is determined by popularity.
+
+Programs with higher popularity come first.
+
+The order of programs with the same popularity depends on their position in 
the cache file.
+
+Let's assume that we have the following programs: `A`, `B1`, `B2`, `C`
+
+`[User input] -> [dmenu output] -> [User's choice] -> {PROGRAM: POPULARITY}`
+
+1. ` ` -> `A`, `B1`, `B2`, `C` -> `C` -> `{}`
+2. ` ` -> `C`, `B1`, `B2`, `A` -> `B2` -> `{C: 1}`
+3. ` ` -> `C`, `B2`, `B1`, `A` -> ` ` -> `{C: 1, B2: 1}`
+4. `B` -> `B2`, `B1` -> `B1` -> `{C: 1, B2: 1}`
+5. `B` -> `B2`, `B1` -> `B1` -> `{C: 1, B2: 1, B1: 1}`
+6. `B` -> `B1`, `B2` -> ` ` -> `{C: 1, B2: 1, B1: 2}`
+
+Download
+--------
+* 
[dmenu-sort_by_popularity-20250117-86f0b51.diff](dmenu-sort_by_popularity-20250117-86f0b51.diff)
+
+Author
+------
+* Wojciech Madry - <[email protected]>


Reply via email to