Take control of your web.

Weaver is a qt5-webengine browser with most of the user control provided via a local tcp socket connection. Weaver focuses on displaying web pages and offering highly customizable and scriptable user control mechanisms. There is no gui other than the windows that display web content. All behavior is controlled by sending commands to the socket connection and / or running userscripts (example scripts include extensive key bindings, hinting, and styling).

Weaver also provides it's own scheme handler to process commands from within the browser context. This means that your javascript userscripts can send commands to the browser via ajax calls or even simple invocations like the following:

location.href = "weaver://<command>" + WeaverPID

location.href = "weaver://<command>/<args, even with spaces>" + WeaverPID

Commands can also be passed to the socket (default localhost:9090) in the same format as the weaver url scheme above, just without the leading weaver: or the trailing WeaverPID. A front-end script is provided to facilitate sending commands to the socket, but feel free to roll-your-own. But if you do, you may want to first review the examples section below.

Note: WeaverPID is defined in js on every page as the PID of the weaver server process. The inclusion of this in the weaver scheme commands ensures that javascript from actual web pages could not send commands to your browser. I would love for weaver to someday be popular enough to be a target of malicious JS for this security measure to matter!

Getting Started

The weaverServer process must be running in the background before the front-end script can send commands. The front-end script will launch the server if passed the -d flag (alternatively, you can run WeaverServer, which is installed to the /lib/ directory, directly):

weaver -d

Note that all browser windows are controlled by this initial server process, not the front-end client. If gui processes need to be launched from a certain environment, it's this weaver -d command that must be launched in that manner. Subsequent calls to send socket commands can come from anywhere. For a specific example, if you are running i3 or sway, only the weaver -d command must be launched with one of the following:

swaymsg exec 'weaver -d'

i3msg exec 'weaver -d'


The examples below do not start with obvious things like how to open a url. This is intentional. The list starts with commands that are "simplest" in terms of being the same when given from the front-end script versus directly to the socket. As you work through the list, you will learn how these two mechanisms diverge. Opening a url with the front-end script works likely just as you would expect, but sending it directly to the socket has some caveats that we'll work up to below.

Note that the netcat implementation used for the examples below has the reasonable behavior of exiting when it gets a EOF on stdin. If you're netcat implementation does not behave in this way by default, you may need to add the appropriate flag (-c for gnu-netcat or -N for openbsd-netcat ... alternatively you can use socat - in place of nc).

  1. List windows:

    weaver //list


    echo //list | nc localhost 9090

  2. List windows with different format - e.g., just the title:

    weaver //list/%1


    echo //list/%1 | nc localhost 9090

  3. Close the second window in the list

    weaver //target/2 //close


    echo //target/2 | nc localhost 9090; echo //close | nc localhost 9090

    Note 1: windows will be renumbered after this as the //target command pulls the window from position 2 and puts it in position 1, then the //close command closes the window at position 1 and all remaining windows shift down to fill the space in the list.

    Note 2: all content sent on a single socket connection is seen as a single command, so if you use netcat or a related tool, you cannot echo more than one command at once. This is one of the benefits of the weaver script as the script creates one connection for each command line argument.

  4. Open duckduckgo.com on the third window in the list

    weaver //target/3 duckduck.com


    echo //target/3 | nc localhost 9090; echo //open/duckduckgo.com | nc localhost 9090

    Note 3: here's another benefit of the script: the script appropriately prepends //open to any non-command input so you can give it urls, search terms, or filenames as you'd expect to be able to.

  5. Open a local file

    weaver mydocument.pdf


    echo //open/file:///full/path/to/mydocument.pdf | nc localhost 9090


    echo //open/file://$(realpath mydocument.pdf) | nc localhost 9090

    Note 4: yeah, the script really helps with this too by prepending //open/file:// and the appropriate path.

  6. Run javascript to get the current url

    weaver //js/location.href //sleep //js


    echo //js/location.ref | nc localhost 9090; sleep 0.1; echo //js | nc localhost 9090

    Note 5: the //js command behaves differently when provided arguments versus when not. With arguments it begins running the script, without arguments it checks for the results of the last script run on the targetted window.

    Note 6: the //sleep command is not actually a server command, but is handled by the weaver script. By default it sleeps for 0.1s, but other durations can be provided as //sleep/1.5 or //sleep/3 to sleep for 1.5s or 3s respectively. For location.href you'd be fine a vast majority of the time with no //sleep command at all, but for more complex js calls that return values, the //js call to get the result may report that there is not yet any data available unless you add a short //sleep (or just wait a moment before running the second //js command).

  7. If all you want is the url, you're better off using info command rather than js

    weaver //info/%2


    echo //info/%2 | nc localhost 9090

  8. The info command has various options. Get all the text content of the page:

    weaver //action/SelectAll //info/%8

  9. Open several urls in one go:

    weaver calendar.google.com weaver //target/0 darksky.net //target/0 bbs.archlinux.org

    Note 7: the target command can be given a window index, or if passed 0, as above, it will create a new window to be the target of subsequent commands.


Now that you've gotten a bit of the flavor of how commands work, here's the reference list of all currently available commands. Arguments in brackets are optional. Below the table are notes on a few of the commands that may require further explanation.

