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


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