poke-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Hyperlinks server in poke


From: Jose E. Marchesi
Subject: Hyperlinks server in poke
Date: Wed, 06 Nov 2019 22:22:44 +0100
User-agent: Gnus/5.13 (Gnus v5.13) Emacs/26.1 (gnu/linux)

Hi people!

So during last weekend's Rabbit Herd Hacking Meeting [1] we hacked a
protocol for terminal hyperlinks.

Well, I'm happy to say poke's terminal hyperlinks server is up and
runnning, and it is looking very good! :)

If you wanna have fun with the hyperserver you will need:

1. To fetch the branch jemarch/hyperserver from the git repo
   https://git.savannah.nongnu.org/git/poke.git and build it.

   Make sure you have a libtextstyle capable of displaying terminal
   hyperlinks.

2. A terminal that understands hyperlinks... I think the latest
   gnome-terminal has good support for them.  I use an Emacs patch
   myself.

3. Bruno's app-client, whose source file is attached.

4. To somehow make your terminal to open app:// uris with app-client.

Ready?  Awesome, lets play :)

First, create three or four test files.  They can be ELF objects or
whatever.  Let's call them foo.o, bar.o and baz.o.

Then, fire up poke.  Among the greetings messages you should see a
mention to the hserver:

  Powered by Jitter 0.9.200.
  Perpetrated by Jose E. Marchesi.

  hserver listening in port 50809.

This indicates your poke can process hyperlinks.  Now open the files
using the .file command:

(poke) .file foo.o
The current file is now `foo.o'.
(poke) .file bar.o
The current file is now `bar.o'.
(poke) .file baz.o
The current file is now `baz.o'.

Now dump a few byts of baz.o:

(poke) dump :size 4#B
...

At this point, list the opened files using the following command:

(poke) .info files
  Id    Mode    Position        Filename
* #0    rw      0x00000020#b    file://baz.o
  #1    rw      0x00000000#b    file://bar.o
  #2    r       0x00000000#b    file://foo.o

Note how the filenames and the current positions are links...

Click on foo.o!  You shall see:

(poke) .file #2
The current file is now `foo.o'.
(poke)

i.e. poke executed the command `.file #2' as the result of clicking on
the link.  This is one of the actions supported by poke in hyperlinks.

But there is another one... imagine you want to see the integer at the
Position of baz.  First click on file://baz.o to make it the current
file again:

(poke) .file #0
The current file is now `foo.o'.

And now, write the following in the prompt _without_ pressing enter (in
the lines below _ means the cursor):

(poke) int @ _

and now click on the Position of baz.o (the 0x00000020#b), you will get:

(poke) int @ 0x20#b_

Now press enter:

(poke) int 0x20#b
whatever

See how the value is inserted in the line editor at the right place
(where the cursor is) in order to complete your command :)

The fun is just starting... there are SO many things that can be
hyperlinked in a program like poke...

