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.
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:
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.
The GTK+ library is divided into two layers:
Inner structure of GTK+ package is divided in these libraries:
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`
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.)
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:
unsigned int dotclock; /* Pixel clock */
unsigned short hdisplay; /* Number of display pixels horizontally */
unsigned short hsyncstart; /* Horizontal sync start */
unsigned short hsyncend; /* Horizontal sync end */
unsigned short htotal; /* Total horizontal pixels */
unsigned short vdisplay; /* Number of display pixels vertically */
unsigned short vsyncstart; /* Vertical sync start */
unsigned short vsyncend; /* Vertical sync start */
unsigned short vtotal; /* Total vertical pixels */
unsigned int flags; /* Mode flags */
int privsize; /* Size of private */
INT32 *private; /* Server privates */
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.
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 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:
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.