FOSDEM 2020 is over, and hyperlink support has just landed for GNU Poke!
What do hyperlinks, a web concept, mean for GNU Poke, a terminal application?
For many years now, terminal emulators have been detecting
http://
URLs in the output of any program and giving the user
a chance to click on them and immediately navigate to the corresponding
web page. In 2017, Egmont Kob made a
proposal
for supporting general hyperlinks in terminal emulators. Gnome Terminal,
iTerm and a few other terminal emulators have already implemented this
proposal in their latest releases. With Egmont's proposal, an application
can emit any valid URI and have the terminal emulator take the user to
that resource.
A few applications, such as systemd
, GNU Coreutls
and GNU Wget2
have already incorporated hyperlink support.
These applications use hyperlink support for one of two things:
file://
URIs to allow the user to click on a filename
and open it using an application of their choice.
While these are nice ways to use the new hyperlink support in terminal emulators, there's so much more that can be done. GNU Poke intends to use hyperlinks to really change how we interact with command line applications. Take a look at this demo video of how such an interaction may look like:
As you can see, one can now simply click on commands printed on the screen to execute them. Similarly, you can change the currently mapped file by simply clicking on the filename in the terminal. In the future, a lot more hyperlinks are planned, making navigation within Poke incredibly easy, and all this without using ncurses.
When Bruno Haible first introduced to us the concept of hyperlinks in
terminal emulators, we were intrigued. This has the potential to be a
paradigm shifting change in how we interact with modern terminal emulators.
Clicking on the output of ls
and playing the video in
mpv
, or viewing the image in feh
, etc. is quite
cool. This is what the future looks like!
And yet we had to ask, "What next!?". Opening files and applications is all nice and dandy, but we wanted more. We wanted to drastically change how users can interact with certain applications. To us, these hyperlinks were most promising in the context of non TUI interactive programs.
Yes, most terminal users love their keyboards, but in today's world we almost always have a mouse at hand. And sometimes, clicking is indeed faster than typing.
So, during one of the Rabbit-Herd Hacking Weekends
(RHHW), we sat and designed a
protocol for interactive applications to use. This is a very simple protocol
based on a new URI handler, app://
or appsocket://
.
I will give a brief description of the protocol here; the full text of the
proposal is available in Bruno's
email to freedesktop.org.
app://
protocol]
Bruno's email introduces the appsocket://
protocol. During the
first iteration of this protocol, we called it simply app://
.
Thus, GNU Poke implements the app://
protocol. However, it is
identical to the protocol suggested by Bruno. Once standardised, Poke will
switch to using the correct version of the name.
Following from RFC3986, the URI syntax for our proposal would be:
URI = scheme ":" hier-part
scheme = "app"
hier-part = "//" authority payload-abempty
authority = host ":" port
payload-abempty = *( "/" segment )
query
and fragment
are currently not supported in
app://
URIs. However, support for those can be added if the
need arises.
The authority
element removes the userinfo
part
since the original proposal for hyperlinks did not use it either.
In easier terms, this translates to:
app://hostname:port/payload
That's it! The protocol is that simple.
The payload is completely application specific. We decided to leave things
like security, multiple-clicks, etc. to the application developer since
these vary wildly in usage. While, in some application a link should be
valid only once, for another it could be reused even across different
invocations of the application.
This is what a real hyperlink generated by Poke looks like:
app://ankh-morpok:46603/1161/e/.help
\___/ \_________/ \___/ \__________/
| | | |
Protocol Hostname Port Payload
The application then listens on the given port for incoming connections carrying a payload. It is the terminal emulator's responsibility to open a connection to the port and handover the payload to the application.
Since, no terminal emulators (except emacs, jemarch hacked that during the
same weekend), support this protocol yet, we designed a small external
utility called
app-client
to perform the task. XDG compliant terminal emulators can be configured to
start app-client
for app://
URIs. Steps for
configuring the clients are shown later.
As mentioned above, the payload is left to the discretion of the application program. For GNU Poke, the payload is described by the following grammar:
payload-abempty = "/" nonce "/" command "/" parameters
nonce = string
command = "i" ; insert into readline prompt
/ "e" ; execute a command
parameters = *(pchar)
That is, the payload is made up of three different components separated by the path separator, "/":
nonce
: A unique string for each hyperlink emitted
command
: The type of command this hyperlink represents.
Currently, this is one of execute or insert
parameters
: Additional information that needs to be sent along
with the command
Together, a real payload looks like this:
Command
|
1161/e/.help
\__/ \___/
| |
Nonce Parameters
Nonce
The nonce
is a unique string generated for each hyperlink. GNU
Poke stores each generated nonce
in memory and verifies the
nonce for each payload it receives on the open port. This safeguards GNU
Poke against:
Ideally, the nonce
is a randomly generated string of fixed or
varying length. However, the current implementation of hyperlinks support in
Poke is rudimentary and only generates a 4-digit random number as
the nonce
.
Currently, Poke also does not verify if the nonce it receives matches the
command it was created for. Hence, it is susceptible to a reuse attack,
where a known good nonce can be used to execute an arbitrary number of
commands. This is a known issue and we will get to fixing it as soon as time
permits.
Command
The command
identifies the type of hyperlink it is.
Currently, it can be one of two types, insert or
execute.
0x00000000#b
into the user's readline prompt.
app://ankh-morpok:46603/1592/i/0x00000000#b
app://ankh-morpok:46603/7623/e/.file #0
Parameters
parameters
are the additional information required by the
command.
In case of insert commands, this is the exact text that will be
put into the readline prompt.
In case of execute commands, this is the Poke command along with
any data that will be executed.
Since the parameters may contain reserved characters, it is preferable to percent encode the parameters before emitting the hyperlink. GNU Poke though, currently takes a shortcut and does not percent encode its hyperlinks. This too will be fixed shortly.
Step 1. Compile a new version
of libtextstyle
libtextstyle
is a library for managing the output of structured and formatted data to the
terminal. GNU Poke uses libtextstyle
for handling coloured
outputs and also for printing hyperlinks.
Since hyperlink support was added fairly recently to libtextstyle
,
there exists no release with support for printing hyperlinks. Hence to have
Poke emit hyperlinks, it is required to build a version of libtextstyle
from git. Since libtextstyle
is currently distributed as a part of
GNU Gettext, one needs to compile the latest git version of that.
$ git clone https://git.savannah.gnu.org/git/gettext.git
$ cd gettext
$ ./gitsub.sh pull
$ ./autogen.sh
$ ./configure
$ make
$ sudo make install
Or, if you use Arch Linux, then simply install gettext-git
using your favourite AUR manager.
Step 2. Get a terminal emulator that supports hyperlinks
Gnome Terminal has support for displaying hyperlinks as do many other emulators that rely on VTE. Check the list here for a mostly up-to-date, non-exhaustive list of emulators that support printing hyperlinks. In the example above, I used Gnome Terminal.
Step 3. Make your terminal emulator
understand app://
URIs
Since app://
is a new URI protocol that we designed, common
terminal emulators don't know what to do when they encounter such a URI. To
work around this problem we use the XDG Desktop Specification and
app-client
.
By setting app-client
as the default handler for
app://
URIs, the terminal emulator does not need to understand
the syntax or semantics of the app://
protocol. It offloads
the handling of the URI entirely to app-client
. In order to
use this, first download and install app-client
:
$ git clone https://gitlab.com/darnir/hyperlink-app-client
$ cd hyperlink-app-client
$ make
$ cp app-client /location/in/$PATH/variable
Next, we need to register the app-client
as the handler for the
app://
URIs. This is a two-step process:
Copy the app-client.desktop
file in the git repository to
$HOME/.local/share/applications
. This is a XDG Desktop
Entry for the app-client. And lets most applications on your system know
that this should be used to handle app://
URIs. (See the
MimeType
field)
However, Gnome Terminal doesn't use xdg-open
to start the
applications. Instead, it parses the mimeapps.list
file
manually to find the right application. Edit your mimeapps.list
,
it is usually located at $HOME/.local/share/applications/mimeapps.list
,
but it might also be at $XDG_CONFIG_DIR/mimeapps.list
, and
add the following line to it:
x-scheme-handler/app=app-client.desktop
This lets Gnome Terminal know how to open app://
links.
Step 4. Compile Poke!
This is the easy part! Just clone GNU Poke and build it.
Instructions and other build requirements are mentioned in the
HACKING
file.
Remember to pass --enable-hserver
during ./configure
to enable the hyperlink support.