[Top][All Lists]

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

new console client for ncurses

From: Marcus Brinkmann
Subject: new console client for ncurses
Date: Thu, 13 Jun 2002 02:51:36 +0200
User-agent: Mutt/1.3.28i


here is my current version of the ncurses console client.  If you don't want
to do some debugging with the current console server it will be pretty
useless, though.  Of course, the important work has been done in the console
server, but as this is the client to go with those changes, I post it here.

The code is probably worse than the last version.  The reason is that
this is now using shared memory and file notification messages for the
communication, and the details of this are quite hairy.  It turns out
that the communication protocol has some hidden quirks.  Nothing that
is unsolvable, and nothing that is really unexpected, but it leads to
some messy code (like the clipping calculation that checks if a changed
part of the file is currently displayed).  Actually, most of the
problems are directly the result of using a ring buffer.  I don't
want to give up on the ring buffer, as it allows for very efficient
scrolling and scrollback buffers on the server side, and in general
is a very good idea.  But with a ring buffer, you always have the
boundary case that a region spans over the end of the physical buffer.
(If you want an exercise, rewrite display.c:screen_shift_left() without
using a loop, but by calculating the boundaries and using wmemmove,
wmemcpy and wmemset.  It's mind boggling).

To solve this at least partially, I will make some hard assumptions about
the communication protocol between the server and client.  I will abuse
the notification interface a bit.  In particular with EXTEND and TRUNCATE,
which will always come in pairs, and signal scrolling rather than file
size changes, and the order in which they appear actually matters (if
you first truncate, then extend, you are scrolling towards the front of
the file, if you first extend, then truncate, you are scrolling to the
right.  This distinction is necessary to make [start:end] with start>end
disambiguously referring to linear regions spanning across the physical end
of the ring buffer).  But luckily, I can hide all this mess in a
libconsole-client, and let all clients share the code and not bother about
the protocol details like this one.  But this library is not written yet.
Which is why this program is so messy.

And of course, the protocol is not finished yet at all.  For now, my main
goal is to make it functional, which involves a few short cuts here and
there.  It will be made nicer a bit later.  If you tried the last version,
you will notice that this one has more bugs: scrolling doesn't work
correctly (there is a grave clipping bug, and the scrolling notifications
are not implemented), and probably other things don't work well either.
But internally it has improved because all the server/client infrastructure
is there now (almost, I still need to enable seqnos in the client and process
the notification messages in order).

Compile with something like this (ourfs_notify.defs from console/ in the
Hurd sources):

mig -n -server ourfs_notifyServer.c ourfs_notify.defs

console-client-curses: console-client-curses.o ourfs_notifyServer.o
        $(CC) console-client-curses.o ourfs_notifyServer.o -lncursesw -lports 
-lthreads -o console-client-curses


`Rhubarb is no Egyptian god.' Debian http://www.debian.org brinkmd@debian.org
Marcus Brinkmann              GNU    http://www.gnu.org    marcus@gnu.org

/* console-client-curses.c -- A console client based on curses.
   Copyright (C) 2002 Free Software Foundation, Inc.
   Written by Marcus Brinkmann.

   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 2, 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
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <wchar.h>
#include <error.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/mman.h>

#include <ncursesw/curses.h>

#include <cthreads.h>

#include <hurd/hurd_types.h>
#include <hurd/ports.h>

#include "console.h"

struct mutex ncurses_lock;

input_loop (any_t fdp)
  int fd = 0;
  int wfd = *(int *) fdp;
  fd_set rfds;

  FD_ZERO (&rfds);
  FD_SET (fd, &rfds);

  while (1)
      int ret;

      FD_SET (fd, &rfds);

      ret = select (fd + 1, &rfds, 0, 0, 0);
      if (ret == 1)
          char buffer[100];
          char *buf = buffer;
          size_t size = 0;

          mutex_lock (&ncurses_lock);
          while ((ret = getch ()) != ERR)
              switch (ret)
                case KEY_DOWN:
                  /* etc */
                  /* Do something */
                  buf[size++] = ret;
                  assert (size != 100);
          mutex_unlock (&ncurses_lock);
              ret = write (wfd, buf, size);
              if (ret > 0)
                  size -= ret;
                  buf += ret;
          while (ret != -1 || errno == EINTR);

mvwputsn (wchar_t *str, size_t len, int x, int y)
  cchar_t chr;
  wchar_t wch[2] = { L'\0', L'\0' };

  move (y, x);
  while (len)
      int ret;

      wch[0] = *str;
      ret = setcchar (&chr, wch, 0, 0, NULL);
      /*      if (ret == ERR)
          printf ("setcchar failed: %s\n", strerror (errno));
          printf ("[%lc]\n", wch[0]);
          assert (!"Do something if setcchar fails.");
      ret = add_wch (&chr);
      /*      if (ret == ERR)
          printf ("add_wch failed: %i, %s\n", ret, strerror (errno));
          printf ("[%lc]\n", wch[0]);
          assert (!"Do something if add_wchr fails.");

static struct cons_display *disp;
static mach_port_t disp_notify;

dir_changed (mach_port_t notify_port, dir_changed_type_t change,
             string_t name)
  return EOPNOTSUPP;

file_changed (mach_port_t notify_port, file_changed_type_t change,
              off_t start, off_t end)
  static struct cons_display _cdisp;
  static struct cons_display *cdisp = &_cdisp;

  if (notify_port != disp_notify)
    return EOPNOTSUPP;

  switch (change)
      /* Always sent first for sync.  */
      mutex_lock (&ncurses_lock);  
      *cdisp = *disp;
      mvwputsn (disp->_matrix + cdisp->screen.cur_line * cdisp->screen.width,
                ((cdisp->screen.lines - cdisp->screen.cur_line
                  < cdisp->screen.height)
                 ? cdisp->screen.lines - cdisp->screen.cur_line
                 : cdisp->screen.height) * cdisp->screen.width, 0, 0);
      if (cdisp->screen.lines - cdisp->screen.cur_line < cdisp->screen.height)
        mvwputsn (cdisp->_matrix,
                  2000 - (cdisp->screen.lines - cdisp->screen.cur_line)
                  * cdisp->screen.width, 0,
                  cdisp->screen.lines - cdisp->screen.cur_line);
      move (cdisp->cursor.row, cdisp->cursor.col);
      refresh ();
      mutex_unlock (&ncurses_lock);  
      /* File data has been written.  */
      if (start == offsetof (struct cons_display, cursor.col))
          int status_changed = 0;
          mutex_lock (&ncurses_lock);
          cdisp->cursor.col = disp->cursor.col;
          cdisp->cursor.row = disp->cursor.row;
          if (end <= offsetof (struct cons_display, cursor.status))
            cdisp->cursor.status = disp->cursor.status;

          move (cdisp->cursor.row, cdisp->cursor.col);
          if (status_changed)
            curs_set (cdisp->cursor.status
                      ? (cdisp->cursor.status == 1 ? 1 : 2) : 0);
          refresh ();
          mutex_unlock (&ncurses_lock);
      else if (start == offsetof (struct cons_display, screen.cur_line))
          uint32_t new_cur_line;
          int scrolling;
          mutex_lock (&ncurses_lock);
          new_cur_line = disp->screen.cur_line;
          if (end <= offsetof (struct cons_display, screen.scr_lines))
            cdisp->screen.scr_lines = disp->screen.scr_lines;
          scrolling = new_cur_line - cdisp->screen.cur_line;
          if (scrolling < 0)
            scrolling += cdisp->screen.lines;
          idlok (stdscr, TRUE);
          scrollok (stdscr, TRUE);
          scrl (scrolling);
          refresh ();
          idlok (stdscr, FALSE);
          scrollok (stdscr, FALSE);
          cdisp->screen.cur_line = new_cur_line;
          mutex_unlock (&ncurses_lock);
      else if (start >= offsetof (struct cons_display, _matrix))
          /* For clipping.  */
          off_t size = cdisp->screen.width * cdisp->screen.lines;
          off_t vis_start = cdisp->screen.width * cdisp->screen.cur_line;
          off_t vis_end = (cdisp->screen.cur_line + cdisp->screen.height)
            * cdisp->screen.width - 1;
          start -= sizeof (struct cons_display);
          start /= sizeof (wchar_t);
          end -= sizeof (struct cons_display);
          end /= sizeof (wchar_t);
          if (start > end)
            end += cdisp->screen.width * cdisp->screen.lines;
          if (start < vis_start)
            start = vis_start;
          if (end > vis_end)
            end = vis_end;
          if (start <= end)
              mutex_lock (&ncurses_lock);
              mvwputsn (disp->_matrix + start,
                        end < size
                        ? end - start + 1
                        : size - start,
                        (start - vis_start) % cdisp->screen.width,
                        (start - vis_start) / cdisp->screen.width);
              if (end >= size)
                mvwputsn (disp->_matrix,
                          end - size + 1,
                          (size - vis_start) % cdisp->screen.width,
                          (size - vis_start) / cdisp->screen.width);
              /* XXX */ move (cdisp->cursor.row, cdisp->cursor.col);
              refresh ();
              mutex_unlock (&ncurses_lock);  
      /* File has grown.  */
      //      break;
      /* File has been truncated.  */
      // break;
      /* Stat information has changed, and none of the previous three
         apply.  Not sent for changes in node times.  */
      return EINVAL;
  return 0;

extern int fs_notify_server (mach_msg_header_t *inp, mach_msg_header_t *outp);

main (int argc, char *argv[])
  error_t err;
  char *display_name;
  char *input_name;
  int dispfd;
  mach_port_t disp_port;
  int inptfd;
  struct stat statbuf;
  int ret;
  struct port_class *notify_class;
  struct port_bucket *notify_bucket;
  struct port_info *notify_port;

  mutex_init (&ncurses_lock);
  initscr ();
  cbreak ();
  noecho ();
  nonl ();
  intrflush (stdscr, FALSE);
  //  timeout (1000);
  nodelay (stdscr, TRUE);
  keypad (stdscr, TRUE);

  if (argc != 2)
    error (1, 0, "missing vc dir name");

  asprintf (&display_name, "%s/display", argv[1]);
  asprintf (&input_name, "%s/input", argv[1]);

  dispfd = open (display_name, O_RDONLY);
  if (dispfd < 0)
    error (2, errno, "opening %s failed", display_name);
  ret = fstat (dispfd, &statbuf);
  if (ret < 0)
    error (3, errno, "stat'ing %s failed", display_name);
  disp = mmap (0, statbuf.st_size, PROT_READ, MAP_SHARED, dispfd, 0);
  if (disp == MAP_FAILED)
    error (3, errno, "mmap failed");
  inptfd = open (input_name, O_WRONLY);
  if (inptfd < 0)
    error (2, errno, "opening %s failed", input_name);
  cthread_detach (cthread_fork (input_loop, &inptfd));

  notify_class = ports_create_class (NULL /* clean */, NULL /* dropweak */);
  if (! notify_class)
    error (5, errno, "Cannot create notify class");
  notify_bucket = ports_create_bucket ();
  if (! notify_bucket)
    error (5, errno, "Cannot create notify bucket");
  err = ports_create_port (notify_class, notify_bucket,
                           0, &notify_port);
  if (err)
    error (5, errno, "Cannot create notify port");
  disp_notify = ports_get_right (notify_port);
  disp_port = getdport (dispfd);
  if (disp_port == MACH_PORT_NULL)
    error (5, errno, "Cannot get display file port");
  err = file_notice_changes (disp_port, disp_notify, MACH_MSG_TYPE_MAKE_SEND);
  if (err)
    error (5, errno, "Cannot request notification messages");

  ports_manage_port_operations_multithread (notify_bucket,
                                            1000 * 60 * 2,
                                            1000 * 60 * 10, 0);

  endwin ();
  return 0;

reply via email to

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