From graphics-request at octave dot org Tue Feb 28 15:49:35 2006 Subject: GUI thoughts From: "John W. Eaton" To: octave graphics and gui mailing list Date: Tue, 28 Feb 2006 16:49:15 -0500 I have some thoughts and questions about how we would like a GUI for Octave to work. Mostly, they are about how the GUI and Octave should 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 few things like handling the diary, automatic function updating based on 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 interactive 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 there must be a better way. Also, if you are embedding Octave with eval_string, I think your GUI will be unresponsive while Octave is performing computations. Unless you are running Octave in a separate thread, I think you have to wait until eval_string returns to continue with the GUI operations. If you are running Octave in a separate thread and then having the GUI touch 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 will 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 sure that this can be done, but it will mean some extra effort. Will you 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 the eval_string approach) and a pty implementation. The idea is that instead of spawning a subprocess, you set up some ptys, dup some file 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 relies 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 prefer 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 just 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 is 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 the necessary changes rolled into the default vte (or other) terminal widget(s). If not, then I suppose we could provide our own terminal 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 functions in the vte library. This function was written by Eric Smith , archived here: http://www.brouhaha.com/~eric/software/vte/vte-0.11.10-add-pty.patch as a patch and was submitted to the Gnome project here: http://bugzilla.gnome.org/show_bug.cgi?id=135230 (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 = GTK_WIDGET(terminal); if (terminal->pvt->pty_master != -1) { _vte_pty_close(terminal->pvt->pty_master); close(terminal->pvt->pty_master); } terminal->pvt->pty_master = 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=/home/jwe/vte/lib ./example This example is extremely simple and does almost nothing. The point is to demonstrate using the terminal widget to embed an interactive application without using a coprocess. The next step would be to add 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 #include #include #include #include #include #include #include #include void execute_line (char *line) { static char *err_hilite_on = "\e[01;31m"; static char *err_hilite_off = "\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 = "FileMan"; /* Loop reading and executing lines until the user quits. */ while (1) { char *line = 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 = gtk_window_new (GTK_WINDOW_TOPLEVEL); terminal = 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; }