GUI Analysis for D3DUT Project

Topics

Available GUI libraries

In these days, there are many usable GUI libraries for GNU/Linux platform. We search wide use library, with abillity to use OpenGL widget. So there are two libraries:

In fact, we want to use C language for our project, I decide to use GTK library.

GTK+ and OpenGL

OpenGL widget for GTK+ library is distributed as extension for GTK+. It is known as GLGtkExt. Homepage of this project is at [GtkGlExt]. This library is used for example for GtkRadiant (the Quake3 map editor) [GtkRadiant]. It's written in C, but has bindings for other language (Mono, C++, Python, etc). Supported (tested) platforms are:

X Window architecture

UNIX X Window system is client server based network application. Server is listening on port and can serve multiple clients. On other side client can connect to multiple X Servers. The connection between X Client and X Server is over TCP/IP, communication is over X Protocol. For more simple use there is library covering network communication (X Protocol messages) Xlib [XLib]. X achitecture looks like this:

Usualy client and server runs on same computer.

GTK+ atchitecture

The GTK+ library is divided into two layers:

The GDK layer wraps Xlib functions. The GTK+ layers buildes widgets over GDK functions. Architecture of GTK:

Inner structure of GTK+ package is divided in these libraries:

Using GTK+

GTK+ is object oriented library. All visual components (sometimes also non-visual) are widgets. Every widget can accept event (known as signals). This short code example creates window with button. After click on this button, application will quit.
#include <gtk/gtk.h>

// quits application
static void destroy(GtkWidget *widget, gpointer data){
    gtk_main_quit();
}

int main(int argc, char** argv){
     GtkWidget *window, *button;

     // initialization of GTK+
     gtk_init(&argc, &argv);

     // creating of window
     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

     // connects destroy signal to window
     // this means: before window destroying, function destroy will be caleld
     g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (destroy), NULL);

     // let's create button with label Hello
     button = gtk_button_new_with_label("Hello");

     // now we connect signal clicked from button to destroy function
     g_signal_connect(G_OBJECT (button), "clicked", G_CALLBACK (destroy), NULL);

     // adding button in window
     gtk_container_add(GTK_CONTAINER (window), button);

     // now we must show button and window to be visible
     gtk_widget_show(window);
     gtk_widget_show(button);

     // this is main loop, this handles component (widget) events (signals)
     // in the application
     gtk_main();


     return 0;
}

Compilation of this example under GNU/Linux looks like:
gcc -c gtk1.c -o gtk1.o `pkg-config --cflags gtk+-2.0`
gcc -o gtk1 gtk1.o `pkg-config --libs gtk+-2.0`

Idle in GTK+

Every time the GTK+ main loop has 'nothing to do' can be idle functions called. The idel function in the GTK+ looks like this:

static gboolean idle_function(gpointer data){
    ... do something ...
    if (should_run) return TRUE;
    else return FALSE:
}

The parameter data is pointer of possible user data. This parameter is set by adding this function. See below. If this function returns FALSE, this means function will be no more called. If TRUE is returned, idle_function will be called next time.

This way we say to GTK+ about our idle function

gint idle_tag;
idle_tag = gtk_idle_add(idle_function, NULL);

In this listing the idle_tag variable is a handle of our idle. By calling gtk_idle_remove(idle_tag) we can remove our idle function. Second parameter of gtk_idle_add function is pointer to user data. These pointer is passed into our function as first parameter. (See above.)

Querying available screen modes

To work with modes we must used functions from Xlib and its extension VidMode. So we must include "X11/extensions/xf86vmode.h" file and link with Xxf86vm library. In order to do it we must pass these -L/usr/X11R6/lib -lXxf86vm argument to linker. (The -L argument is there because Xxf86vm library is commonly placed in /usr/X11R6/lib directory.)

This way we can obtain information about available screen modes we can set up. First we need is a variable for list of available modes.

XF86VidModeModeInfo **modes;
int modes_count

Varialbe modes is vector of pointers to XF86VidModeModeInfo. This structure has these members:

The variable modes_count is count of items in our list (modes).

Now we can query X11 for available modes list calling XF86VidModeGetAlModeLines. Prototype of this function:

Bool XF86VidModeGetAllModeLines(Display *display, int screen, int *modecount_return, XF86VidModeModeInfo ***modesinfo);

To call this function we need to know default display and screen identifiers. This information we must get from GDK layer of GTK+ calling gtk_x11_get_default_xdisplay() and gtk_x11_get_default_screen() functions. Here is code example:

