1
0
mirror of https://github.com/alrayyes/dmenu synced 2023-11-14 15:56:27 +00:00

Compare commits

...

7 Commits

Author SHA1 Message Date
Ryan Kes
34224bd4d5 feat: enable emojis 2020-11-28 14:01:50 +01:00
Ryan Kes
a5e7482731 refactor: updated dependencies 2020-11-28 13:47:12 +01:00
Ryan Kes
5f11ab86e8 feat: use default mono font & joypixels 2020-11-28 13:46:50 +01:00
Ryan Kes
b73c913c19 fix: release version needs to be +s so package is not overridden by other dmenu package 2020-11-28 11:25:16 +01:00
Ryan Kes
621d4d7acd fix: update relase version 2020-11-28 11:21:00 +01:00
Ryan Kes
5f8d30520c feat: installed patches 2020-11-28 11:14:26 +01:00
Ryan Kes
0e531bb769 feat: updated to 5.0 2020-11-28 09:40:47 +01:00
16 changed files with 844 additions and 393 deletions

View File

@ -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
View 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.

View File

@ -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:

View File

@ -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
View 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]].

View File

@ -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"},
[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;

View File

@ -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"},
[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
View 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 */

View 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

View File

@ -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()

View 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
View 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
View 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

View 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;

View 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

View 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);