5.8 KiB
Dunlin - a extensible Fennel-based web browser
The dunlin (Calidris alpina) is a small wader. It is a circumpolar breeder in Arctic or subarctic regions. Birds that breed in northern Europe and Asia are long-distance migrants, wintering south to Africa, southeast Asia and the Middle East. Birds that breed in Alaska and the Canadian Arctic migrate short distances to the Pacific and Atlantic coasts of North America, although those nesting in northern Alaska overwinter in Asia.
Dunlin is a GTK-based Webkit browser which can be extended in (indeed, is mostly written in) Fennel.
Why does the world need another half-assed Webkit browser?
It doesn't, really. But me, personally, I think I do. I want a browser that lets me easily customize it to do things like:
- watch a local html file and reload when it changes
- find the open tab for my home Fediverse instance (or open one if needed) and post a message/share a link to what I'm looking at
- search the {Lua reference, Ruby docs, GTK docs} for a given term instead of wading through the SEO cesspool that is the internet in 2022
- search in previously "liked" pages for the thing I found three weeks ago, instead of ... ditto
- find the tab that's playing music and pause the player (instead of muting it)
- more stuff I haven't thought of
Full disclosure: Dunlin today can do none of these things - or much of anything else either, right now - but if I borrow enough design decisions from Emacs, I hope some day it will.
Installation and getting started
If you're using Nix, you can run nix-shell
to pull all the
dependencies. Otherwise ... apt-get and Luarocks? Refer to
default.nix
and shell.nix
, and obtain:
- All Lua packages listed in any
lua5_3.withPackages
stanza - All system packages listed in any
buildInputs
ornativeBuildInputs
stanzas.
To run the browser,
$ fennel dunlin.fnl
Dunlin will open a socket in $XDG_RUNTIME_DIR
to allow communication
with a Fennel REPL. You can run the repl.sh
script to connect to it
with socat.
Running tests
Tests are in test/*.fnl
. The test coverage is not 100% nor will it
ever be, probably: tests exist only for the bits that were hard to
write (algorithmically complex) and easy to test (not full of UI).
make watch
will watch the filesystem in this directory and run the
tests whenever something changes.
Running everything
There's a Procfile
that starts the test runner and a Fennel repl and
probably in time some other useful things. I use it with Overmind - overmind start -D; overmind connect
to create a tmux session.
Customizing
This is all quite fluid right now and I reserve the right to change things. In particular, I anticipate the need to bind to non-key events (mouse events, on-screen buttons, touch gestures)
(Among) the concepts you need to know here are "commands" and "bindings".
-
A "command" is a chunk of code that may be invoked interactively, plus a descrition of the parameters it needs and their default values. For example,
visit-location
needs aurl
parameter for the location and abuffer
parameter that says which tab should visit it. -
A "binding" is a sequence of keystrokes which map to a command: it may supply zero or more parameter values to the command. For example, (as of git commit cde0b8cd56d; YMMV if you're reading this significantly before or after 2022-12-23) the
g
binding invokesvisit-location
with thebuffer
parameter set tomain
, and theurl
parameter unset.
When a command is invoked without all parameter values, Dunlin will
prompt for each missing parameter in the "commander" text entry widget.
A command with no binding may be invoked by pressing M-x
(hold down
the "meta" or "alt" key and press x
) and then typing the command name.
To see how commands are implemented, read the code in command.fnl
.
There is a simple keymap in dunlin.fnl
, and you can see the details
of how keymaps work in keymap.fnl
When writing key bindings or printing errors, Dunlin assumes that the key producing "Mod 1" (often labelled Alt) is the Meta key, and the key producing "Mod 4" (on a PC, typically the key with the Windows logo) is the Super key. For me this matches how Emacs does it, but I would welcome reports of machines/setups that don't act ths way
Fennel style
https://fennel-lang.org/style is my style bible, although there are
places I have departed from it. In particular, I have used CamelCase
to name several modules which act as classes, so that I can
distinguish buffer
(an object which represents a buffer, and
understands messages like visit
or name
or location
) from
Buffer
(the factory which instantiates buffer
objects). There is
probably a nicer way to do this, but I haven't found it yet.
Contributing
If you like (or see the potential in) Dunlin enough to want to contribute to it, that is awesome and you have just made my day!
However, I need to point out that it's under active development: my vision for how it'll end up is indefinite in some places and poorly articulated in others. So, please, don't send me code that you're personally invested in and would be disappointed if I reject/ignore it because it doesn't fit some plan that you didn't know about when you wrote it.
You can checkout Dunlin from https://gti.telent.net/dan/dunlin. I haven't put it on Github because I have uneasy feelings about the ongoing centralisation of Open Source, but certainly don't let that stop you. You can email me your change as a patch, or you can point me at code on your preferred source code hosting service - whatever works for you. I reserve the right to be less flexible in future if I start drowning in changes.