#include <gtk/gtk.h>
#include <X11/Xlib.h>
#include <X11/extensions/xf86vmode.h>
...
// available mode list
XF86VidModeModeInfo **modes;
int modes_count
...
// display and screen identifiers
XDisplay *dpy;
gint screen;
...
dpy = gdk_x11_get_default_xdisplay()
screen = gdk_x11_get_default_screen()
...
if (!XF86VidModeGetAllModeLines( dpy, screen, &modes_count, &modes)) __ERROR__();

This way we can go throw list of available mode:

int i;
XF86VideModeModeInfo *mode; for (i = 0; i < modes_count; i++)
{
    mode = modes[i];
    printf("Mode %d: %d x %d\n", i, mode->hdisplay, mode->vdisplay);
}

Note the current mode is the first in the modes list. This is important to know if you want to restore original mode. See below.

Switching to X11 mode

In order to switch to another X11 mode we must call XF86VidModeSwitchToMode function (from xf86vm library - see above). Here is prototype of this function:

Bool XF86VidModeSwitchToMode( Display *display, int screen, XF86VidModeModeInfo *modeline);

This example shows switching to another X11 mode, it's used variable modes and modes_count from previous listinig.

gboolean switch_to_mode(gint mode_index)
{
    XDisplay *dpy;
    gint screen;
    
    dpy = gdk_x11_get_default_xdisplay();
    screen = gdk_x11_get_default_screen();
    
    if (modes_count >= mode_index) return FALSE;
    return XF86VidModeSwitchToMode(modes[mode_index]);
}

Current X11 mode is the first mode in modes list. (See previous note.) To restore original mode we need to pass 0 as argument mode_index to our switch_to_mode function. This will restore original mode:

if (!switch_to_mode(0)) __ERROR__();

Here is program source of screen resolution switcher. It also shows the unsabilty of gtk_window_fullscren method.

Virtual screen and fullscreen window

Virtual screen is mechanism to access logical screen with larger resolution than physical screen resolution. Because of high resolution of physic screen there is no more need to use virtual screen, so the virtual screen (logical screen) resolution is same as physical screen (for example monitor) resoltuion. So if you switch to mode, which resolution is smaller than actual, you can access whole screen area by scorlling it using mouse pointer. This effect is unwanted in fullscreen mode. To avoid it we must lock mouse pointer inside our window, which is resized to fit to monitor in switched resolution.

GTK+ library provide fullscreen window switching via gtk_window_fullscreen function (method). This function works good if you are in original resoltuion (the virtual screen size is same as monitor resolution). Calling this function makes window frameless, resized to fit virtual screen (!) size. To restore window from fullscreen mode, there is gtk_window_unfullscreen window method.

But we want make window fullscreen mode in smaller resolutions than original, so we must make our window fullscreen method, which must:

Multihead support

GTK+ library supports multihead devices. To understand how to manage more than more screen we must know what XDisplay and Screen stand for.

The XDisplay represents the workstation. It means the XDisplay consists of keyboard, mouse, and one or more screens. The Screen represents for example individual monitors.

This example shows testing count of available screens:

gint num_screen = 0;
gchar *displayname = NULL;
GdkScreen **screen_list;
GdkDisplay *display;

gtk_init (&argc, &argv);

display = gdk_display_get_default();
num_screen = gdk_display_get_n_screens(display);

if (num_screen <= 1)
{
     printf ("This Xserver (%s) manages only one screen. exiting...\n", displayname);
     exit (1);
}
else
{
    printf ("This Xserver (%s) manages %d screens.\n", displayname, num_screen);
}

First thing we must to do, in order to place window on other screen, is to open screen, the window should be placed to. This listing shows how to place window on other screen.

gchar *second_screen_name;
GdkDisplay *second_display;
GdkScreen *second_screen;
GtkWidget *window;

gtk_init (&argc, &argv);

/* screen_second_name needs to be initialized before calling
/* gdk_display_new() */
second_display = gdk_display_new (&argc, &argv, second_screen_name);
if (second_display)
     second_screen = gdk_display_get_default_screen (second_display);
else
{
    g_print ("Can't open display :\n\t%s\n\n", second_screen_name);
    exit (1);
}
/* now GdkScreen can be assigned to GtkWindows */

window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_screen (window, second_screen);

Please note that the variable secnond_screen_name must be initialized before calling gdk_display_new. Actualy I don't know what string second_display_name should contain to open second monitor screen. I supposed the second_display_name value may be ":0.1" to open local second screen.

Links