2022-09-05, updated: 2024-03-12
Tested with Nyxt 3-pre-release-2.
Making your own interfaces in Nyxt
Browsers are quite conventional interface-wise. Tab bar, "Back" and "Forward" buttons, address bar (now merged with the search bar, can you believe it?!), all familiar faces. Nyxt's start page, after the recent redesign, is no exception to this familiarity:
While some people may like the status quo design, some don't (including one member of our team). Should everyone stick with the same layout and styling, then? No! Changing the existing browser-internal pages and adding new ones to make the browser better at being a personal Internet window is a right, not a privilege. While this right is respected by some browsers (Firefox allows customising some interface elements), we're far from having the ability to redesign the browser to the finest grain.
Nyxt tries to not get in your way when it comes to customization. If you want to change the interface or add a new page, no one stops you from doing so. Imagine you want to have a weather forecast as your start page. Should be possible in a browser like Nyxt, right?
Simple customization: set your new buffer URL
First, you can just set your "home page" URL to something that shows the weather forecast:
And the next time you open Nyxt, you'll see your weather forecast provided by the website you prefer.
But we're here for something more sophisticated, aren't we?
Making your own start page
You can create your own web pages native to Nyxt, and it should be fairly easy if you know some HTML. The main thing you need is the define-internal-page-command(-global)
macro to define a page.
This macro defines a new internal page that you can open with the URL and command of the same name. So, if you want a weather page define-internal-page-command-global weather
will generate both a weather
command and nyxt:weather
URL for the page this command opens.
Say you want to present the weather forecast from some website that you trust, but you don't like their page design. Why not write a Nyxt page with the design you prefer? Here's an example of how you can do that:
;; Note that this function will be used later.
(defun weather-data (location)
"Get the weather data for LOCATION."
(let* ((location (or location "armenia/yerevan"))
(document (plump:parse
;; Fetch the weather document.
(dex:get (format nil "https://www.timeanddate.com/weather/~a" location))))
(info (elt (clss:select "div.bk-focus__info" document) 0))
;; Find the actual parameters usign CLSS selectors.
(temp (plump:text (elt (clss:select "#qlook div.h2" document) 0)))
(visibility (plump:text (elt (clss:select "tbody tr:nth-child(4) td" info) 0)))
(pressure (plump:text (elt (clss:select "tbody tr:nth-child(5) td" info) 0)))
(humidity (plump:text (elt (clss:select "tbody tr:nth-child(6) td" info) 0))))
(values temp visibility pressure humidity)))
;; Internal commands, much like functions, can accept arguments, but only keyword ones.
(define-internal-page-command-global weather (&key location)
(buffer (format nil "*Weather in ~a*" location))
"Show the current weather in LOCATION."
(multiple-value-bind (temp visibility pressure humidity)
(weather-data location)
;; Lay the parameters out in HTML.
(spinneret:with-html-string
;; Use the default Nyxt UI style.
(:style (style buffer))
(:h1 "Weather")
(:h2 temp)
(:dl
(:dt "Visibility")
(:dd visibility)
(:dt "Pressure")
(:dd pressure)
(:dt "Humidity")
(:dd humidity)))))
Now, if you start Nyxt with this command in your config, you'd be able to execute the weather
command and look at your weather forecast in a nice Nyxt-native style!
Now, we can look at the URL that it shows (nyxt:nyxt-user:weather
) and set this URL as the default start page to make it your default:
Defining your own schemes
Now, if you're not satisfied with adding pages to the cozy and pre-configured nyxt:
scheme, how about writing your own scheme instead? This way, you can make Nyxt support protocols it doesn't support by default, or natively link to Spotify in Nyxt. This is also possible, with the help of define-internal-scheme
!
What this function does is
- registering the scheme in the renderer,
- attaching the processing function to it,
- and setting up the security restrictions for the scheme.
Not much, but more than enough to support almost any scheme there is, including Gopher and Gemini support that Nyxt recently received.
So, how does one define a new scheme, say, weather:
? Supposing that it's going to do the same as the weather
page, it's easy:
(define-internal-scheme "weather"
(lambda (url buffer)
(multiple-value-bind (temp visibility pressure humidity)
(weather-data (quri:uri-path (quri:uri url)))
;; Lay the parameters out in HTML.
(spinneret:with-html-string
;; Use the default Nyxt UI style.
(:style (style buffer))
(:h1 "Weather")
(:h2 temp)
(:dl
(:dt "Visibility")
(:dd visibility)
(:dt "Pressure")
(:dd pressure)
(:dt "Humidity")
(:dd humidity)))))
:no-access-p t)
Most of the code is the same as in the weather
command, except that:
define-internal-scheme
accepts a function of two arguments:- a URL string, and
- the buffer to load it in.
- You can return any type of content, as long as you also return the content type as a second value. Which means that you can return byte arrays for PNG images, providing the
"image/png"
content type. - You can set the security settings of the newly created scheme as an optional keyword argument after the function.
- Not getting anything from the other schemes (
:no-access-p t
). - Ultra secure and leaking nothing to the Internet (
:local-p t
). - CORS-requestable, enabling you to use it as an interactive API for you extensions (
:cors-enabled-p t
). - Web-enabled and a proper Internet citizen (
:secure-p t
)!
- Not getting anything from the other schemes (
With this wealth of settings, you can design full-blown applications based on Nyxt and embedded into it, with schemes behaving the way you prefer! As an example, you can make a weather-json:
scheme to produce JSON for the weather data instead of the page:
(define-internal-scheme "weather-json"
(lambda (url buffer)
(declare (ignore buffer))
(multiple-value-bind (temp visibility pressure humidity)
;; Defined earlier.
(weather-data (quri:uri-path (quri:uri url)))
(values
(nyxt:encode-json `((:temperature . ,temp)
(:visibility . ,visibility)
(:pressure . ,pressure)
(:humidity . ,humidity)))
"application/json")))
:cors-enabled-p t)
An then you can request this scheme via a script on you weather
page, for example. But that we'll leave as an exercise to you :)
This was an overview of the tools to make your own interfaces integrated into Nyxt. We hope that you use those to build the best interfaces there can be and make your browser the most powerful user agent on the Internet :D
P.S. You can use describe-function
on define-internal-page-*
macros and define-internal-scheme
to learn more about them, and get a feeling of the capabilities which extend way beyond the weather start page :P
Did you enjoy this article? Register for our newsletter to receive the latest hacker news from the world of Lisp and browsers!
- Maximum one email per month
- Unsubscribe at any time