Recently Written · git

sbm

dmenu bookmarks. Instantly fuzzy search thousands of bookmarks and plumb them. LOOKING FOR SUCKLESS SOFTWARE EDITION? GO TO sbm-suckless INSTEAD

git clone https://github.com/equwal/sbm

Log | Files | Refs


commit 5aab41504e05ad1fc9a3a168be169eb27f8148bd
Spenser Truex <truex@equwal.com>
2026-09-20 22:29:48 -0700

Windows: an installer for Cygwin, with a desktop shortcut

contrib/windows/build-installer.sh makes sbm-VERSION-setup.exe with
NSIS. The installer needs Cygwin. It puts bm and the bm-* tools in
/usr/local/bin, and fzf.exe in /usr/local/libexec/sbm. It makes a
desktop shortcut that opens bm in mintty, with the key
Ctrl+Alt+Shift+F24.

The fzf package of Cygwin is version 0.11 from 2016. It has no
--preview and no --expect. The wrapper contrib/windows/fzf runs
fzf.exe in mintty. /etc/profile.d/sbm.sh sets the opener, the clipboard
and --no-height for Cygwin.

make dist now includes contrib.

 .gitignore                         |   1 +
 Makefile                           |   2 +-
 README                             |  21 ++++++
 contrib/windows/build-installer.sh |  54 ++++++++++++++
 contrib/windows/fzf                |  23 ++++++
 contrib/windows/sbm-setup          |  10 +++
 contrib/windows/sbm.nsi            | 146 +++++++++++++++++++++++++++++++++++++
 contrib/windows/sbm.sh             |  11 +++
 8 files changed, 267 insertions(+), 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index d2c33fd..192f813 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
 tags.*
 bm
 bmks
+sbm-*-setup.exe
diff --git a/Makefile b/Makefile
index 42449fb..1ff7f7c 100644
--- a/Makefile
+++ b/Makefile
@@ -17,7 +17,7 @@ check:
 dist:
 	@echo creating dist tarball
 	@mkdir -p sbm-${VERSION}-temp
-	@cp -R Makefile config.mk README TODO ${SCRIPTS} usertags engines test sbm-${VERSION}-temp
+	@cp -R Makefile config.mk README TODO ${SCRIPTS} usertags engines test contrib sbm-${VERSION}-temp
 	@mv sbm-${VERSION}-temp sbm-${VERSION}
 	@tar -cf sbm-${VERSION}.tar sbm-${VERSION}
 	@gzip sbm-${VERSION}.tar
diff --git a/README b/README
index 2b0d7fe..8a8ddc0 100644
--- a/README
+++ b/README
@@ -211,6 +211,27 @@ choices on stdin as one of:
 
 Printing nothing means the user cancelled. test/fakemenu is a small example.
 
+== Windows ==
+bm runs under Cygwin. Get sbm-VERSION-setup.exe from a release on GitHub and
+start it. The installer puts bm and the bm-* tools in /usr/local/bin. It
+makes a desktop shortcut that opens bm in a mintty window, and Esc closes
+the window. The key of the shortcut is Ctrl+Alt+Shift+F24. Almost no keyboard
+has an F24 key, so keyboard firmware can use this key combination without
+conflicts.
+
+The installer also puts fzf.exe, the Windows build of fzf, in
+/usr/local/libexec/sbm. Do not use the fzf package of Cygwin. That package is
+version 0.11 from 2016, and it has no --preview and no --expect. The wrapper
+/usr/local/bin/fzf lets fzf.exe run in mintty. /etc/profile.d/sbm.sh sets
+SBM_OPEN, SBM_COPY, SBM_PASTE and SBM_FZF_OPTS for Cygwin. Settings in
+~/.bash_profile win.
+
+For bm-title, bm-check and bm-import, install the curl and jq packages of
+Cygwin. The Windows builds of curl and jq do not understand Cygwin paths,
+such as /dev/null. To remove sbm, use Apps in the Windows settings. The
+uninstaller keeps your bookmarks. contrib/windows/build-installer.sh makes
+the installer with NSIS.
+
 == Tests ==
     make check
 