Imagine how smooth the interaction can be: from menus printed in a line
("wanna do _a_ or _b_") to clicking on some byte in the output of `dump'
and getting the corresponding offset inserted in the prompt line.

Please reports problems you may find.  I plan to merge the hserver in
master soon...

Finally, for the finest and curious Pokist:

You may be wondering, how do the uris themselves look like?

For an "execute" command (i.e. a link asking poke to execute a command
and show you the result before your current prompt line, which is left
untouched) the uri looks like:

    app://termi:50809/763/e/.file #0

For an "insert" command (i.e. a link that inserts something in the
prompt line) the uri looks like:

    app://termi:50809/1592/i/0x20#b

In the uris above, only the `app://HOSTNAME:PORT/' is part of the simple
`app protocol' we designed last weekend.  The rest is poke's own
protocol: an unique token followed by the command type (e or i) and
finally the command itself.

[1] http://www.jemarch.net/rhhw

/* app-client - Program that dispatches app URIs to the particular program.  */

/* Copyright (C) 2019 Free Software Foundation, Inc.  */

/* This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* Written by Bruno Haible <address@hidden>, 2011, 2019.  */

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/uio.h>

#define VERSION "0.0"
#define MAX_URI_LENGTH 2080 /* see Koblinger's gist */


/* Creates a client socket, by connecting to a server on the given port.  */
static int
create_client_socket (const char *hostname, int port)
{
  /* Create a client socket.  */
  int client_socket = socket (PF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (client_socket < 0)
    {
      fprintf (stderr, "Failed to create socket.\n");
      exit (10);
    }
  /* Connect to the server process at the specified port.  */
  {
    struct sockaddr_in addr;

    memset (&addr, 0, sizeof (addr)); /* needed on AIX and OSF/1 */
    addr.sin_family = AF_INET;
    inet_pton (AF_INET, hostname, &addr.sin_addr);
    addr.sin_port = htons (port);

    if (connect (client_socket, (const struct sockaddr *) &addr, sizeof (addr)) 
< 0)
      {
        fprintf (stderr, "Failed to open socket %s:%d\n", hostname, port);
        exit (11);
      }
  }

  return client_socket;
}

static void
usage (FILE *out)
{
  printf ("Usage: printf '%%s' URI | app-client\n");
}

int
main (int argc, char *argv[])
{
  if (argc > 1)
    {
      if (strcmp (argv[1], "--help") == 0)
        {
          usage (stdout);
          exit (0);
        }
      if (strcmp (argv[1], "--version") == 0)
        {
          printf ("app-client " VERSION "\n");
          exit (0);
        }
      fprintf (stderr, "Invalid argument '%s'\n", argv[1]);
      usage (stderr);
      exit (1);
    }

  char uri[MAX_URI_LENGTH + 1];

  size_t n = fread (uri, 1, sizeof (uri), stdin);
  if (n == 0)
    {
      fprintf (stderr, "No URI given on standard input.\n");
      exit (2);
    }
  if (n > MAX_URI_LENGTH)
    {
      fprintf (stderr, "URI too long.\n");
      exit (3);
    }
  /* NUL-terminate, for simplicity.  */
  uri[n] = '\0';

  /* Verify the URI starts with "app:".  */
  if (!(n >= 4 && memcmp (uri, "app:", 4) == 0))
    {
      fprintf (stderr, "URI does not have the 'app' protocol.\n");
      exit (4);
    }

  /* Dissect the URI and verify its syntax: app://hostname:port/payload.  */
  if (!(uri[4] == '/' && uri[5] == '/'))
    {
      fprintf (stderr, "URI syntax error.\n");
      exit (5);
    }

  char *colon_pos = strchr (uri + 6, ':');
  char *slash_pos = strchr (uri + 6, '/');
  if (!(colon_pos != NULL && (slash_pos == NULL || colon_pos < slash_pos)))
    {
      fprintf (stderr, "URI syntax error.\n");
      exit (5);
    }

  const char *hostname = uri + 6;
  *colon_pos = '\0';

  if (*hostname == '\0')
    {
      fprintf (stderr, "URI syntax error.\n");
      exit (5);
    }

  char *port_str = colon_pos + 1;
  if (slash_pos != NULL)
    *slash_pos = '\0';

  long port;
  char *end_of_port;
  errno = 0;
  port = strtol (port_str, &end_of_port, 10);
  if (*end_of_port != '\0' || errno != 0 || port <= 0 || port >= 65535)
    {
      fprintf (stderr, "URI syntax error.\n");
      exit (5);
    }

  char empty_string[1] = { '\0' };
  char *payload = (slash_pos != NULL ? slash_pos + 1 : empty_string);

  /* Open the port on the hostname.  */
  char my_hostname[64];
  if (gethostname (my_hostname, sizeof (my_hostname)) >= 0
      && strcmp (my_hostname, hostname) == 0)
    /* Use localhost instead of my_hostname.  */
    hostname = "localhost";

  /* Send the payload and a newline.  */
  int client_socket = create_client_socket (hostname, port);
  size_t remaining = strlen (payload) + 1;
  payload[remaining - 1] = '\0';
  do
    {
      ssize_t written = write (client_socket, payload, remaining);
      if (written <= 0)
        {
          fprintf (stderr, "Failed to write to socket %s:%d\n", hostname, 
(int)port);
          exit (12);
        }
      payload += written;
      remaining -= written;
    }
  while (remaining > 0);

  exit (0);
}


reply via email to

[Prev in Thread] Current Thread [Next in Thread]