Re: GUI thoughts

John Swensen <[email protected]> Sat, 11 Mar 2006 11:54:28 -0700
Newsgroups gmane.comp.gnu.octave.graphics
Message-ID <[email protected]>
A couple of weeks ago, I sent out a server.h file that had a framework=20
for a singleton class that would contain all the data to be transmitted=20
between a GUI and octave.  It contains a mutex to protect access to the=20
data.  Once the data has been updated, the UI is sent a signal to=20
indicate new data is available.

I have a little bit of the class implemented (e.g. the variable info and=20
the history list), but once it gets more mature, I'll let you know.

-John

P.S. jwe, thanks for pointing me to the VTE patch.  I have modified my=20
GTK GUI to use it and now the UI and octave both show up as the same=20
process AND share a memory space (so I can access the server class I=20
mentioned above directly, rather than writing a pipe or socket server on=20
top of it).  As of about a month ago, VTE under cygwin is broken.  It=20
has somethign to do with changes to cygwin itself and how it handles=20
PTYs.  I was going to make a preliminary release of my UI, but now I am=20
going to wait until cygwin VTE/PTYs is fixed.

Sebastien Loisel wrote:
> John,
>
> I just returned from Italy and I've started digging around for your=20
> method of communicating with Octave. It looks as if openpty does not=20
> exist in MinGW, so I won't be able to use it. I don't know if there's=20
> going to be an issue with the DLL's fd 1 being different from the=20
> EXE's fd 1, I'll see when I try it. I think MinGW has pipes, I don't=20
> know what having a PTY instead of a pipe for Octave will do, do you=20
> suppose that's ok?
>
> From time to time, the GUI wants to go and pry in Octave's innards.=20
> While ideally Octave would be thread-safe, for now it would be nice to=20
> be backward compatible and not rely on this. One simple workaround is=20
> to only pry into Octave's innards when it is safe to do so.
>
> One way of doing this would be to have Octave call a function before=20
> each prompt. From within this function, I could dump all the info from=20
> Octave into my GUI. Another method would be to be to read a variable=20
> or call a function which would tell me whether Octave is running code=20
> or waiting for input. Or, if Octave has an idle() function (like GLUT)=20
> which it calls whenever it has some spare cycles, I could hook into=20
> that to update the GUI. Are there backwards-compatible ways of doing=20
> this?
>
> Cheers,
>
> S=E9bastien Loisel
>
> On 2/28/06, *John W. Eaton* <[email protected]=20
> <mailto:[email protected]>> wrote:
>
>     I have some thoughts and questions about how we would like a GUI fo=
r
>     Octave to work.  Mostly, they are about how the GUI and Octave shou=
ld
>     be linked together, who should be in charge of events, etc.
>
>     In Sebastien's Octave Workshop, I think the GUI handles all events
>     (correct me if I'm wrong).  Octave is embedded in the GUI and gets
>     input via Octave's eval_string function.  Output is captured by
>     grabbing std::cout in some way.  This sort of works, but misses a f=
ew
>     things like handling the diary, automatic function updating based o=
n
>     the last prompt time, the actual readline library for command-line
>     editing, etc.  If you want to stick with eval_string, then it would
>     still be possible to handle the all these things, but you have to
>     duplicate a lot of functionality that is already in Octave.  The
>     eval_string function was not really meant for embedding an interact=
ive
>     Octave session (for one thing, embedding Octave this way turns off
>     the
>     internal flag that tells it that it is running in an interactive
>     session).  You could probably work around that too, but I think the=
re
>     must be a better way.
>
>     Also, if you are embedding Octave with eval_string, I think your GU=
I
>     will be unresponsive while Octave is performing computations.  Unle=
ss
>     you are running Octave in a separate thread, I think you have to wa=
it
>     until eval_string returns to continue with the GUI operations.  If =
you
>     are running Octave in a separate thread and then having the GUI tou=
ch
>     Octave internals while eval_string is running, you will likely have
>     unexpected results since Octave is not currently thread safe (there
>     is no mechanism to prevent global data from being modified at the
>     wrong time).
>
>     To avoid threads, you could start Octave as a coprocess to your GUI=
,
>     but you will still need some modifications to Octave so that it can
>     process requests while it is off running user code.  You won't
>     want to
>     use a pipe for the communication though, because that will not be
>     recognized as tty, so the interactive features won't work properly.
>     Opening a pty connection to a separate process is better (Octave wi=
ll
>     see the pty as a tty, so it will run in interactive mode).
>
>     There is a terminal widget for GTK (VTE) that would make it easy to
>     start Octave as a separate process and communicate with it using a
>     pty.  It provides a terminal window that is (more than) sufficient =
for
>     running Octave.  Running Octave this way, you get readline, Octave
>     thinks it is interactive, diary works, etc.  Perhaps there is
>     something similar for Qt?
>
>     The difficulty with this approach is that if you want the GUI to do
>     more than you can get by running Octave in a terminal window, then =
the
>     GUI and Octave have to pass some extra information around.  I'm sur=
e
>     that this can be done, but it will mean some extra effort.  Will yo=
u
>     parse text data or design a protocol for passing binary data?  Will
>     Octave or the GUI be in charge of that, and how much overhead will =
it
>     add?  In terms of passing data back and forth, it would be much
>     simpler if the GUI and Octave were in the same process and the GUI
>     could just call internal Octave functions to do its thing.
>
>     At least with GTK on Unixy systems, it should be possible to embed
>     Octave in a terminal widget and make Octave think it is talking to =
a
>     tty without having to run Octave as a separate process.  To do this=
,
>     you will need a thread-safe Octave (but you need that anyway with t=
he
>     eval_string approach) and a pty implementation.  The idea is that
>     instead of spawning a subprocess, you set up some ptys, dup some fi=
le
>     descriptors, start Octave in a separate thread, then attach the
>     master
>     pty to the terminal widget.
>
>     Unfortunately, the VTE widget will not allow this mode of operation
>     out of the box.  Fixing it requires the addition of one small
>     function, but it must be added to the core library because it relie=
s
>     on some private internal data to work.
>
>     So, some questions and things to think about.
>
>       * Should we work to make Octave thread-safe?  I think I would pre=
fer
>         to work on this instead of a protocol that could be used to
>         communicate with a separate Octave process.
>
>       * Is there a terminal widget for Qt that could do the same thing =
as
>         VTE?  I don't really want a GUI for Octave to be useful with ju=
st
>         one toolkit.
>
>       * Would any of this work in a Windows environment?
>
>     I'm appending a simple example program that illustrates this.  It
>     depends on the modified VTE terminal widget and GTK.  Building it i=
s a
>     bit tricky since you need to first patch the VTE sources and build
>     the
>     modified VTE library.  Then you need to ensure that you use that
>     library for building and running the example program.
>
>     I'm assuming that if we choose this sort of approach, we can get th=
e
>     necessary changes rolled into the default vte (or other) terminal
>     widget(s).  If not, then I suppose we could provide our own termina=
l
>     widget with Octave (though that would definitely not be my
>     preference).
>
>     jwe
>
>
>     First, here is the function that must be added to vte:
>
>     /* We need the following additional function for vte:
>
>          int vte_terminal_set_pty(VteTerminal *terminal, int pty_master=
);
>
>        It must be added to vte because it calls static private function=
s
>        in the vte library.
>
>        This function was written by Eric Smith <[email protected]
>     <mailto:[email protected]>>,
>        archived here:
>
>        =20
>     http://www.brouhaha.com/~eric/software/vte/vte-0.11.10-add-pty.patc=
h
>     <http://www.brouhaha.com/%7Eeric/software/vte/vte-0.11.10-add-pty.p=
atch>
>
>        as a patch and was submitted to the Gnome project here:
>
>          http://bugzilla.gnome.org/show_bug.cgi?id=3D135230
>     <http://bugzilla.gnome.org/show_bug.cgi?id=3D135230>
>
>        (that was Feb 2004, and still not accepted).
>     */
>
>     int
>     vte_terminal_set_pty(VteTerminal *terminal, int pty_master)
>     {
>             GtkWidget *widget;
>
>             g_return_val_if_fail(VTE_IS_TERMINAL(terminal), -1);
>             widget =3D GTK_WIDGET(terminal);
>
>             if (terminal->pvt->pty_master !=3D -1) {
>                     _vte_pty_close(terminal->pvt->pty_master);
>                     close(terminal->pvt->pty_master);
>             }
>             terminal->pvt->pty_master =3D pty_master;
>
>             /* Open channels to listen for input on. */
>             xvte_terminal_connect_pty_read(terminal);
>
>             /* Open channels to write output to. */
>             xvte_terminal_connect_pty_write(terminal);
>     }
>
>     Now the main program.  This is just a much-simplified version of an
>     example program that is distributed with readline.  I installed the
>     modified vte library in /home/jwe/vte, so I compile the example
>     program with the following command
>
>       gcc -I/home/jwe/vte/include -g $(pkg-config gtk+-2.0 --cflags) \
>         example.c -o example \
>         -L/home/jwe/vte/lib -lvte $(pkg-config gtk+-2.0 --libs) \
>         -lreadline -lpthread -lutil
>
>     and run it with
>
>       LD_LIBRARY_PATH=3D/home/jwe/vte/lib ./example
>
>     This example is extremely simple and does almost nothing.  The poin=
t
>     is to demonstrate using the terminal widget to embed an interactive
>     application without using a coprocess.  The next step would be to a=
dd
>     a button or two that modifies some global data and protect the
>     accesses to the global data as needed.  Add in some signal handling
>     and I think that would demonstrate most of what Octave does.
>
>     #include <stdio.h>
>     #include <sys/types.h>
>     #include <unistd.h>
>     #include <gtk/gtk.h>
>     #include <vte/vte.h>
>     #include <pthread.h>
>     #include <pty.h>
>
>     #include <readline/readline.h>
>     #include <readline/history.h>
>
>     void
>     execute_line (char *line)
>     {
>       static char *err_hilite_on =3D "\e[01;31m";
>       static char *err_hilite_off =3D "\e[0m";
>
>       if (! strcmp (line, "ls"))
>         system ("ls -FClg");
>       else if (! strcmp (line, "quit"))
>         exit (0);
>       else
>         fprintf (stderr, "%sunknown command `%s'%s\n",
>                  err_hilite_on, line, err_hilite_off);
>     }
>
>     void *
>     fileman_main (void *dummy)
>     {
>       rl_readline_name =3D "FileMan";
>
>       /* Loop reading and executing lines until the user quits. */
>       while (1)
>         {
>           char *line =3D readline ("FileMan: ");
>
>           if (! line)
>             break;
>
>           if (*line)
>             {
>               add_history (line);
>               execute_line (line);
>             }
>           else
>             free (line);
>         }
>
>       return 0;
>     }
>
>     int
>     main (int argc, char **argv)
>     {
>     #ifndef FORK_COMMAND
>       pthread_t fileman_thread;
>
>       int fdm;
>       int fds;
>     #endif
>
>       /* GtkWidget is the storage type for widgets */
>       GtkWidget *window;
>       GtkWidget *terminal;
>
>       /* This is called in all GTK applications. Arguments are parsed
>        * from the command line and are returned to the application. */
>       gtk_init (&argc, &argv);
>
>       /* create a new window */
>       window =3D gtk_window_new (GTK_WINDOW_TOPLEVEL);
>
>       terminal =3D vte_terminal_new ();
>
>     #ifdef FORK_COMMAND
>       vte_terminal_fork_command (VTE_TERMINAL (terminal),
>                                  "/usr/bin/octave", 0, 0, 0, 0, 0, 0);
>     #else
>       if (openpty (&fdm, &fds, 0, 0, 0) < 0)
>         fprintf (stderr, "oops!\n");
>
>       dup2 (fds, 0);
>       dup2 (fds, 1);
>       dup2 (fds, 2);
>
>       pthread_create (&fileman_thread, 0, fileman_main, 0);
>
>       vte_terminal_set_pty (VTE_TERMINAL (terminal), fdm);
>     #endif
>
>       /* This packs the terminal into the window (a gtk container). */
>       gtk_container_add (GTK_CONTAINER (window), terminal);
>
>       vte_terminal_set_font_from_string (VTE_TERMINAL (terminal),
>     "Fixed 11");
>
>       vte_terminal_set_size (VTE_TERMINAL (terminal), 80, 24);
>
>       vte_terminal_set_scrollback_lines (VTE_TERMINAL (terminal), 1024)=
;
>
>       /* The final step is to display this newly created widget. */
>       gtk_widget_show (terminal);
>
>       /* and the window */
>       gtk_widget_show (window);
>
>       /* All GTK applications must have a gtk_main(). Control ends here
>        * and waits for an event to occur (like a key press or
>        * mouse event). */
>       gtk_main ();
>
>       return 0;
>     }
>
>