dmenu-sort_by_popularity-20250117-86f0b51.diff (7298B)
1 From e2cbe709046b733d2a770beb315ef5511abe9a19 Mon Sep 17 00:00:00 2001 2 From: Wojciech Madry <madrywojciech99@gmail.com> 3 Date: Fri, 17 Jan 2025 17:11:48 +0100 4 Subject: [PATCH] Sort matches by popularity 5 6 Patch will sort all matched instances by popularity. 7 Each time you open any program, the popularity is increased by 1. 8 The popularity is stored in the .cache folder. 9 --- 10 dmenu.c | 186 +++++++++++++++++++++++++++++++++++++++++++++++++------- 11 1 file changed, 165 insertions(+), 21 deletions(-) 12 13 diff --git a/dmenu.c b/dmenu.c 14 index 804da64..cb26068 100644 15 --- a/dmenu.c 16 +++ b/dmenu.c 17 @@ -35,11 +35,13 @@ struct item { 18 19 static char text[BUFSIZ] = ""; 20 static char *embed; 21 +static char *popcache = NULL; 22 static int bh, mw, mh; 23 static int inputw = 0, promptw; 24 static int lrpad; /* sum of left and right padding */ 25 static size_t cursor; 26 static struct item *items = NULL; 27 +static struct item *popitems = NULL; 28 static struct item *matches, *matchend; 29 static struct item *prev, *curr, *next, *sel; 30 static int mon = -1, screen; 31 @@ -105,7 +107,13 @@ cleanup(void) 32 free(scheme[i]); 33 for (i = 0; items && items[i].text; ++i) 34 free(items[i].text); 35 + for (i = 0; popitems && popitems[i].text; ++i) 36 + free(popitems[i].text); 37 free(items); 38 + free(popitems); 39 + if(popcache != NULL) 40 + free(popcache); 41 + 42 drw_free(drw); 43 XSync(dpy, False); 44 XCloseDisplay(dpy); 45 @@ -226,6 +234,57 @@ grabkeyboard(void) 46 die("cannot grab keyboard"); 47 } 48 49 +static void 50 +sortitemsbypop(struct item* first, struct item* last) 51 +{ 52 + struct item* item = NULL; 53 + struct item* pop = NULL; 54 + size_t idx = 0; 55 + for (pop = popitems; pop && pop->text; ++pop) { 56 + for (item = first; item && item->text && (item <= last || last == NULL); item++) { 57 + if(strcmp(item->text, pop->text) == 0) { 58 + char* lhs = first[idx].text; 59 + first[idx].text = item->text; 60 + item->text = lhs; 61 + ++idx; 62 + break; 63 + } 64 + } 65 + } 66 +} 67 + 68 +static void 69 +incpop(struct item* sel) { 70 + if(!(sel && sel->text)) 71 + return; 72 + struct item* pop = NULL; 73 + int found = 0; 74 + FILE *out; 75 + out = fopen(popcache, "w"); 76 + if (out == NULL) { 77 + printf("Cannot open file '%s'", popcache); 78 + return; 79 + } 80 + char decimal[16] = {'\0'}; 81 + for (pop = popitems; pop && pop->text; ++pop) { 82 + if(found == 0 && strcmp(pop->text, sel->text) == 0) { 83 + pop->out += 1; 84 + found = 1; 85 + } 86 + fputs(pop->text, out); 87 + fputs(" ", out); 88 + sprintf(decimal, "%i", MIN(pop->out, 999)); 89 + fputs(decimal, out); 90 + fputs("\n", out); 91 + } 92 + if(found == 0) { 93 + fputs(sel->text, out); 94 + fputs(" 1", out); 95 + fputs("\n", out); 96 + } 97 + fclose(out); 98 +} 99 + 100 static void 101 match(void) 102 { 103 @@ -234,17 +293,16 @@ match(void) 104 105 char buf[sizeof text], *s; 106 int i, tokc = 0; 107 - size_t len, textsize; 108 - struct item *item, *lprefix, *lsubstr, *prefixend, *substrend; 109 + size_t textsize; 110 + struct item *item, *others, *othersend; 111 112 strcpy(buf, text); 113 /* separate input text into tokens to be matched individually */ 114 for (s = strtok(buf, " "); s; tokv[tokc - 1] = s, s = strtok(NULL, " ")) 115 if (++tokc > tokn && !(tokv = realloc(tokv, ++tokn * sizeof *tokv))) 116 die("cannot realloc %zu bytes:", tokn * sizeof *tokv); 117 - len = tokc ? strlen(tokv[0]) : 0; 118 119 - matches = lprefix = lsubstr = matchend = prefixend = substrend = NULL; 120 + matches = others = matchend = othersend = NULL; 121 textsize = strlen(text) + 1; 122 for (item = items; item && item->text; item++) { 123 for (i = 0; i < tokc; i++) 124 @@ -252,29 +310,20 @@ match(void) 125 break; 126 if (i != tokc) /* not all tokens match */ 127 continue; 128 - /* exact matches go first, then prefixes, then substrings */ 129 + /* exact matches go first, then others */ 130 if (!tokc || !fstrncmp(text, item->text, textsize)) 131 appenditem(item, &matches, &matchend); 132 - else if (!fstrncmp(tokv[0], item->text, len)) 133 - appenditem(item, &lprefix, &prefixend); 134 else 135 - appenditem(item, &lsubstr, &substrend); 136 - } 137 - if (lprefix) { 138 - if (matches) { 139 - matchend->right = lprefix; 140 - lprefix->left = matchend; 141 - } else 142 - matches = lprefix; 143 - matchend = prefixend; 144 + appenditem(item, &others, &othersend); 145 } 146 - if (lsubstr) { 147 + if (others) { 148 + sortitemsbypop(others, othersend); 149 if (matches) { 150 - matchend->right = lsubstr; 151 - lsubstr->left = matchend; 152 + matchend->right = others; 153 + others->left = matchend; 154 } else 155 - matches = lsubstr; 156 - matchend = substrend; 157 + matches = others; 158 + matchend = othersend; 159 } 160 curr = sel = matches; 161 calcoffsets(); 162 @@ -489,6 +538,7 @@ insert: 163 break; 164 case XK_Return: 165 case XK_KP_Enter: 166 + incpop(sel); 167 puts((sel && !(ev->state & ShiftMask)) ? sel->text : text); 168 if (!(ev->state & ControlMask)) { 169 cleanup(); 170 @@ -711,6 +761,98 @@ setup(void) 171 drawmenu(); 172 } 173 174 +static void 175 +itemize(struct item* item, const char* line, const ssize_t size) 176 +{ 177 + const size_t UNDEF = size + 1; 178 + size_t firstchar = UNDEF, lastchar = UNDEF; 179 + size_t firstnum = UNDEF; 180 + size_t i; 181 + int afterspace = 0; 182 + for (i = 0 ; i < size ; ++i) { 183 + const char c = line[i]; 184 + if (c == ' ') { 185 + if (firstchar != UNDEF) 186 + afterspace = 1; 187 + continue; 188 + } 189 + if (afterspace == 1 && (c >= '0' && c <= '9')) { 190 + firstnum = i; 191 + break; 192 + } 193 + if (firstchar == UNDEF) 194 + firstchar = i; 195 + lastchar = i; 196 + } 197 + size_t len = lastchar - firstchar + 2; 198 + item->text = (char*)malloc(sizeof(char) * len); 199 + memcpy(item->text, line + firstchar, len); 200 + item->text[len - 1] = '\0'; 201 + 202 + item->out = 0; 203 + if (firstnum != UNDEF) 204 + item->out = atoi(line + firstnum); 205 +} 206 + 207 +static int 208 +compareitembyoutrev(const void* lhs, const void* rhs) 209 +{ 210 + return ((struct item*)rhs)->out - ((struct item*)lhs)->out; 211 +} 212 + 213 +static void 214 +loadpopitems(void) 215 +{ 216 + const char* xdg_cache_home = getenv("XDG_CACHE_HOME"); 217 + const char* home = getenv("HOME"); 218 + char* cache = NULL; 219 + const char* CACHE_FILENAME = "/dmenu_pop.txt"; 220 + if(xdg_cache_home != NULL) { 221 + size_t xdglen = strlen(xdg_cache_home); 222 + cache = (char*)malloc(xdglen + 1); 223 + cache[xdglen] = '\0'; 224 + strcpy(cache, xdg_cache_home); 225 + } else { 226 + const char* cachefolder = "/.cache"; 227 + size_t hclen = strlen(home) + strlen(cachefolder) + 1; 228 + cache = (char*)malloc(hclen + 1); 229 + cache[hclen - 1] = '\0'; 230 + strcpy(cache, home); 231 + strcpy(cache + strlen(home), cachefolder); 232 + } 233 + const size_t cache_size = strlen(cache) + strlen(CACHE_FILENAME) + 1; 234 + popcache = (char*)malloc(sizeof(char) * cache_size); 235 + popcache[cache_size - 1] = '\0'; 236 + strcpy(popcache, cache); 237 + strcpy(popcache + strlen(cache), CACHE_FILENAME); 238 + free(cache); 239 + 240 + FILE * fp; 241 + char * line = NULL; 242 + size_t i, itemsiz = 0, linesiz = 0; 243 + ssize_t len; 244 + 245 + fp = fopen(popcache, "r"); 246 + if (fp == NULL) 247 + return; 248 + 249 + for (i = 0; (len = getline(&line, &linesiz, fp)) != -1; i++) { 250 + if (i + 1 >= itemsiz) { 251 + itemsiz += 256; 252 + if (!(popitems = realloc(popitems, itemsiz * sizeof(*popitems)))) 253 + die("cannot realloc %zu bytes:", itemsiz * sizeof(*popitems)); 254 + } 255 + itemize((struct item*)&popitems[i], line, len); 256 + } 257 + fclose(fp); 258 + if (line) 259 + free(line); 260 + 261 + if (popitems) 262 + popitems[i].text = NULL; 263 + qsort(popitems, i, sizeof(struct item), compareitembyoutrev); 264 +} 265 + 266 static void 267 usage(void) 268 { 269 @@ -788,6 +930,8 @@ main(int argc, char *argv[]) 270 readstdin(); 271 grabkeyboard(); 272 } 273 + loadpopitems(); 274 + sortitemsbypop(items, NULL); 275 setup(); 276 run(); 277 278 -- 279 2.48.1 280