diff --git a/contrib/windows/build-installer.sh b/contrib/windows/build-installer.sh
new file mode 100755
index 0000000..38db287
--- /dev/null
+++ b/contrib/windows/build-installer.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+
+# build-installer.sh: make sbm-VERSION-setup.exe, the Windows installer, in
+# the top directory of the source. The script needs makensis (NSIS), curl,
+# unzip and sha256sum. It runs on Linux, and on Windows under Cygwin.
+#
+#     sh contrib/windows/build-installer.sh
+#
+# The script gets fzf.exe from the fzf release on GitHub. It stops if the
+# SHA-256 sum of the download is not the expected one.
+
+set -e
+
+fzf_version=0.74.4
+fzf_sum=5e63c0e798406fcb9c51a9fed4988398e25fdabf8c670e32c16caf7b4a7ed02d
+
+here=$(cd "$(dirname "$0")" && pwd)
+top=$(cd "$here/../.." && pwd)
+version=$(sed -n 's/^VERSION=//p' "$top/config.mk")
+# VIProductVersion needs four numbers: 0.3 becomes 0.3.0.0.
+viversion=$(printf '%s.0.0.0\n' "$version" | cut -d. -f1-4)
+out=$top/sbm-$version-setup.exe
+
+# makensis for Windows needs Windows paths.
+native () {
+    if command -v cygpath >/dev/null 2>&1; then
+        cygpath -w "$1"
+    else
+        printf '%s\n' "$1"
+    fi
+}
+
+work=${TMPDIR:-/tmp}/sbm-installer.$$
+mkdir "$work"
+trap 'rm -rf "$work"' EXIT INT TERM
+mkdir "$work/libexec" "$work/share" "$work/etc"
+
+make -s -C "$top" install PREFIX="$work" >/dev/null
+cp "$here/fzf" "$work/bin/"
+cp "$here/sbm-setup" "$work/libexec/"
+cp "$here/sbm.sh" "$work/etc/"
+cp "$top/engines" "$top/usertags" "$work/share/"
+
+url=https://github.com/junegunn/fzf/releases/download/v$fzf_version
+curl -fsSL -o "$work/fzf.zip" "$url/fzf-$fzf_version-windows_amd64.zip"
+printf '%s  %s\n' "$fzf_sum" "$work/fzf.zip" | sha256sum -c - >/dev/null
+unzip -q "$work/fzf.zip" fzf.exe -d "$work/libexec"
+curl -fsSL -o "$work/libexec/fzf-LICENSE.txt" \
+    "https://raw.githubusercontent.com/junegunn/fzf/v$fzf_version/LICENSE"
+
+makensis -V2 -DVERSION="$version" -DVIVERSION="$viversion" \
+    -DSRC="$(native "$work")" -DOUTFILE="$(native "$out")" \
+    "$(native "$here/sbm.nsi")"
+printf '%s\n' "$out"
diff --git a/contrib/windows/fzf b/contrib/windows/fzf
new file mode 100755
index 0000000..7893b74
--- /dev/null
+++ b/contrib/windows/fzf
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+# fzf: run fzf.exe, the Windows build of fzf, under Cygwin.
+#
+# The fzf package of Cygwin is version 0.11 from 2016. That version has no
+# --preview and no --expect, and bm uses both. The installer puts fzf.exe
+# in ../libexec/sbm. Without the installer, this script uses the fzf.exe in
+# PATH, for example from "winget install junegunn.fzf".
+#
+# MSYS=enable_pcon: when MSYS does not contain enable_pcon, fzf.exe in mintty
+# starts itself again through a proxy for MSYS2. Cygwin does not need the
+# proxy, because Cygwin gives native programs a pseudo console. Cygwin
+# ignores MSYS.
+#
+# SHELL: fzf.exe runs --preview and --bind commands with $SHELL. When SHELL
+# is not set, fzf.exe uses cmd.exe, and the bm preview fails.
+
+exe=${0%/*}/../libexec/sbm/fzf.exe
+[ -x "$exe" ] || exe=fzf.exe
+MSYS=${MSYS:+$MSYS }enable_pcon
+SHELL=${SHELL:-/bin/sh}
+export MSYS SHELL
+exec "$exe" "$@"
diff --git a/contrib/windows/sbm-setup b/contrib/windows/sbm-setup
new file mode 100755
index 0000000..b33dead
--- /dev/null
+++ b/contrib/windows/sbm-setup
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+# sbm-setup: give the user the example tags and search keywords of sbm.
+# The installer runs this script. It keeps the files that the user has.
+
+d=${XDG_DATA_HOME:-$HOME/.local/share}/sbm
+mkdir -p "$d"
+for f in engines usertags; do
+    [ -e "$d/$f" ] || cp "/usr/local/share/sbm/$f" "$d/"
+done
diff --git a/contrib/windows/sbm.nsi b/contrib/windows/sbm.nsi
new file mode 100644
index 0000000..30b201c
--- /dev/null
+++ b/contrib/windows/sbm.nsi
@@ -0,0 +1,146 @@
+; sbm.nsi: the Windows installer of sbm. contrib/windows/build-installer.sh
+; runs makensis on this file.
+;
+; The installer needs Cygwin with mintty. It puts bm and the bm-* tools in
+; /usr/local/bin, fzf.exe in /usr/local/libexec/sbm, and the settings in
+; /etc/profile.d/sbm.sh. It also makes a desktop shortcut that opens bm in a
+; mintty window. The key of the shortcut is Ctrl+Alt+Shift+F24. Almost no
+; keyboard has an F24 key, so keyboard firmware can use this key combination
+; without conflicts.
+;
+; The uninstaller removes these files. It keeps the bookmarks of the user.
+
+Unicode true
+ManifestDPIAware true
+!include LogicLib.nsh
+
+!ifndef VERSION
+  !error "Give the version, for example -DVERSION=0.3"
+!endif
+!ifndef VIVERSION
+  !error "Give the version in four numbers, for example -DVIVERSION=0.3.0.0"
+!endif
+!ifndef SRC
+  !error "Give the directory with the files to install, with -DSRC=dir"
+!endif
+!ifndef OUTFILE
+  !error "Give the name of the installer, with -DOUTFILE=file"
+!endif
+
+!define UNINSTKEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\sbm"
+!define MINTTYARGS "--class sbm -t sbm -s 120,32 -p center -o ConfirmExit=no -o Scrollbar=none /bin/bash -lc bm"
+
+Name "sbm ${VERSION}"
+OutFile "${OUTFILE}"
+RequestExecutionLevel admin
+ShowInstDetails show
+ShowUninstDetails show
+
+VIProductVersion "${VIVERSION}"
+VIAddVersionKey "ProductName" "sbm"
+VIAddVersionKey "ProductVersion" "${VERSION}"
+VIAddVersionKey "FileVersion" "${VERSION}"
+VIAddVersionKey "FileDescription" "sbm ${VERSION} installer"
+VIAddVersionKey "LegalCopyright" "Spenser Truex"
+
+Var Cygwin
+
+Page instfiles
+UninstPage uninstConfirm
+UninstPage instfiles
+
+Function .onInit
+  ; Cygwin setup is a 64-bit program. It writes to the 64-bit registry.
+  SetRegView 64
+  ReadRegStr $Cygwin HKLM "Software\Cygwin\setup" "rootdir"
+  ${If} $Cygwin == ""
+    ReadRegStr $Cygwin HKCU "Software\Cygwin\setup" "rootdir"
+  ${EndIf}
+  ${IfNot} ${FileExists} "$Cygwin\bin\mintty.exe"
+    MessageBox MB_ICONSTOP "sbm needs Cygwin with mintty.$\r$\nInstall Cygwin from https://cygwin.com, then start this installer again."
+    Abort
+  ${EndIf}
+FunctionEnd
+
+Section
+  SetOutPath "$Cygwin\usr\local\bin"
+  File "${SRC}\bin\bm"
+  File "${SRC}\bin\bm-check"
+  File "${SRC}\bin\bm-commit"
+  File "${SRC}\bin\bm-html"
+  File "${SRC}\bin\bm-import"
+  File "${SRC}\bin\bm-migrate"
+  File "${SRC}\bin\bm-title"
+  File "${SRC}\bin\fzf"
+  SetOutPath "$Cygwin\usr\local\libexec\sbm"
+  File "${SRC}\libexec\fzf.exe"
+  File "${SRC}\libexec\fzf-LICENSE.txt"
+  File "${SRC}\libexec\sbm-setup"
+  SetOutPath "$Cygwin\usr\local\share\sbm"
+  File "${SRC}\share\engines"
+  File "${SRC}\share\usertags"
+  SetOutPath "$Cygwin\etc\profile.d"
+  File "${SRC}\etc\sbm.sh"
+
+  ; Cygwin does not see an execute permission on files that a Windows
+  ; program makes. Cygwin chmod sets it.
+  nsExec::ExecToLog '"$Cygwin\bin\chmod.exe" 755 /usr/local/bin/bm /usr/local/bin/bm-check /usr/local/bin/bm-commit /usr/local/bin/bm-html /usr/local/bin/bm-import /usr/local/bin/bm-migrate /usr/local/bin/bm-title /usr/local/bin/fzf /usr/local/libexec/sbm/fzf.exe'
+  Pop $0
+  ${If} $0 != 0
+    Abort "chmod failed with exit status $0"
+  ${EndIf}
+
+  nsExec::ExecToLog '"$Cygwin\bin\sh.exe" /usr/local/libexec/sbm/sbm-setup'
+  Pop $0
+  ${If} $0 != 0
+    Abort "sbm-setup failed with exit status $0"
+  ${EndIf}
+
+  WriteUninstaller "$Cygwin\usr\local\libexec\sbm\uninstall.exe"
+
+  SetShellVarContext current
+  CreateShortcut /NoWorkingDir "$DESKTOP\sbm.lnk" "$Cygwin\bin\mintty.exe" \
+    "${MINTTYARGS}" "$Cygwin\bin\mintty.exe" 0 SW_SHOWNORMAL \
+    ALT|CONTROL|SHIFT|F24 "sbm: bookmarks in fzf"
+
+  WriteRegStr HKLM "${UNINSTKEY}" "DisplayName" "sbm"
+  WriteRegStr HKLM "${UNINSTKEY}" "DisplayVersion" "${VERSION}"
+  WriteRegStr HKLM "${UNINSTKEY}" "Publisher" "Spenser Truex"
+  WriteRegStr HKLM "${UNINSTKEY}" "URLInfoAbout" "https://github.com/equwal/sbm"
+  WriteRegStr HKLM "${UNINSTKEY}" "InstallLocation" "$Cygwin"
+  WriteRegStr HKLM "${UNINSTKEY}" "UninstallString" '"$Cygwin\usr\local\libexec\sbm\uninstall.exe"'
+  WriteRegDWORD HKLM "${UNINSTKEY}" "NoModify" 1
+  WriteRegDWORD HKLM "${UNINSTKEY}" "NoRepair" 1
+SectionEnd
+
+Function un.onInit
+  SetRegView 64
+  ReadRegStr $Cygwin HKLM "${UNINSTKEY}" "InstallLocation"
+  ${IfNot} ${FileExists} "$Cygwin\bin\mintty.exe"
+    MessageBox MB_ICONSTOP "The registry does not give the Cygwin directory of sbm."
+    Abort
+  ${EndIf}
+FunctionEnd
+
+Section Uninstall
+  Delete "$Cygwin\usr\local\bin\bm"
+  Delete "$Cygwin\usr\local\bin\bm-check"
+  Delete "$Cygwin\usr\local\bin\bm-commit"
+  Delete "$Cygwin\usr\local\bin\bm-html"
+  Delete "$Cygwin\usr\local\bin\bm-import"
+  Delete "$Cygwin\usr\local\bin\bm-migrate"
+  Delete "$Cygwin\usr\local\bin\bm-title"
+  Delete "$Cygwin\usr\local\bin\fzf"
+  Delete "$Cygwin\usr\local\libexec\sbm\fzf.exe"
+  Delete "$Cygwin\usr\local\libexec\sbm\fzf-LICENSE.txt"
+  Delete "$Cygwin\usr\local\libexec\sbm\sbm-setup"
+  Delete "$Cygwin\usr\local\libexec\sbm\uninstall.exe"
+  RMDir "$Cygwin\usr\local\libexec\sbm"
+  Delete "$Cygwin\usr\local\share\sbm\engines"
+  Delete "$Cygwin\usr\local\share\sbm\usertags"
+  RMDir "$Cygwin\usr\local\share\sbm"
+  Delete "$Cygwin\etc\profile.d\sbm.sh"
+  SetShellVarContext current
+  Delete "$DESKTOP\sbm.lnk"
+  DeleteRegKey HKLM "${UNINSTKEY}"
+SectionEnd
diff --git a/contrib/windows/sbm.sh b/contrib/windows/sbm.sh
new file mode 100644
index 0000000..010ed9c
--- /dev/null
+++ b/contrib/windows/sbm.sh
@@ -0,0 +1,11 @@
+# sbm.sh: settings for bm under Cygwin. The installer puts this file in
+# /etc/profile.d, and each login shell reads it. The shell reads
+# ~/.bash_profile and ~/.profile later, so settings there win.
+
+# Open URLs with the default browser of Windows.
+export SBM_OPEN="${SBM_OPEN:-cygstart}"
+# Use the Windows clipboard. sed removes the CR at the end of each line.
+export SBM_COPY="${SBM_COPY:-cp /dev/stdin /dev/clipboard}"
+export SBM_PASTE="${SBM_PASTE:-sed s/\r\$// /dev/clipboard}"
+# fzf.exe reads non-ASCII input only in full screen.
+export SBM_FZF_OPTS="${SBM_FZF_OPTS:---no-height}"