Command Arguments Description
action action name Run the specified WebAction in the target window
close N/A Close the currently targetted window
devtools N/A Alias for inspect
downloads [format] Display list of downloads following format
exit N/A Alias for quit
help N/A Open this README on the project webpage
incognito N/A Alias for profile with no argument (i.e., ram-only profile)
info [format] Provide information about the focused window following format
inspect N/A Launch a devtools / inspector page for the target window
javascript [script content] Alias for js
js [script content] W/ args, send script to target to run; w/o, check for results
list [format] List every open window with it's index and info
ls [format] Alias for list
open url / search Open the provided url, or send provided text to the search page
profile name Create a new profile named name in the targetted window
quit N/A Shut down the weaver server
target index Target the window by index or create a new window if 0
ua user-agent-string Sets a new user-agent-string for the targetted window
useragent user-agent-string Alias for ua


The list of available actions and their names can be found here. For example //action/Back would navigate back one step in the history.


Format for the download list should first be customized in your config.conf. The sample config file provided in the examples directory documents the format specifiers that can be used. The //downloads command only requires a format string as an argument if you want to override what is in the config file.

//info/, //list/

Either of these commands can be used without arguments and will provide a useful default. But a format string can be provided to customize the output. Format specifiers for this string are in the table below. The default for //info is "%1\t%2" while //list just prepends a window number (that can be used for //target).

Placeholder Description
%1 Window title
%2 Full url
%3 Url scheme (e.g., "http")
%4 Url host / domain name
%5 Url path
%6 Url file name
%7 Url query string
%8 All selected text on the page


All javascript calls are run asyncronously. If you do not need the result of the script you provide, then you can use just one call to this command. For example, to popup an alert in the currently targetted window you'd use //js/alert('Hello World');. This would need to be quoted appropriately to be used on the command line to prevent the shell from griping about the parentheses:

weaver "//js/alert('Hello World');"

If you want to get the result of the script which is the value of the last line of the script, you need to make a second call to the //js command but with no parameters to request any result data available. The //js command will either return a message that no data is (yet) available, which means the script is still running, or it will return the result (which may be an empty string).

For trivial js calls like //js/location.href you could pass both commands at once and likely have success:

weaver //js/location.href //js

However for any substantial script, there is potential that the results might not be available yet for the second command. It's recommended to either wait a moment between entering the two, or use the front-end script's built in //sleep command:

weaver "//js/longFunction()"
weaver //js


weaver "//js/longFunction()" //sleep //js

The above assumes longFunction() was already defined on the page, perhaps by a userscript.


The profile command should be provided with a name as a parameter. This name will identify the alternative profile to be used in the target window. It will also define the on-disk name of the profile. If this is an empty string, it will result in a RAM-only profile in which no content is stored to disk. This is often called private mode or incognito mode (thus the //icognito alias for this purpose).

Note, however, that this change in profile applies only to the targetted window. Any subsequently created windows will revert to using the default profile.


Target must be passed an integer parameter and this must either match the index provided in //list output to identify a target window, or it must be 0 to specify that a new window should be created and targetted for subsequent commands.

Note that window indices are dynamic. Internally a list of open windows is maintained. The order of this list can be changed explicitly by //target/ commands, but can also be changed every time a window is focused or brought to the front. In these cases, the focused / targetted window is popped from it's previous position in the list and prepended to be at the first position. So if you check a window's index from the //list command, then cycle through windows, that index will no longer point to the same window.

If no target is explicitly specified, most commands will act on the first window in the list (generally the most-recently focused weaver window). Some commands (those that will change window content like close, open, action, etc) will only act on either the currently focused or explicilty targetted windows.


Please see the well commented config.conf in the examples directory. Copy this file (or create your own) under $XDG_CONFIG_HOME/weaver/

User Scripts

User scripts are javascript. Again, userscripts are javascript. Not a subset, not a dialect. Anything you can do in javascript can be done in userscripts (and actually a lot more as userscripts can access the weaver:// scheme to send browser commands).

Examples of some of what can be done with userscripts are provided in the *.js files in the examples directory. These include key bindings, hinting, image viewing, and some custom CSS. User submissions of other interesting scripts are most welcome on the ticketting system.


Google services' sign in page actively blocks connections from browsers with QtWebEngine in their user agent string. You can quite easily use google services (calendar, gmail, etc) in weaver, but you need to do the following steps just once first.

  1. Set the user agent to a non-webengine string
  2. Log in to a google service
  3. Reset the user agent or just close the ua-spoofing window
  4. From then on you will be able to access google services with out any spoofing

The following command will do step 1 and allow you to do step 2:

weaver //target/0 "//ua/Mozilla/5.0 (Linux x86_64; rv:90.0) Gecko/20100101 Firefox/90.0" calendar.google.com


There most definitely are some, and there will be more. If you see them before I do, feel free to submit detailed descriptions via the ticketting system.

Current issues under investigation

Accepting a download results in a WebPage::onLoadFinished(false) and "[ERROR] " in the title

  • This is entirely begnin and just a cosmetic annoyance.
  • There is no actual 'ERROR' from the page.

Note: this should be fixed now, but still needs testing

Shift+Insert pastes clipboard rather than primary selection in Qt. This is far from ideal. Options under consideration:

  1. Override event handler to catch this key and ... still not sure how to paste primary
  2. Javascript in keys.js to paste ... but I don't think js can get primary selection
  3. 1+2 runJavascript to paste from event handler?

  4. 2+helper: add weaver://selection type command that returns primary selection content. Then in keys.js handle S+insert ...

Note: middle-click still pastes the primary selection. Perhaps a workaround for shift-insert is not worth the effort.

Intel graphics systems do not get along well with chromium / webengine as discussed here:


For now ... weaver may have to be periodically restarted.