mirror of
https://github.com/alrayyes/dmenu
synced 2023-11-14 15:56:27 +00:00
Compare commits
7 Commits
88799555b2
...
34224bd4d5
Author | SHA1 | Date | |
---|---|---|---|
|
34224bd4d5 | ||
|
a5e7482731 | ||
|
5f11ab86e8 | ||
|
b73c913c19 | ||
|
621d4d7acd | ||
|
5f8d30520c | ||
|
0e531bb769 |
22
LICENSE.md
22
LICENSE.md
@ -1,22 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
=====================
|
||||
|
||||
Copyright © `2019` `Ryan Kes <alrayyes at gmail dot com>`
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
22
LICENSE.org
Normal file
22
LICENSE.org
Normal file
@ -0,0 +1,22 @@
|
||||
* The MIT License (MIT)
|
||||
|
||||
Copyright © =<2020>= =<Ryan Kes>=
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
33
PKGBUILD
33
PKGBUILD
@ -1,23 +1,36 @@
|
||||
# Maintainer: Ryan Kes <alrayyes@gmail.com>
|
||||
# Maintainer: Ryan Kes <ryan@ryankes.eu>
|
||||
|
||||
pkgname=dmenu
|
||||
pkgver=4.9
|
||||
pkgver=5.0
|
||||
pkgrel=1
|
||||
pkgdesc='Generic menu for X'
|
||||
url='https://tools.suckless.org/dmenu/'
|
||||
arch=('x86_64')
|
||||
license=('MIT')
|
||||
depends=('sh' 'libxinerama' 'libxft' 'freetype2' 'menu-calc' 'pass' 'yubikey-oath-dmenu' 'otf-nerd-fonts-fira-code')
|
||||
depends=('sh' 'glibc' 'coreutils' 'libx11' 'libxinerama' 'freetype2' 'fontconfig' 'libfontconfig.so' 'menu-calc' 'pass' 'yubikey-oath-dmenu')
|
||||
|
||||
sha512sums=('c2779209fe012de8ca1cdd72923da6d594f4a8368c85c3c0e0afd4ae489a95fe0e6f05a947d115b6b389aa7170ab14c2c645a2031353b0a08f38327ab461fe65'
|
||||
'e2aefad0eb1d7abd31ed359d62687096b1657bb47bae963ff7626c7b0f5da40ce3dccc154234f9f785e02d518fde9dc4843f8a02030a393c5e0188066ff459e5'
|
||||
'ad8183cca1ad1dd5b786896f94d127eeea53cece291c485f928388a7589402f49ed7e0ee8bdae71cc2ed41f3276b96591349cd820621f345376a54d557307fdf')
|
||||
sha512sums=('2b6a7cdf5aefc5e7ca7a4944883c3c16ee6f5005d2a96b61482d4899ad395f9cb8926907681d88b9df3e1188cf421dad4cc17e343b752f6cb8b161d33384b3f3'
|
||||
'9a9fb243aa799342378ff7dc89b2dd4fb369861549fc8f02d87335d176f0896cf5fefea1e3417a4a724e7b9f7bb5360fc2fe4bd77ef1418b7fe1f93683b6e38a'
|
||||
'beed0f7725d84b4d73cb35fb7d78d7db90c7fa15ed0ac4d6cf19e6469caf83167bc3edae246c30a2f0a79e765b46d9a37997a9a72f5dec147cc0a904fa2ef790'
|
||||
'ce806c6481b20d5b2352fc387816878c75285162f6404b3a969a95f9dbd6c7476fd4c6571f260886fa30ccc40e4a388ea8f6902e40ff90447e04bec3faf0669d'
|
||||
'6ce4ce0ddb9c1058f253ce46cdae8949c468d97ce7d787831d8a4f4ebd4db672761e2552703f58d36d01b2933dea624a30627629027b9a5960b404542a40a732'
|
||||
'dd46c1ac2ea8d5ffc29c01ec83ef7dc771d50475252a5723495a4969e5315e29ce059c2ee4f82f6c8b75d12868e27b3d9dfe74f9825ab86ac7a407902b29a2ab'
|
||||
'e6ff377a4a4ed9e98d05a8870d6c57a5f89f853b0ec1160c3702642f899d8079eca595775b3d9f436bd67626fd4df659e511faa6d026130b07eef09a7c0c8b85'
|
||||
'15d88ea4f10c2fa6fb3eb7478f23b24210a0da3542ae7a5be609013500c15564eedd8153d850f079c2c1f4bab1f5e61ff51153e4c85445ee04a0de5c851e305a'
|
||||
'fe0167c56ca9381928b5dd20383d36554729fb5417bf42f0db2e1634a1f82774f068cbea347e68c6cb3adc6c1a5f97ab263c8a9450f0b2c6429ac66de3edf894')
|
||||
|
||||
_patches=()
|
||||
_patches=(
|
||||
"dmenu-border-4.9.diff"
|
||||
"dmenu-fuzzyhighlight-4.9.diff"
|
||||
"dmenu-fuzzymatch-4.9.diff"
|
||||
"local-dmenu-morecolor-20190922-4bf895b.diff"
|
||||
"local-dmenu-mousesupport-5.0.diff"
|
||||
"dmenu-numbers-4.9.diff"
|
||||
"local-allow-color-fonts.diff"
|
||||
)
|
||||
|
||||
source=(https://dl.suckless.org/tools/dmenu-${pkgver}.tar.gz
|
||||
config.h
|
||||
dmenu-frequency
|
||||
"${_patches[@]}")
|
||||
|
||||
prepare() {
|
||||
@ -46,10 +59,6 @@ package() {
|
||||
cd ${pkgname}-${pkgver}
|
||||
make PREFIX=/usr DESTDIR="${pkgdir}" install
|
||||
install -Dm 644 LICENSE -t "${pkgdir}/usr/share/licenses/${pkgname}"
|
||||
|
||||
cd ../
|
||||
install -Dm0755 dmenu-frequency "${pkgdir}/usr/bin/dmenu-frequency"
|
||||
}
|
||||
|
||||
# vim: ts=2 sw=2 et:
|
||||
|
||||
|
13
README.md
13
README.md
@ -1,13 +0,0 @@
|
||||
# My Dmenu Build
|
||||
|
||||
[Dmenu](https://tools.suckless.org/dmenu) [Arch](https://www.archlinux.org/) package with a few patches installed to keep things nice.
|
||||
|
||||
```
|
||||
makepkg -si
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
This theme is released under the MIT License. For more information read the [license][license].
|
||||
|
||||
[license]: https://github.com/alrayyes/dmenu/blob/master/LICENSE.md
|
24
README.org
Normal file
24
README.org
Normal file
@ -0,0 +1,24 @@
|
||||
* Description
|
||||
[[https://tools.suckless.org/dmenu/][Dmenu]] Arch package with a few patches installed to keep things nice:
|
||||
|
||||
- [[https://tools.suckless.org/dmenu/patches/border/][border]]
|
||||
- [[https://tools.suckless.org/dmenu/patches/center/][center]]
|
||||
- [[https://tools.suckless.org/dmenu/patches/fuzzyhighlight/][fuzzyhighlight]]
|
||||
- [[https://tools.suckless.org/dmenu/patches/fuzzymatch/][fuzzymatch]]
|
||||
- [[https://tools.suckless.org/dmenu/patches/morecolor/][morecolor]]
|
||||
- [[https://tools.suckless.org/dmenu/patches/mouse-support/][mousesupport]]
|
||||
- [[https://tools.suckless.org/dmenu/patches/numbers/][numbers]]
|
||||
- My own custom patch to enable colours (and thus emojis)
|
||||
|
||||
* Install
|
||||
#+begin_src shell
|
||||
makepkg -si
|
||||
#+end_src
|
||||
|
||||
- Default font is monospace
|
||||
- When modifying [[https://github.com/alrayyes/dwm/blob/master/config.h][config.h]] be sure to run =updpkgsums= to update checksums before
|
||||
running =makepkg -si=
|
||||
|
||||
* License
|
||||
This theme is released under the MIT License. For more information read
|
||||
the [[file:LICENSE.org][license]].
|
22
config.h
22
config.h
@ -2,16 +2,25 @@
|
||||
/* Default settings; can be overriden by command line. */
|
||||
|
||||
static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
|
||||
static int min_width = 500; /* minimum width when centered */
|
||||
static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */
|
||||
|
||||
/* -fn option overrides fonts[0]; default X11 font or font set */
|
||||
static const char *fonts[] = {
|
||||
"FuraCode Nerd Font Mono:style=Regular:pixelsize=18:antialias=true:autohint=true"
|
||||
"monospace:size=10",
|
||||
"JoyPixels:pixelsize=8:antialias=true:autohint=true"
|
||||
};
|
||||
static const char *prompt = NULL; /* -p option; prompt to the left of input field */
|
||||
|
||||
static const char *prompt =
|
||||
NULL; /* -p option; prompt to the left of input field */
|
||||
static const char *colors[SchemeLast][2] = {
|
||||
/* fg bg */
|
||||
[SchemeNorm] = { "#bbbbbb", "#222222" },
|
||||
[SchemeSel] = { "#eeeeee", "#005577" },
|
||||
[SchemeOut] = { "#000000", "#00ffff" },
|
||||
[SchemeNorm] = {"#bbbbbb", "#222222"},
|
||||
[SchemeSel] = {"#eeeeee", "#005577"},
|
||||
[SchemeSelHighlight] = {"#ffc978", "#005577"},
|
||||
[SchemeNormHighlight] = {"#ffc978", "#222222"},
|
||||
[SchemeOut] = {"#000000", "#00ffff"},
|
||||
[SchemeMid] = {"#eeeeee", "#770000"},
|
||||
};
|
||||
/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
|
||||
static unsigned int lines = 0;
|
||||
@ -21,3 +30,6 @@ static unsigned int lines = 0;
|
||||
* for example: " /?\"&[]"
|
||||
*/
|
||||
static const char worddelimiters[] = " ";
|
||||
|
||||
/* Size of the window border */
|
||||
static unsigned int border_width = 0;
|
||||
|
@ -2,16 +2,25 @@
|
||||
/* Default settings; can be overriden by command line. */
|
||||
|
||||
static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
|
||||
static int min_width = 500; /* minimum width when centered */
|
||||
static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */
|
||||
|
||||
/* -fn option overrides fonts[0]; default X11 font or font set */
|
||||
static const char *fonts[] = {
|
||||
"FuraCode Nerd Font Mono:style=Regular:pixelsize=18:antialias=true:autohint=true"
|
||||
"monospace:size=10",
|
||||
"JoyPixels:pixelsize=8:antialias=true:autohint=true"
|
||||
};
|
||||
static const char *prompt = NULL; /* -p option; prompt to the left of input field */
|
||||
|
||||
static const char *prompt =
|
||||
NULL; /* -p option; prompt to the left of input field */
|
||||
static const char *colors[SchemeLast][2] = {
|
||||
/* fg bg */
|
||||
[SchemeNorm] = { "#bbbbbb", "#222222" },
|
||||
[SchemeSel] = { "#eeeeee", "#005577" },
|
||||
[SchemeOut] = { "#000000", "#00ffff" },
|
||||
[SchemeNorm] = {"#bbbbbb", "#222222"},
|
||||
[SchemeSel] = {"#eeeeee", "#005577"},
|
||||
[SchemeSelHighlight] = {"#ffc978", "#005577"},
|
||||
[SchemeNormHighlight] = {"#ffc978", "#222222"},
|
||||
[SchemeOut] = {"#000000", "#00ffff"},
|
||||
[SchemeMid] = {"#eeeeee", "#770000"},
|
||||
};
|
||||
/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
|
||||
static unsigned int lines = 0;
|
||||
@ -21,3 +30,6 @@ static unsigned int lines = 0;
|
||||
* for example: " /?\"&[]"
|
||||
*/
|
||||
static const char worddelimiters[] = " ";
|
||||
|
||||
/* Size of the window border */
|
||||
static unsigned int border_width = 0;
|
||||
|
25
dmenu-border-4.9.diff
Normal file
25
dmenu-border-4.9.diff
Normal file
@ -0,0 +1,25 @@
|
||||
diff -up dmenu-4.9-b/config.def.h dmenu-4.9-a/config.def.h
|
||||
--- dmenu-4.9-b/config.def.h 2019-02-02 13:55:02.000000000 +0100
|
||||
+++ dmenu-4.9-a/config.def.h 2019-05-19 02:10:12.740040403 +0200
|
||||
@@ -21,3 +21,6 @@ static unsigned int lines = 0;
|
||||
* for example: " /?\"&[]"
|
||||
*/
|
||||
static const char worddelimiters[] = " ";
|
||||
+
|
||||
+/* Size of the window border */
|
||||
+static const unsigned int border_width = 5;
|
||||
diff -up dmenu-4.9-b/dmenu.c dmenu-4.9-a/dmenu.c
|
||||
--- dmenu-4.9-b/dmenu.c 2019-02-02 13:55:02.000000000 +0100
|
||||
+++ dmenu-4.9-a/dmenu.c 2019-05-19 02:11:20.966710117 +0200
|
||||
@@ -654,9 +654,10 @@ setup(void)
|
||||
swa.override_redirect = True;
|
||||
swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
|
||||
swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
|
||||
- win = XCreateWindow(dpy, parentwin, x, y, mw, mh, 0,
|
||||
+ win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width,
|
||||
CopyFromParent, CopyFromParent, CopyFromParent,
|
||||
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
|
||||
+ XSetWindowBorder(dpy, win, scheme[SchemeSel][ColBg].pixel);
|
||||
XSetClassHint(dpy, win, &ch);
|
||||
|
||||
/* open input methods */
|
120
dmenu-center-20200111-8cd37e1.diff
Normal file
120
dmenu-center-20200111-8cd37e1.diff
Normal file
@ -0,0 +1,120 @@
|
||||
From 8cd37e1ab9e7cb025224aeb3543f1a5be8bceb93 Mon Sep 17 00:00:00 2001
|
||||
From: Nihal Jere <nihal@nihaljere.xyz>
|
||||
Date: Sat, 11 Jan 2020 21:16:08 -0600
|
||||
Subject: [PATCH] center patch now has adjustable minimum width
|
||||
|
||||
---
|
||||
config.def.h | 2 ++
|
||||
dmenu.1 | 3 +++
|
||||
dmenu.c | 39 ++++++++++++++++++++++++++++++++-------
|
||||
3 files changed, 37 insertions(+), 7 deletions(-)
|
||||
|
||||
diff --git a/config.def.h b/config.def.h
|
||||
index 1edb647..88ef264 100644
|
||||
--- a/config.def.h
|
||||
+++ b/config.def.h
|
||||
@@ -2,6 +2,8 @@
|
||||
/* Default settings; can be overriden by command line. */
|
||||
|
||||
static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
|
||||
+static int centered = 0; /* -c option; centers dmenu on screen */
|
||||
+static int min_width = 500; /* minimum width when centered */
|
||||
/* -fn option overrides fonts[0]; default X11 font or font set */
|
||||
static const char *fonts[] = {
|
||||
"monospace:size=10"
|
||||
diff --git a/dmenu.1 b/dmenu.1
|
||||
index 323f93c..c036baa 100644
|
||||
--- a/dmenu.1
|
||||
+++ b/dmenu.1
|
||||
@@ -40,6 +40,9 @@ which lists programs in the user's $PATH and runs the result in their $SHELL.
|
||||
.B \-b
|
||||
dmenu appears at the bottom of the screen.
|
||||
.TP
|
||||
+.B \-c
|
||||
+dmenu appears centered on the screen.
|
||||
+.TP
|
||||
.B \-f
|
||||
dmenu grabs the keyboard before reading stdin if not reading from a tty. This
|
||||
is faster, but will lock up X until stdin reaches end\-of\-file.
|
||||
diff --git a/dmenu.c b/dmenu.c
|
||||
index 65f25ce..041c7f8 100644
|
||||
--- a/dmenu.c
|
||||
+++ b/dmenu.c
|
||||
@@ -89,6 +89,15 @@ calcoffsets(void)
|
||||
break;
|
||||
}
|
||||
|
||||
+static int
|
||||
+max_textw(void)
|
||||
+{
|
||||
+ int len = 0;
|
||||
+ for (struct item *item = items; item && item->text; item++)
|
||||
+ len = MAX(TEXTW(item->text), len);
|
||||
+ return len;
|
||||
+}
|
||||
+
|
||||
static void
|
||||
cleanup(void)
|
||||
{
|
||||
@@ -611,6 +620,7 @@ setup(void)
|
||||
bh = drw->fonts->h + 2;
|
||||
lines = MAX(lines, 0);
|
||||
mh = (lines + 1) * bh;
|
||||
+ promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
|
||||
#ifdef XINERAMA
|
||||
i = 0;
|
||||
if (parentwin == root && (info = XineramaQueryScreens(dpy, &n))) {
|
||||
@@ -637,9 +647,16 @@ setup(void)
|
||||
if (INTERSECT(x, y, 1, 1, info[i]))
|
||||
break;
|
||||
|
||||
- x = info[i].x_org;
|
||||
- y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
|
||||
- mw = info[i].width;
|
||||
+ if (centered) {
|
||||
+ mw = MIN(MAX(max_textw() + promptw, min_width), info[i].width);
|
||||
+ x = info[i].x_org + ((info[i].width - mw) / 2);
|
||||
+ y = info[i].y_org + ((info[i].height - mh) / 2);
|
||||
+ } else {
|
||||
+ x = info[i].x_org;
|
||||
+ y = info[i].y_org + (topbar ? 0 : info[i].height - mh);
|
||||
+ mw = info[i].width;
|
||||
+ }
|
||||
+
|
||||
XFree(info);
|
||||
} else
|
||||
#endif
|
||||
@@ -647,11 +664,17 @@ setup(void)
|
||||
if (!XGetWindowAttributes(dpy, parentwin, &wa))
|
||||
die("could not get embedding window attributes: 0x%lx",
|
||||
parentwin);
|
||||
- x = 0;
|
||||
- y = topbar ? 0 : wa.height - mh;
|
||||
- mw = wa.width;
|
||||
+
|
||||
+ if (centered) {
|
||||
+ mw = MIN(MAX(max_textw() + promptw, min_width), wa.width);
|
||||
+ x = (wa.width - mw) / 2;
|
||||
+ y = (wa.height - mh) / 2;
|
||||
+ } else {
|
||||
+ x = 0;
|
||||
+ y = topbar ? 0 : wa.height - mh;
|
||||
+ mw = wa.width;
|
||||
+ }
|
||||
}
|
||||
- promptw = (prompt && *prompt) ? TEXTW(prompt) - lrpad / 4 : 0;
|
||||
inputw = MIN(inputw, mw/3);
|
||||
match();
|
||||
|
||||
@@ -709,6 +732,8 @@ main(int argc, char *argv[])
|
||||
topbar = 0;
|
||||
else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */
|
||||
fast = 1;
|
||||
+ else if (!strcmp(argv[i], "-c")) /* centers dmenu on screen */
|
||||
+ centered = 1;
|
||||
else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
|
||||
fstrncmp = strncasecmp;
|
||||
fstrstr = cistrstr;
|
||||
--
|
||||
2.24.1
|
||||
|
330
dmenu-frequency
330
dmenu-frequency
@ -1,330 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
"""Dmenu launcher with history sorted by frecency.
|
||||
|
||||
Usage:
|
||||
dmenu-frecency [--read-apps]
|
||||
|
||||
Options:
|
||||
--read-apps rereads all .desktop files.
|
||||
"""
|
||||
|
||||
from docopt import docopt
|
||||
import os
|
||||
import sys
|
||||
import xdg.BaseDirectory
|
||||
from xdg.DesktopEntry import DesktopEntry
|
||||
from subprocess import Popen, PIPE
|
||||
from datetime import datetime
|
||||
from collections import defaultdict
|
||||
import pickle
|
||||
import re
|
||||
import gzip
|
||||
import json
|
||||
import tempfile
|
||||
import shlex
|
||||
|
||||
|
||||
CONFIG_DIR = xdg.BaseDirectory.save_config_path('dmenu-frecency')
|
||||
|
||||
|
||||
# Python 2 compatibility
|
||||
try:
|
||||
FileNotFoundError
|
||||
except NameError:
|
||||
FileNotFoundError = IOError
|
||||
|
||||
|
||||
class Application:
|
||||
def __init__(self, name, command_line, mtime=None, path=None, is_desktop=False):
|
||||
self.name = name
|
||||
self.path = path
|
||||
self.command_line = command_line
|
||||
self.is_desktop = is_desktop
|
||||
self.show_command = False
|
||||
if mtime is None:
|
||||
self.mtime = datetime.now()
|
||||
else:
|
||||
self.mtime = mtime
|
||||
|
||||
def run(self):
|
||||
if os.fork() == 0:
|
||||
if self.path:
|
||||
os.chdir(self.path)
|
||||
os.execvp(os.path.expanduser(self.command_line[0]), self.command_line)
|
||||
|
||||
def __lt__(self, other):
|
||||
return (self.is_desktop, self.mtime) < (other.is_desktop, other.mtime)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == other.name
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
def __str__(self):
|
||||
return "<Application: {} {!r}>".format(self.name, self.command_line)
|
||||
|
||||
|
||||
STATE_VERSION = 4
|
||||
|
||||
|
||||
def get_command(desktop_entry):
|
||||
tokens = []
|
||||
for token in shlex.split(desktop_entry.getExec()):
|
||||
if token == '%i':
|
||||
if desktop_entry.getIcon():
|
||||
tokens.append('--icon')
|
||||
tokens.append(desktop_entry.getIcon())
|
||||
else:
|
||||
i = 0
|
||||
newtok = ""
|
||||
nc = len(token)
|
||||
while i < nc:
|
||||
c = token[i]
|
||||
if c == '%' and i < nc - 1:
|
||||
i += 1
|
||||
code = token[i]
|
||||
if code == 'c' and desktop_entry.getName():
|
||||
newtok += desktop_entry.getName()
|
||||
elif code == '%':
|
||||
newtok += '%'
|
||||
else:
|
||||
newtok += c
|
||||
i += 1
|
||||
if newtok:
|
||||
tokens.append(newtok)
|
||||
return tuple(tokens)
|
||||
|
||||
|
||||
class LauncherState:
|
||||
STATE_FILENAME = os.path.join(CONFIG_DIR, 'state')
|
||||
|
||||
def __init__(self, config):
|
||||
self.version = STATE_VERSION
|
||||
self.config = config
|
||||
self.find_apps()
|
||||
self.apps_generated_at = datetime.now()
|
||||
self.visits = defaultdict(list)
|
||||
self.visit_count = defaultdict(int)
|
||||
self.app_last_visit = None
|
||||
self.frecency_cache = {}
|
||||
|
||||
def apps_by_frecency(self):
|
||||
app_last_visit = self.app_last_visit if self.config['preselect-last-visit'] else None
|
||||
if app_last_visit is not None:
|
||||
yield app_last_visit
|
||||
for app, frec in sorted(self.frecency_cache.items(), key=lambda x: (-x[1], x[0])):
|
||||
if app_last_visit is None or app_last_visit != app:
|
||||
yield app
|
||||
for app in self.sorted_apps:
|
||||
if app not in self.frecency_cache:
|
||||
if app_last_visit is None or app_last_visit != app:
|
||||
yield app
|
||||
|
||||
def add_visit(self, app):
|
||||
if not app.is_desktop and app.command_line in self.command_apps:
|
||||
app = self.command_apps[app.command_line]
|
||||
app.show_command = True
|
||||
try:
|
||||
self.sorted_apps.remove(app)
|
||||
except ValueError:
|
||||
pass # not in list
|
||||
vs = self.visits[app]
|
||||
now = datetime.now()
|
||||
vs.append(now)
|
||||
self.visit_count[app] += 1
|
||||
self.visits[app] = vs[-self.config['frecency-visits']:]
|
||||
self.app_last_visit = app if self.config['preselect-last-visit'] else None
|
||||
|
||||
def update_frecencies(self):
|
||||
for app in self.visits.keys():
|
||||
self.frecency_cache[app] = self.frecency(app)
|
||||
|
||||
def frecency(self, app):
|
||||
points = 0
|
||||
for v in self.visits[app]:
|
||||
days_ago = (datetime.now() - v).days
|
||||
if days_ago < 4:
|
||||
points += 100
|
||||
elif days_ago < 14:
|
||||
points += 70
|
||||
elif days_ago < 31:
|
||||
points += 50
|
||||
elif days_ago < 90:
|
||||
points += 30
|
||||
else:
|
||||
points += 10
|
||||
|
||||
return int(self.visit_count[app] * points / len(self.visits[app]))
|
||||
|
||||
@classmethod
|
||||
def load(cls, config):
|
||||
try:
|
||||
with gzip.open(cls.STATE_FILENAME, 'rb') as f:
|
||||
obj = pickle.load(f)
|
||||
version = getattr(obj, 'version', 0)
|
||||
if version < STATE_VERSION:
|
||||
new_obj = cls(config)
|
||||
if version <= 1:
|
||||
for app, vs in obj.visits.items():
|
||||
vc = obj.visit_count[app]
|
||||
app.is_desktop = True
|
||||
new_obj.visit_count[app] = vc
|
||||
new_obj.visits[app] = vs
|
||||
new_obj.find_apps()
|
||||
new_obj.clean_cache()
|
||||
new_obj.update_frecencies()
|
||||
new_obj.config = config
|
||||
return new_obj
|
||||
else:
|
||||
obj.config = config
|
||||
return obj
|
||||
except FileNotFoundError:
|
||||
return cls(config)
|
||||
|
||||
def save(self):
|
||||
with tempfile.NamedTemporaryFile(
|
||||
'wb',
|
||||
dir=os.path.dirname(self.STATE_FILENAME),
|
||||
delete=False) as tf:
|
||||
tempname = tf.name
|
||||
with gzip.open(tempname, 'wb') as gzipf:
|
||||
pickle.dump(self, gzipf)
|
||||
os.rename(tempname, self.STATE_FILENAME)
|
||||
|
||||
def find_apps(self):
|
||||
self.apps = {}
|
||||
self.command_apps = {}
|
||||
if self.config['scan-desktop-files']:
|
||||
for applications_directory in xdg.BaseDirectory.load_data_paths("applications"):
|
||||
if os.path.exists(applications_directory):
|
||||
for dirpath, dirnames, filenames in os.walk(applications_directory):
|
||||
for f in filenames:
|
||||
if f.endswith('.desktop'):
|
||||
full_filename = os.path.join(dirpath, f)
|
||||
self.add_desktop(full_filename)
|
||||
|
||||
if self.config['scan-path']:
|
||||
for pathdir in os.environ["PATH"].split(os.pathsep):
|
||||
pathdir = pathdir.strip('"')
|
||||
if not os.path.isdir(pathdir):
|
||||
continue
|
||||
|
||||
for f in os.listdir(pathdir):
|
||||
filename = os.path.join(pathdir, f)
|
||||
if os.path.isfile(filename) and os.access(filename, os.X_OK):
|
||||
app = Application(
|
||||
name=f,
|
||||
command_line=(f,),
|
||||
mtime=datetime.fromtimestamp(os.path.getmtime(filename)))
|
||||
self.add_app(app)
|
||||
self.sorted_apps = sorted(self.apps.values(), reverse=True)
|
||||
|
||||
def add_desktop(self, filename):
|
||||
try:
|
||||
d = DesktopEntry(filename)
|
||||
if d.getHidden() or d.getNoDisplay() or d.getTerminal() or d.getType() != 'Application':
|
||||
return
|
||||
app = Application(
|
||||
name=d.getName(),
|
||||
command_line=get_command(d),
|
||||
mtime=datetime.fromtimestamp(os.path.getmtime(filename)),
|
||||
is_desktop=True)
|
||||
if d.getPath():
|
||||
app.path = d.getPath()
|
||||
self.add_app(app)
|
||||
except (xdg.Exceptions.ParsingError,
|
||||
xdg.Exceptions.DuplicateGroupError,
|
||||
xdg.Exceptions.DuplicateKeyError,
|
||||
ValueError) as e:
|
||||
sys.stderr.write("Failed to parse desktop file '{}': {!r}\n".format(filename, e))
|
||||
|
||||
def add_app(self, app):
|
||||
if app.command_line not in self.command_apps:
|
||||
self.apps[app.name] = app
|
||||
self.command_apps[app.command_line] = app
|
||||
|
||||
def clean_cache(self):
|
||||
for app in list(self.frecency_cache.keys()):
|
||||
if app.is_desktop and app.name not in self.apps:
|
||||
del self.frecency_cache[app]
|
||||
if self.app_last_visit is not None and self.app_last_visit.name not in self.apps:
|
||||
self.app_last_visit = None
|
||||
|
||||
|
||||
class DmenuFrecency:
|
||||
CONFIG_FILENAME = os.path.join(CONFIG_DIR, 'config.json')
|
||||
DEFAULT_CONFIG = {
|
||||
'dmenu': 'dmenu',
|
||||
'dmenu-args': ['-i'],
|
||||
'cache-days': 1,
|
||||
'frecency-visits': 10,
|
||||
'preselect-last-visit': False,
|
||||
'scan-desktop-files': True,
|
||||
'scan-path': False,
|
||||
}
|
||||
NAME_WITH_COMMAND = re.compile(r"(.+) \([^()]+\)")
|
||||
|
||||
def __init__(self, arguments):
|
||||
self.read_apps = arguments['--read-apps']
|
||||
self.load_config()
|
||||
self.state = LauncherState.load(self.config)
|
||||
assert self.state, "Failed to load state."
|
||||
|
||||
def load_config(self):
|
||||
self.config = {}
|
||||
self.config.update(self.DEFAULT_CONFIG)
|
||||
try:
|
||||
with open(self.CONFIG_FILENAME, 'r') as f:
|
||||
self.config.update(json.load(f))
|
||||
except FileNotFoundError:
|
||||
with open(self.CONFIG_FILENAME, 'w') as f:
|
||||
json.dump(self.config, f, sort_keys=True, indent=4)
|
||||
f.write('\n')
|
||||
|
||||
def main(self):
|
||||
if self.read_apps:
|
||||
self.state.find_apps()
|
||||
self.state.clean_cache()
|
||||
self.state.save()
|
||||
return
|
||||
|
||||
dmenu = Popen([self.config['dmenu']] + self.config['dmenu-args'], stdin=PIPE, stdout=PIPE)
|
||||
for app in self.state.apps_by_frecency():
|
||||
app_name = app.name.encode('utf-8')
|
||||
dmenu.stdin.write(app_name)
|
||||
if app.show_command and app.name != app.command_line[0]:
|
||||
dmenu.stdin.write(" ({})".format(' '.join(app.command_line)).encode('utf-8'))
|
||||
dmenu.stdin.write(b'\n')
|
||||
stdout, stderr = dmenu.communicate()
|
||||
result = stdout.decode('utf-8').strip()
|
||||
|
||||
if not result:
|
||||
return
|
||||
|
||||
if result in self.state.apps:
|
||||
app = self.state.apps[result]
|
||||
else:
|
||||
m = self.NAME_WITH_COMMAND.match(result)
|
||||
if m and m.group(1) in self.state.apps:
|
||||
app = self.state.apps[m.group(1)]
|
||||
else:
|
||||
app = Application(
|
||||
name=result,
|
||||
command_line=tuple(shlex.split(result)))
|
||||
|
||||
app.run()
|
||||
|
||||
self.state.add_visit(app)
|
||||
self.state.update_frecencies()
|
||||
|
||||
if (datetime.now() - self.state.apps_generated_at).days >= self.config['cache-days']:
|
||||
self.state.find_apps()
|
||||
self.state.clean_cache()
|
||||
self.state.save()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
arguments = docopt(__doc__, version="0.1")
|
||||
DmenuFrecency(arguments).main()
|
||||
|
152
dmenu-fuzzyhighlight-4.9.diff
Normal file
152
dmenu-fuzzyhighlight-4.9.diff
Normal file
@ -0,0 +1,152 @@
|
||||
Author: Chris Noxz <chris@noxz.tech>
|
||||
note: This patch is meant to be used together with fuzzymatch
|
||||
|
||||
diff -upN dmenu-4.9/config.def.h dmenu-4.9-fuzzyhighlight/config.def.h
|
||||
--- dmenu-4.9/config.def.h 2019-02-02 13:55:02.000000000 +0100
|
||||
+++ dmenu-4.9-fuzzyhighlight/config.def.h 2020-04-04 10:26:36.990890854 +0200
|
||||
@@ -11,6 +11,8 @@ static const char *colors[SchemeLast][2]
|
||||
/* fg bg */
|
||||
[SchemeNorm] = { "#bbbbbb", "#222222" },
|
||||
[SchemeSel] = { "#eeeeee", "#005577" },
|
||||
+ [SchemeSelHighlight] = { "#ffc978", "#005577" },
|
||||
+ [SchemeNormHighlight] = { "#ffc978", "#222222" },
|
||||
[SchemeOut] = { "#000000", "#00ffff" },
|
||||
};
|
||||
/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
|
||||
diff -upN dmenu-4.9/dmenu.1 dmenu-4.9-fuzzyhighlight/dmenu.1
|
||||
--- dmenu-4.9/dmenu.1 2019-02-02 13:55:02.000000000 +0100
|
||||
+++ dmenu-4.9-fuzzyhighlight/dmenu.1 2020-04-04 10:30:16.430054933 +0200
|
||||
@@ -20,6 +20,14 @@ dmenu \- dynamic menu
|
||||
.IR color ]
|
||||
.RB [ \-sf
|
||||
.IR color ]
|
||||
+.RB [ \-nhb
|
||||
+.IR color ]
|
||||
+.RB [ \-nhf
|
||||
+.IR color ]
|
||||
+.RB [ \-shb
|
||||
+.IR color ]
|
||||
+.RB [ \-shf
|
||||
+.IR color ]
|
||||
.RB [ \-w
|
||||
.IR windowid ]
|
||||
.P
|
||||
@@ -75,6 +83,18 @@ defines the selected background color.
|
||||
.BI \-sf " color"
|
||||
defines the selected foreground color.
|
||||
.TP
|
||||
+.BI \-nhb " color"
|
||||
+defines the normal highlight background color.
|
||||
+.TP
|
||||
+.BI \-nhf " color"
|
||||
+defines the normal highlight foreground color.
|
||||
+.TP
|
||||
+.BI \-shb " color"
|
||||
+defines the selected highlight background color.
|
||||
+.TP
|
||||
+.BI \-shf " color"
|
||||
+defines the selected highlight foreground color.
|
||||
+.TP
|
||||
.B \-v
|
||||
prints version information to stdout, then exits.
|
||||
.TP
|
||||
diff -upN dmenu-4.9/dmenu.c dmenu-4.9-fuzzyhighlight/dmenu.c
|
||||
--- dmenu-4.9/dmenu.c 2019-02-02 13:55:02.000000000 +0100
|
||||
+++ dmenu-4.9-fuzzyhighlight/dmenu.c 2020-04-04 10:27:43.888026309 +0200
|
||||
@@ -26,7 +26,9 @@
|
||||
#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
|
||||
|
||||
/* enums */
|
||||
-enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
|
||||
+enum { SchemeNorm, SchemeSel, SchemeNormHighlight, SchemeSelHighlight,
|
||||
+ SchemeOut, SchemeLast }; /* color schemes */
|
||||
+
|
||||
|
||||
struct item {
|
||||
char *text;
|
||||
@@ -113,9 +115,49 @@ cistrstr(const char *s, const char *sub)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
+static void
|
||||
+drawhighlights(struct item *item, int x, int y, int maxw)
|
||||
+{
|
||||
+ int i, indent;
|
||||
+ char *highlight;
|
||||
+ char c;
|
||||
+
|
||||
+ if (!(strlen(item->text) && strlen(text)))
|
||||
+ return;
|
||||
+
|
||||
+ drw_setscheme(drw, scheme[item == sel
|
||||
+ ? SchemeSelHighlight
|
||||
+ : SchemeNormHighlight]);
|
||||
+ for (i = 0, highlight = item->text; *highlight && text[i];) {
|
||||
+ if (*highlight == text[i]) {
|
||||
+ /* get indentation */
|
||||
+ c = *highlight;
|
||||
+ *highlight = '\0';
|
||||
+ indent = TEXTW(item->text);
|
||||
+ *highlight = c;
|
||||
+
|
||||
+ /* highlight character */
|
||||
+ c = highlight[1];
|
||||
+ highlight[1] = '\0';
|
||||
+ drw_text(
|
||||
+ drw,
|
||||
+ x + indent - (lrpad / 2),
|
||||
+ y,
|
||||
+ MIN(maxw - indent, TEXTW(highlight) - lrpad),
|
||||
+ bh, 0, highlight, 0
|
||||
+ );
|
||||
+ highlight[1] = c;
|
||||
+ i++;
|
||||
+ }
|
||||
+ highlight++;
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+
|
||||
static int
|
||||
drawitem(struct item *item, int x, int y, int w)
|
||||
{
|
||||
+ int r;
|
||||
if (item == sel)
|
||||
drw_setscheme(drw, scheme[SchemeSel]);
|
||||
else if (item->out)
|
||||
@@ -123,7 +165,9 @@ drawitem(struct item *item, int x, int y
|
||||
else
|
||||
drw_setscheme(drw, scheme[SchemeNorm]);
|
||||
|
||||
- return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
|
||||
+ r = drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
|
||||
+ drawhighlights(item, x, y, w);
|
||||
+ return r;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -683,7 +727,8 @@ static void
|
||||
usage(void)
|
||||
{
|
||||
fputs("usage: dmenu [-bfiv] [-l lines] [-p prompt] [-fn font] [-m monitor]\n"
|
||||
- " [-nb color] [-nf color] [-sb color] [-sf color] [-w windowid]\n", stderr);
|
||||
+ " [-nb color] [-nf color] [-sb color] [-sf color]\n"
|
||||
+ " [-nhb color] [-nhf color] [-shb color] [-shf color] [-w windowid]\n", stderr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
@@ -724,6 +769,14 @@ main(int argc, char *argv[])
|
||||
colors[SchemeSel][ColBg] = argv[++i];
|
||||
else if (!strcmp(argv[i], "-sf")) /* selected foreground color */
|
||||
colors[SchemeSel][ColFg] = argv[++i];
|
||||
+ else if (!strcmp(argv[i], "-nhb")) /* normal hi background color */
|
||||
+ colors[SchemeNormHighlight][ColBg] = argv[++i];
|
||||
+ else if (!strcmp(argv[i], "-nhf")) /* normal hi foreground color */
|
||||
+ colors[SchemeNormHighlight][ColFg] = argv[++i];
|
||||
+ else if (!strcmp(argv[i], "-shb")) /* selected hi background color */
|
||||
+ colors[SchemeSelHighlight][ColBg] = argv[++i];
|
||||
+ else if (!strcmp(argv[i], "-shf")) /* selected hi foreground color */
|
||||
+ colors[SchemeSelHighlight][ColFg] = argv[++i];
|
||||
else if (!strcmp(argv[i], "-w")) /* embedding window id */
|
||||
embed = argv[++i];
|
||||
else
|
163
dmenu-fuzzymatch-4.9.diff
Normal file
163
dmenu-fuzzymatch-4.9.diff
Normal file
@ -0,0 +1,163 @@
|
||||
From 94353eb52055927d9079f3d9e33da1c954abf386 Mon Sep 17 00:00:00 2001
|
||||
From: aleks <aleks.stier@icloud.com>
|
||||
Date: Wed, 26 Jun 2019 13:25:10 +0200
|
||||
Subject: [PATCH] Add support for fuzzy-matching
|
||||
|
||||
---
|
||||
config.def.h | 1 +
|
||||
config.mk | 2 +-
|
||||
dmenu.c | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
3 files changed, 91 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/config.def.h b/config.def.h
|
||||
index 1edb647..51612b9 100644
|
||||
--- a/config.def.h
|
||||
+++ b/config.def.h
|
||||
@@ -2,6 +2,7 @@
|
||||
/* Default settings; can be overriden by command line. */
|
||||
|
||||
static int topbar = 1; /* -b option; if 0, dmenu appears at bottom */
|
||||
+static int fuzzy = 1; /* -F option; if 0, dmenu doesn't use fuzzy matching */
|
||||
/* -fn option overrides fonts[0]; default X11 font or font set */
|
||||
static const char *fonts[] = {
|
||||
"monospace:size=10"
|
||||
diff --git a/config.mk b/config.mk
|
||||
index 0929b4a..d14309a 100644
|
||||
--- a/config.mk
|
||||
+++ b/config.mk
|
||||
@@ -20,7 +20,7 @@ FREETYPEINC = /usr/include/freetype2
|
||||
|
||||
# includes and libs
|
||||
INCS = -I$(X11INC) -I$(FREETYPEINC)
|
||||
-LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS)
|
||||
+LIBS = -L$(X11LIB) -lX11 $(XINERAMALIBS) $(FREETYPELIBS) -lm
|
||||
|
||||
# flags
|
||||
CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_POSIX_C_SOURCE=200809L -DVERSION=\"$(VERSION)\" $(XINERAMAFLAGS)
|
||||
diff --git a/dmenu.c b/dmenu.c
|
||||
index 6b8f51b..96ddc98 100644
|
||||
--- a/dmenu.c
|
||||
+++ b/dmenu.c
|
||||
@@ -1,6 +1,7 @@
|
||||
/* See LICENSE file for copyright and license details. */
|
||||
#include <ctype.h>
|
||||
#include <locale.h>
|
||||
+#include <math.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -32,6 +33,7 @@ struct item {
|
||||
char *text;
|
||||
struct item *left, *right;
|
||||
int out;
|
||||
+ double distance;
|
||||
};
|
||||
|
||||
static char text[BUFSIZ] = "";
|
||||
@@ -210,9 +212,94 @@ grabkeyboard(void)
|
||||
die("cannot grab keyboard");
|
||||
}
|
||||
|
||||
+int
|
||||
+compare_distance(const void *a, const void *b)
|
||||
+{
|
||||
+ struct item *da = *(struct item **) a;
|
||||
+ struct item *db = *(struct item **) b;
|
||||
+
|
||||
+ if (!db)
|
||||
+ return 1;
|
||||
+ if (!da)
|
||||
+ return -1;
|
||||
+
|
||||
+ return da->distance == db->distance ? 0 : da->distance < db->distance ? -1 : 1;
|
||||
+}
|
||||
+
|
||||
+void
|
||||
+fuzzymatch(void)
|
||||
+{
|
||||
+ /* bang - we have so much memory */
|
||||
+ struct item *it;
|
||||
+ struct item **fuzzymatches = NULL;
|
||||
+ char c;
|
||||
+ int number_of_matches = 0, i, pidx, sidx, eidx;
|
||||
+ int text_len = strlen(text), itext_len;
|
||||
+
|
||||
+ matches = matchend = NULL;
|
||||
+
|
||||
+ /* walk through all items */
|
||||
+ for (it = items; it && it->text; it++) {
|
||||
+ if (text_len) {
|
||||
+ itext_len = strlen(it->text);
|
||||
+ pidx = 0; /* pointer */
|
||||
+ sidx = eidx = -1; /* start of match, end of match */
|
||||
+ /* walk through item text */
|
||||
+ for (i = 0; i < itext_len && (c = it->text[i]); i++) {
|
||||
+ /* fuzzy match pattern */
|
||||
+ if (!fstrncmp(&text[pidx], &c, 1)) {
|
||||
+ if(sidx == -1)
|
||||
+ sidx = i;
|
||||
+ pidx++;
|
||||
+ if (pidx == text_len) {
|
||||
+ eidx = i;
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
+ /* build list of matches */
|
||||
+ if (eidx != -1) {
|
||||
+ /* compute distance */
|
||||
+ /* add penalty if match starts late (log(sidx+2))
|
||||
+ * add penalty for long a match without many matching characters */
|
||||
+ it->distance = log(sidx + 2) + (double)(eidx - sidx - text_len);
|
||||
+ /* fprintf(stderr, "distance %s %f\n", it->text, it->distance); */
|
||||
+ appenditem(it, &matches, &matchend);
|
||||
+ number_of_matches++;
|
||||
+ }
|
||||
+ } else {
|
||||
+ appenditem(it, &matches, &matchend);
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ if (number_of_matches) {
|
||||
+ /* initialize array with matches */
|
||||
+ if (!(fuzzymatches = realloc(fuzzymatches, number_of_matches * sizeof(struct item*))))
|
||||
+ die("cannot realloc %u bytes:", number_of_matches * sizeof(struct item*));
|
||||
+ for (i = 0, it = matches; it && i < number_of_matches; i++, it = it->right) {
|
||||
+ fuzzymatches[i] = it;
|
||||
+ }
|
||||
+ /* sort matches according to distance */
|
||||
+ qsort(fuzzymatches, number_of_matches, sizeof(struct item*), compare_distance);
|
||||
+ /* rebuild list of matches */
|
||||
+ matches = matchend = NULL;
|
||||
+ for (i = 0, it = fuzzymatches[i]; i < number_of_matches && it && \
|
||||
+ it->text; i++, it = fuzzymatches[i]) {
|
||||
+ appenditem(it, &matches, &matchend);
|
||||
+ }
|
||||
+ free(fuzzymatches);
|
||||
+ }
|
||||
+ curr = sel = matches;
|
||||
+ calcoffsets();
|
||||
+}
|
||||
+
|
||||
static void
|
||||
match(void)
|
||||
{
|
||||
+ if (fuzzy) {
|
||||
+ fuzzymatch();
|
||||
+ return;
|
||||
+ }
|
||||
static char **tokv = NULL;
|
||||
static int tokn = 0;
|
||||
|
||||
@@ -702,6 +789,8 @@ main(int argc, char *argv[])
|
||||
topbar = 0;
|
||||
else if (!strcmp(argv[i], "-f")) /* grabs keyboard before reading stdin */
|
||||
fast = 1;
|
||||
+ else if (!strcmp(argv[i], "-F")) /* grabs keyboard before reading stdin */
|
||||
+ fuzzy = 0;
|
||||
else if (!strcmp(argv[i], "-i")) { /* case-insensitive item matching */
|
||||
fstrncmp = strncasecmp;
|
||||
fstrstr = cistrstr;
|
||||
--
|
||||
2.22.0
|
||||
|
81
dmenu-numbers-4.9.diff
Normal file
81
dmenu-numbers-4.9.diff
Normal file
@ -0,0 +1,81 @@
|
||||
From 61abc60dbfaa8ec63fcd176307308aee88a19e32 Mon Sep 17 00:00:00 2001
|
||||
From: Miles Alan <m@milesalan.com>
|
||||
Date: Sat, 10 Aug 2019 17:20:08 -0500
|
||||
Subject: [PATCH] Display number of matched and total items in top right corner
|
||||
|
||||
---
|
||||
dmenu.c | 25 +++++++++++++++++++++++--
|
||||
1 file changed, 23 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/dmenu.c b/dmenu.c
|
||||
index 6b8f51b..98c5810 100644
|
||||
--- a/dmenu.c
|
||||
+++ b/dmenu.c
|
||||
@@ -24,6 +24,8 @@
|
||||
* MAX(0, MIN((y)+(h),(r).y_org+(r).height) - MAX((y),(r).y_org)))
|
||||
#define LENGTH(X) (sizeof X / sizeof X[0])
|
||||
#define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
|
||||
+#define NUMBERSMAXDIGITS 100
|
||||
+#define NUMBERSBUFSIZE (NUMBERSMAXDIGITS * 2) + 1
|
||||
|
||||
/* enums */
|
||||
enum { SchemeNorm, SchemeSel, SchemeOut, SchemeLast }; /* color schemes */
|
||||
@@ -34,6 +36,7 @@ struct item {
|
||||
int out;
|
||||
};
|
||||
|
||||
+static char numbers[NUMBERSBUFSIZE] = "";
|
||||
static char text[BUFSIZ] = "";
|
||||
static char *embed;
|
||||
static int bh, mw, mh;
|
||||
@@ -126,6 +129,21 @@ drawitem(struct item *item, int x, int y, int w)
|
||||
return drw_text(drw, x, y, w, bh, lrpad / 2, item->text, 0);
|
||||
}
|
||||
|
||||
+static void
|
||||
+recalculatenumbers()
|
||||
+{
|
||||
+ unsigned int numer = 0, denom = 0;
|
||||
+ struct item *item;
|
||||
+ if (matchend) {
|
||||
+ numer++;
|
||||
+ for (item = matchend; item && item->left; item = item->left)
|
||||
+ numer++;
|
||||
+ }
|
||||
+ for (item = items; item && item->text; item++)
|
||||
+ denom++;
|
||||
+ snprintf(numbers, NUMBERSBUFSIZE, "%d/%d", numer, denom);
|
||||
+}
|
||||
+
|
||||
static void
|
||||
drawmenu(void)
|
||||
{
|
||||
@@ -151,6 +169,7 @@ drawmenu(void)
|
||||
drw_rect(drw, x + curpos, 2, 2, bh - 4, 1, 0);
|
||||
}
|
||||
|
||||
+ recalculatenumbers();
|
||||
if (lines > 0) {
|
||||
/* draw vertical list */
|
||||
for (item = curr; item != next; item = item->right)
|
||||
@@ -165,13 +184,15 @@ drawmenu(void)
|
||||
}
|
||||
x += w;
|
||||
for (item = curr; item != next; item = item->right)
|
||||
- x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">")));
|
||||
+ x = drawitem(item, x, 0, MIN(TEXTW(item->text), mw - x - TEXTW(">") - TEXTW(numbers)));
|
||||
if (next) {
|
||||
w = TEXTW(">");
|
||||
drw_setscheme(drw, scheme[SchemeNorm]);
|
||||
- drw_text(drw, mw - w, 0, w, bh, lrpad / 2, ">", 0);
|
||||
+ drw_text(drw, mw - w - TEXTW(numbers), 0, w, bh, lrpad / 2, ">", 0);
|
||||
}
|
||||
}
|
||||
+ drw_setscheme(drw, scheme[SchemeNorm]);
|
||||
+ drw_text(drw, mw - TEXTW(numbers), 0, TEXTW(numbers), bh, lrpad / 2, numbers, 0);
|
||||
drw_map(drw, win, 0, 0, mw, mh);
|
||||
}
|
||||
|
||||
--
|
||||
2.19.2
|
||||
|
23
local-allow-color-fonts.diff
Normal file
23
local-allow-color-fonts.diff
Normal file
@ -0,0 +1,23 @@
|
||||
diff -up a/drw.c b/drw.c
|
||||
--- a/drw.c 2020-11-28 13:52:47.581529054 +0100
|
||||
+++ b/drw.c 2020-11-28 13:53:32.351573846 +0100
|
||||
@@ -133,19 +133,6 @@ xfont_create(Drw *drw, const char *fontn
|
||||
die("no font specified.");
|
||||
}
|
||||
|
||||
- /* Do not allow using color fonts. This is a workaround for a BadLength
|
||||
- * error from Xft with color glyphs. Modelled on the Xterm workaround. See
|
||||
- * https://bugzilla.redhat.com/show_bug.cgi?id=1498269
|
||||
- * https://lists.suckless.org/dev/1701/30932.html
|
||||
- * https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=916349
|
||||
- * and lots more all over the internet.
|
||||
- */
|
||||
- FcBool iscol;
|
||||
- if(FcPatternGetBool(xfont->pattern, FC_COLOR, 0, &iscol) == FcResultMatch && iscol) {
|
||||
- XftFontClose(drw->dpy, xfont);
|
||||
- return NULL;
|
||||
- }
|
||||
-
|
||||
font = ecalloc(1, sizeof(Fnt));
|
||||
font->xfont = xfont;
|
||||
font->pattern = pattern;
|
32
local-dmenu-morecolor-20190922-4bf895b.diff
Normal file
32
local-dmenu-morecolor-20190922-4bf895b.diff
Normal file
@ -0,0 +1,32 @@
|
||||
diff -up a/config.def.h b/config.def.h
|
||||
--- a/config.def.h 2020-11-28 10:43:05.242935618 +0100
|
||||
+++ b/config.def.h 2020-11-28 10:44:59.249845805 +0100
|
||||
@@ -15,6 +15,7 @@ static const char *colors[SchemeLast][2]
|
||||
[SchemeSelHighlight] = { "#ffc978", "#005577" },
|
||||
[SchemeNormHighlight] = { "#ffc978", "#222222" },
|
||||
[SchemeOut] = { "#000000", "#00ffff" },
|
||||
+ [SchemeMid] = { "#eeeeee", "#770000" },
|
||||
};
|
||||
/* -l option; if nonzero, dmenu uses vertical list with given number of lines */
|
||||
static unsigned int lines = 0;
|
||||
diff -up a/dmenu.c b/dmenu.c
|
||||
--- a/dmenu.c 2020-11-28 10:43:05.242935618 +0100
|
||||
+++ b/dmenu.c 2020-11-28 10:45:57.066625600 +0100
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
/* enums */
|
||||
enum { SchemeNorm, SchemeSel, SchemeNormHighlight, SchemeSelHighlight,
|
||||
- SchemeOut, SchemeLast }; /* color schemes */
|
||||
+ SchemeOut, SchemeMid, SchemeLast }; /* color schemes */
|
||||
|
||||
|
||||
struct item {
|
||||
@@ -162,6 +162,8 @@ drawitem(struct item *item, int x, int y
|
||||
int r;
|
||||
if (item == sel)
|
||||
drw_setscheme(drw, scheme[SchemeSel]);
|
||||
+ else if (item->left == sel || item->right == sel)
|
||||
+ drw_setscheme(drw, scheme[SchemeMid]);
|
||||
else if (item->out)
|
||||
drw_setscheme(drw, scheme[SchemeOut]);
|
||||
else
|
141
local-dmenu-mousesupport-5.0.diff
Normal file
141
local-dmenu-mousesupport-5.0.diff
Normal file
@ -0,0 +1,141 @@
|
||||
diff -up a/dmenu.c b/dmenu.c
|
||||
--- a/dmenu.c 2020-11-28 10:50:19.430405801 +0100
|
||||
+++ b/dmenu.c 2020-11-28 11:01:30.877939721 +0100
|
||||
@@ -634,6 +634,117 @@ draw:
|
||||
}
|
||||
|
||||
static void
|
||||
+buttonpress(XEvent *e)
|
||||
+{
|
||||
+ struct item *item;
|
||||
+ XButtonPressedEvent *ev = &e->xbutton;
|
||||
+ int x = 0, y = 0, h = bh, w;
|
||||
+
|
||||
+ if (ev->window != win)
|
||||
+ return;
|
||||
+
|
||||
+ /* right-click: exit */
|
||||
+ if (ev->button == Button3)
|
||||
+ exit(1);
|
||||
+
|
||||
+ if (prompt && *prompt)
|
||||
+ x += promptw;
|
||||
+
|
||||
+ /* input field */
|
||||
+ w = (lines > 0 || !matches) ? mw - x : inputw;
|
||||
+
|
||||
+ /* left-click on input: clear input,
|
||||
+ * NOTE: if there is no left-arrow the space for < is reserved so
|
||||
+ * add that to the input width */
|
||||
+ if (ev->button == Button1 &&
|
||||
+ ((lines <= 0 && ev->x >= 0 && ev->x <= x + w +
|
||||
+ ((!prev || !curr->left) ? TEXTW("<") : 0)) ||
|
||||
+ (lines > 0 && ev->y >= y && ev->y <= y + h))) {
|
||||
+ insert(NULL, -cursor);
|
||||
+ drawmenu();
|
||||
+ return;
|
||||
+ }
|
||||
+ /* middle-mouse click: paste selection */
|
||||
+ if (ev->button == Button2) {
|
||||
+ XConvertSelection(dpy, (ev->state & ShiftMask) ? clip : XA_PRIMARY,
|
||||
+ utf8, utf8, win, CurrentTime);
|
||||
+ drawmenu();
|
||||
+ return;
|
||||
+ }
|
||||
+ /* scroll up */
|
||||
+ if (ev->button == Button4 && prev) {
|
||||
+ sel = curr = prev;
|
||||
+ calcoffsets();
|
||||
+ drawmenu();
|
||||
+ return;
|
||||
+ }
|
||||
+ /* scroll down */
|
||||
+ if (ev->button == Button5 && next) {
|
||||
+ sel = curr = next;
|
||||
+ calcoffsets();
|
||||
+ drawmenu();
|
||||
+ return;
|
||||
+ }
|
||||
+ if (ev->button != Button1)
|
||||
+ return;
|
||||
+ if (ev->state & ~ControlMask)
|
||||
+ return;
|
||||
+ if (lines > 0) {
|
||||
+ /* vertical list: (ctrl)left-click on item */
|
||||
+ w = mw - x;
|
||||
+ for (item = curr; item != next; item = item->right) {
|
||||
+ y += h;
|
||||
+ if (ev->y >= y && ev->y <= (y + h)) {
|
||||
+ puts(item->text);
|
||||
+ if (!(ev->state & ControlMask))
|
||||
+ if (sel) {
|
||||
+ sel->out = 1;
|
||||
+ drawmenu();
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+ } else if (matches) {
|
||||
+ /* left-click on left arrow */
|
||||
+ x += inputw;
|
||||
+ w = TEXTW("<");
|
||||
+ if (prev && curr->left) {
|
||||
+ if (ev->x >= x && ev->x <= x + w) {
|
||||
+ sel = curr = prev;
|
||||
+ calcoffsets();
|
||||
+ drawmenu();
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+ /* horizontal list: (ctrl)left-click on item */
|
||||
+ for (item = curr; item != next; item = item->right) {
|
||||
+ x += w;
|
||||
+ w = MIN(TEXTW(item->text), mw - x - TEXTW(">"));
|
||||
+ if (ev->x >= x && ev->x <= x + w) {
|
||||
+ puts(item->text);
|
||||
+ if (!(ev->state & ControlMask))
|
||||
+ exit(0);
|
||||
+ sel = item;
|
||||
+ if (sel) {
|
||||
+ sel->out = 1;
|
||||
+ drawmenu();
|
||||
+ }
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+ /* left-click on right arrow */
|
||||
+ w = TEXTW(">");
|
||||
+ x = mw - w;
|
||||
+ if (next && ev->x >= x && ev->x <= x + w) {
|
||||
+ sel = curr = next;
|
||||
+ calcoffsets();
|
||||
+ drawmenu();
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+static void
|
||||
paste(void)
|
||||
{
|
||||
char *p, *q;
|
||||
@@ -694,6 +805,9 @@ run(void)
|
||||
break;
|
||||
cleanup();
|
||||
exit(1);
|
||||
+ case ButtonPress:
|
||||
+ buttonpress(&ev);
|
||||
+ break;
|
||||
case Expose:
|
||||
if (ev.xexpose.count == 0)
|
||||
drw_map(drw, win, 0, 0, mw, mh);
|
||||
@@ -791,7 +905,8 @@ setup(void)
|
||||
/* create menu window */
|
||||
swa.override_redirect = True;
|
||||
swa.background_pixel = scheme[SchemeNorm][ColBg].pixel;
|
||||
- swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask;
|
||||
+ swa.event_mask = ExposureMask | KeyPressMask | VisibilityChangeMask |
|
||||
+ ButtonPressMask;
|
||||
win = XCreateWindow(dpy, parentwin, x, y, mw, mh, border_width,
|
||||
CopyFromParent, CopyFromParent, CopyFromParent,
|
||||
CWOverrideRedirect | CWBackPixel | CWEventMask, &swa);
|
Loading…
Reference in New Issue
Block a user