README (13611 bytes)
1 Manage bookmarks with dmenu and a basic file. 2 3 The file is Tab Separated Values, one bookmark per line: 4 5 URL<tab>description<tab>tag tag tag 6 7 For example: 8 9 https://equwal.com Spenser Truex's website. users 10 11 Lines starting with # are ignored. bm is one way to work with the file; 12 cut, grep, awk and your editor are others. 13 14 == Design == 15 bm owns the bookmark file and talks to the menu. Everything else is a small 16 filter that reads or writes bookmark lines, so the pieces combine like any 17 other Unix tools, and you install only the ones you use: 18 19 bm pick, open, copy, add, edit, delete, list, merge 20 bm-import browser bookmarks -> bookmark lines 21 bm-check bookmark lines -> the dead links among them 22 bm-html bookmark lines -> a searchable web page 23 bm-migrate the old sbm file format -> bookmark lines 24 bm-title a URL -> the page's title 25 bm-commit commit the bookmark file to git 26 bm-watch browser bookmarks -> bm, now and after each change 27 bm-sync the bookmark file <-> your other devices 28 29 bm-import bookmarks.html | bm --merge 30 bm --tag code --list | bm-check - 31 bm --list | grep -i wiki | bm-html -t Wikis - > wikis.html 32 33 Everything is POSIX sh and POSIX utilities: no bash, no mktemp, no GNU 34 options. make check runs shellcheck -s sh over all of it. Where the usual 35 tool is not in POSIX, the script says what stands in for it: noclobber for 36 mktemp, a FIFO of tokens for xargs -P, dd for head -c, awk's srand() for 37 date +%s. 38 39 == Requirements == 40 dmenu (or fzf), one of xclip, xsel or wl-clipboard, sh, awk, make. 41 curl for bm-title, bm-check and bm-sync, jq for bm-import of Chromium files, 42 git for bm-commit. 43 44 == Install == 45 Edit TOOLS in config.mk to choose what gets installed, then 46 47 make install # to /usr/local/bin 48 make PREFIX="$HOME/.local" install 49 50 Each release on GitHub also has an installer for Windows and a portable app 51 for macOS. See Windows and macOS below. 52 53 == Setup == 54 Nothing is required. Bookmarks are kept in ~/.local/share/sbm/bookmarks 55 ($XDG_DATA_HOME is honoured) and the file is created on first use. 56 57 Tag choices are read from ~/.local/share/sbm/usertags: 58 59 <tag><space>|<space><description><newline> 60 61 Start from the included example, or from nothing: a tag typed into the tag 62 menu that is not in the file yet is added to it. 63 64 mkdir -p ~/.local/share/sbm 65 cp usertags engines ~/.local/share/sbm/ 66 67 == Usage == 68 bm browse (see below) 69 bm -o, --open open a bookmark, an address, or a web search 70 (-p, --plumb is the same) 71 bm -c, --copy copy a bookmark's URL to the clipboard 72 bm --print print a bookmark's URL 73 bm -a, --add [url] add a bookmark 74 bm -e, --edit open the file in $VISUAL/$EDITOR at a bookmark 75 bm -d, --delete delete a bookmark 76 bm -l, --list print the bookmarks 77 bm -m, --merge add the bookmark lines on stdin, skipping known ones 78 -t, --tag TAG restrict any of the above to one tag 79 -S, --sort ORDER used (default), recent, url, desc or tag 80 81 Bind "bm" and "bm -a" to keys in your window manager. 82 83 == Browsing == 84 Plain "bm" lists the bookmarks. Pick one and a second menu shows it in full 85 (dmenu cannot preview while you scroll, so this is its preview) and asks what 86 to do: 87 88 open 89 copy 90 edit 91 delete 92 https://suckless.org/ 93 software that sucks less 94 tags: code org 95 used: 12 96 97 Closing the second menu goes back to the list, closing the list leaves. 98 [add] at the top of the list adds a bookmark. 99 100 With fzf (no display, or SBM_MENU=fzf) it is one screen: the bookmark under 101 the cursor is previewed live, and keys do the rest. 102 103 ENTER open ctrl-a add ctrl-s next sort order 104 ctrl-y copy ctrl-d delete (asks) 105 ESC leave ctrl-e edit 106 107 To see the bookmarks of a tag, type the tag. fzf searches the tags too. 108 109 == Web search == 110 In the list, and in "bm -o", text that is not a bookmark is not wasted: 111 112 suckless.org/dwm one word with a dot or "://": opened as an address 113 posix sh printf anything else: searched for on the web 114 w dynamic menu first word is a keyword: that engine is used 115 116 So one key gives you bookmarks first and the web behind them. The default 117 engine is DuckDuckGo; set another with SBM_SEARCH='https://.../?q=%s'. 118 Keywords are read from ~/.local/share/sbm/engines: 119 120 <keyword><space>|<space><url with %s><newline> 121 122 See the included engines file. 123 124 == Adding == 125 Without a url, bm offers the X11 primary selection, or the clipboard, when 126 it holds a URL. With bm-title installed the description defaults to the 127 page's title (curl, 3 second limit; SBM_FETCH=0 turns it off). In dmenu the 128 default is the one entry in the menu: Return accepts it, Shift-Return sends 129 exactly what you typed. 130 131 Tags are chosen one at a time until "done" is picked or the menu is closed 132 (fzf: TAB marks, ENTER confirms). Typing a tag that does not exist creates it. 133 134 A URL that is already bookmarked is refused (exit status 3). URLs are 135 compared without their scheme, a leading www. and trailing slashes. 136 137 == Sorting == 138 used most opened or copied first, then by url (the default) 139 recent most recently added first 140 url, desc, tag 141 142 Set a default with SBM_SORT. Use counts live in "$BOOKMARKS.usage", so the 143 bookmark file itself stays a plain list. 144 145 == A web page of your bookmarks == 146 bm-html > ~/public_html/bookmarks.html 147 148 One self-contained file: no server side, nothing loaded from elsewhere. 149 Bookmarks are listed under each of their tags, with the tags linked at the 150 top. A dozen lines of inline script add a filter box; without script the page 151 is still the complete list, and Ctrl-F works. page.html?q=words starts out 152 filtered, so the page can be a search keyword in your browser, or in bm: 153 154 b | https://example.org/bookmarks.html?q=%s 155 156 Links that would run script (javascript:, data:) are never written. 157 158 == Importing == 159 bm-import bookmarks.html | bm --merge # the HTML export of any browser 160 bm-import vivaldi | bm --merge # or chrome, brave, edge, chromium 161 bm-import path/to/Bookmarks | bm --merge # a Chromium JSON file (needs jq) 162 163 A browser name imports the bookmarks of all profiles of that browser. The 164 profiles are in ~/.config, in ~/Library/Application Support on macOS, or in 165 %LOCALAPPDATA% on Windows. Folder names become tags ("Dev Tools" becomes 166 dev-tools). bm --merge skips what you already have, so importing twice adds 167 nothing. To sync new browser bookmarks into bm, run the same command again. 168 Look before you merge: bm-import prints plain bookmark lines. 169 170 bm-watch does this import at once, and again each time that the bookmark 171 file of a profile changes. It only adds bookmarks, and it never writes to 172 the browser: 173 174 bm-watch brave & 175 176 == Dead links == 177 bm-check 178 179 prints status<tab>url for links that fail, and status<tab>url<tab>new-url for 180 ones that moved for good. It exits 1 if it printed anything. 181 182 == History and sync with git == 183 With bm-commit installed, every change bm makes is committed, provided the 184 directory holding the bookmark file is a git repository of its own. It never 185 pushes or pulls. 186 187 cd ~/.local/share/sbm && git init 188 printf '%s\n' '*.usage' '*.bak*' > .gitignore 189 190 A file that merely sits inside a bigger repository (your dotfiles) is left 191 alone unless SBM_GIT=1. SBM_GIT=0 turns commits off. 192 193 == Sync == 194 bm-sync keeps the bookmark file the same on all your computers, the sbm app 195 for Android and the sbm add-on for Firefox and Chrome. 196 197 bm-sync login # once on each computer: email and password 198 bm-sync # sync now 199 200 When bm-sync is installed and signed in, bm syncs after each change, in the 201 background. Bookmarks that you add or remove on one device are added or 202 removed on the others. The file stays a plain file, and bm works without a 203 network. The sign-in token is kept in ~/.config/sbm/sync. 204 205 An empty or missing bookmark file removes nothing on the other devices: 206 bm-sync gets all the bookmarks back from the server. 207 208 bm and bm-sync lock the file for each change, with the directory 209 $BOOKMARKS.lock, so that neither writes over a change of the other. bm -e 210 holds the lock until the editor ends; bm-sync waits for it, and bm syncs 211 the edit after. A lock left by a process that is gone is taken over. 212 213 The server is sbm-sync (https://github.com/equwal/sbm-sync). bm-sync uses 214 https://sbm.subread.space unless you give another server: create an account 215 there. The server is free software, so you can run your own: 216 217 bm-sync login https://sbm.example.org 218 219 The sbm app for Android is at https://github.com/equwal/sbm-android. A demo of 220 sync between bm and the browser add-on (80 seconds): 221 https://github.com/equwal/sbm-sync#sbm-sync 222 223 == Migrating from the old format == 224 Earlier versions wrote "URL description | tag tag". 225 226 bm-migrate # $BOOKMARKS, or the default file 227 bm-migrate -n file # only show what it would become 228 bm-migrate - < old > new # as a filter 229 230 The original is kept as .bak, and an existing .bak is never overwritten. 231 232 == Configuration == 233 All through the environment, all optional. 234 235 BOOKMARKS bookmark file 236 USERTAGS tag choices file 237 DMENULINES lines shown by dmenu (default 15) 238 SBM_MENU dmenu, fzf, or a custom command 239 SBM_FZF_OPTS extra options for every fzf call 240 SBM_SORT default sort order 241 SBM_SEARCH default search engine, with %s for the query 242 SBM_ENGINES search keywords file 243 SBM_COPY command that reads the new clipboard contents from stdin 244 SBM_PASTE command that writes the clipboard contents to stdout 245 SBM_OPEN command that opens the URL given as its argument 246 SBM_LOCK_WAIT seconds that bm and bm-sync wait for the lock (default 10) 247 248 With a display bm uses dmenu; fzf is only used when dmenu is missing or 249 there is no X11 or Wayland display. A custom SBM_MENU is called with the 250 choices on stdin as one of: 251 252 <command> pick <prompt> print the chosen line, or the typed text 253 <command> multi <prompt> print every chosen line 254 <command> ask <prompt> print free text; stdin holds a default, if any 255 256 Printing nothing means the user cancelled. test/fakemenu is a small example. 257 258 == Windows == 259 bm runs under Cygwin. Get sbm-VERSION-setup.exe from a release on GitHub and 260 start it. The installer puts bm and the bm-* tools in /usr/local/bin. It 261 makes a Start menu entry and a desktop shortcut that open bm in a mintty 262 window, and Esc closes the window. The key of the desktop shortcut is 263 Ctrl+Alt+Shift+F24. Almost no keyboard has an F24 key, so keyboard firmware 264 can use this key combination without conflicts. 265 266 The installer imports the bookmarks of all profiles of the Chromium browsers 267 (Brave, Chrome, Chromium, Edge and Vivaldi). A Startup entry, sbm-watch, runs 268 bm-watch at each login, so new browser bookmarks come into bm a few seconds 269 after you add them. The import needs the jq package of Cygwin. 270 271 The installer also puts fzf.exe, the Windows build of fzf, in 272 /usr/local/libexec/sbm. Do not use the fzf package of Cygwin. That package is 273 version 0.11 from 2016, and it has no --preview and no --expect. The wrapper 274 /usr/local/bin/fzf lets fzf.exe run in mintty. /etc/profile.d/sbm.sh sets 275 SBM_OPEN, SBM_COPY, SBM_PASTE and SBM_FZF_OPTS for Cygwin. Settings in 276 ~/.bash_profile win. 277 278 For bm-title, bm-check and bm-import, install the curl and jq packages of 279 Cygwin. The Windows builds of curl and jq do not understand Cygwin paths, 280 such as /dev/null. To remove sbm, use Apps in the Windows settings. The 281 uninstaller keeps your bookmarks. contrib/windows/build-installer.sh makes 282 the installer with NSIS. 283 284 == macOS == 285 Get sbm-VERSION.dmg from a release on GitHub, and drag sbm.app to 286 Applications. The app contains bm, the bm-* tools and fzf, so it needs no 287 other software. sbm.app opens bm in a Terminal window. The first time, macOS 288 asks if sbm can control Terminal. 289 290 The app has no signature from a registered developer, so macOS stops it the 291 first time. To open it, use Open Anyway in System Settings, Privacy & 292 Security. Or remove the quarantine attribute: 293 294 xattr -dr com.apple.quarantine /Applications/sbm.app 295 296 To open sbm with a key, make a shortcut with the action Open App in the 297 Shortcuts app, and give it a keyboard shortcut. macOS has function keys up 298 to F20. To use the tools in a shell, add these lines to ~/.zprofile: 299 300 PATH=/Applications/sbm.app/Contents/Resources/bin:$PATH 301 export SBM_OPEN=open SBM_COPY=pbcopy SBM_PASTE=pbpaste 302 303 contrib/macos/build-dmg.sh makes the image on a Mac. 304 305 == Tests == 306 make check 307 308 runs shellcheck, when installed, and test/run.sh, under sh. Run the tests 309 under another shell with SBM_SH=dash sh test/run.sh. They script dmenu, fzf 310 and curl, so they need no display and no network. 311 312 == Releases == 313 Set VERSION in config.mk. Tag the release as vVERSION. make dist makes the 314 tarball. contrib/windows/build-installer.sh makes the Windows installer. On 315 a Mac, contrib/macos/build-dmg.sh makes the image. Attach the three files to 316 the GitHub release of the tag. 317 318 == Credits == 319 Some ideas taken from Karl Voit: 320 https://karl-voit.at/2022/01/29/How-to-Use-Tags/ 321 322 == License == 323 AGPL-3.0: see LICENSE. If you run a changed version for other people over a 324 network, give them its source code. 325 326 If sbm is useful to you, you can support it on Ko-fi: 327 https://ko-fi.com/truex