X11 mit Motif

Mit GTK+ hatte ich ein portables UI Framework gefunden, welches ich sowohl in Windows als auch in Linux zur Entwicklung nutzen konnte.

Doch … wenn es um kleine UIs geht, ist dieses Riesenframework schon ein ziemlicher Overhead.

Und dann fiel mir wieder ein: X11 hat ja seine 30 Jahre alten X-Toolkits integriert.


Vorgeschichte

Es war Ende 2007 als ich mich zum ersten Mal mit UIs unter Linux und X11 auseinandersetzte. Schon damals wollte ich ein Äquivalent zur nativen WinAPI für UIs haben, die auf jedem System vorhanden sind und keine fetten Runtimes brauchten.

Die üblichen Vertreter GTK und QT waren mir damals schon unsympathisch, weil sie viel Eigenaufwand erforderten um kompiliert werden zu können und Tools wie CMake kannte ich damals noch nicht.

Hängen geblieben bin ich bei beim X-Toolkit mit seinen Xt* APIs, welches die Erstellung von “Widgets” ermöglicht, also Usercontrols wie Buttons, Labels und andere übliche UI Elemente.

Schon damals listete Wikipedia Athena, Motif und XView als klassische Implementierungen von solchen UI Widgets auf, doch da die letzten beiden zu dem Zeitpunkt noch nicht Open-Source verträglich waren, versuchte ich mein Glück mit Athena und seinen Xaw* APIs.

Athena’s Widgets sind mit Abstand die altbackensten, schwarz-weiß und mit teils unbenutzbaren UI Verhalten, wie z.B. dass man nur dann in eine Textbox schreiben konnte, wenn der Mauszeiger über dieser thronte. Auch die Scrollbars verdienten ihren Namen nicht.
Und somit war das UI-Experiment schnell beendet.

Heute

Wie gesagt: Ich hätte gerne eine UI Framework, dass möglichst leichtgewichtig ist und welches man leicht in eigene Projekte integrieren kann. Komplexe Sachen brauche ich nicht, ich will nur Buttons, Labels und Textboxen für primitive UI-Dialoge.

Und nachdem Motif gegen 2013 als Open Source Bibliothek neu veröffentlicht wurde und heute in jeder Linux und BSD Distro zu finden ist, dachte ich mir:

Implementieren wir doch einfach mal Motif Apps!

Motif

Bei Motif muss man zwischen den Xt (X-Toolkit) und den Motif Widgets unterscheiden. Man erkennt die Toolkit Funktionen am Xt Prefix, während die Motif Funktionen mit Xm beginnen. Widget-Klassen sind eigentlich nur String-Konstanten (beginnend mit xm) und identifizieren das zu ladende UI Control, bzw. dessen Layout und Verhalten.

Wie oft in UI Frameworks läuft alles in einer Event-Loop, die Benutzerereignisse an die Controls (Widgets) weiterleitet.
Das UI selbst beginnt mit einem Hauptfenster, in dem einer von mehreren Layout-Containern als Kind eingefügt wird.
Innerhalb des Containers werden dann die UI-Controls wie Buttons erzeugt und der Container übernimmt dann deren Ausrichtung und Positionierung.

flowchart LR subgraph XToolkit XTLOOP[[Event Loop]] SHELL[Application
Main
Window] end subgraph Motif Widgets CONTAINER(Control
Layout
Container) CONTROL1([UI Button A]) CONTROL2([UI Textbox B]) end subgraph User/Logic CALLBACK1[[button_pushed
Callback]] CALLBACK2[[text_changed
Callback]] end XTLOOP --- SHELL SHELL --- CONTAINER CONTAINER --- CONTROL1 CONTAINER --- CONTROL2 CONTROL1 --- CALLBACK1 CONTROL2 --- CALLBACK2

An die Widgets können Callbacks angehängt werden, damit ein “Button-Click” zum Aufruf einer Funktion führt.

In Code sieht das etwas so aus:

 1#include <X11/Intrinsic.h>
 2#include <Xm/Xm.h>
 3#include <Xm/Primitive.h>
 4#include <Xm/BulletinB.h>
 5#include <Xm/PushB.h>
 6
 7static void button_pushed_callback(Widget widget, XtPointer client_data, XtPointer call_data)
 8{
 9  XtAppContext app = (XtAppContext)client_data;
10  XtAppSetExitFlag(app);
11}
12
13void create_ui_widgets(XtAppContext app, Display* disp)
14{
15  Widget main_window = XtAppCreateShell(NULL, NULL, applicationShellWidgetClass, disp, NULL, 0);
16  XtManageChild(main_window);
17  XtRealizeWidget(main_window);
18    
19  Widget control_container = XtCreateWidget(NULL, xmBulletinBoardWidgetClass, main_window, NULL, 0);
20  XtManageChild(control_container);
21  XtRealizeWidget(control_container);
22
23  Widget motif_button = XtCreateWidget(NULL, xmPushButtonWidgetClass, control_container, NULL, 0);
24  XtManageChild(motif_button);
25  XtRealizeWidget(motif_button);    
26  XtAddCallback(motif_button, XmNactivateCallback, &button_pushed_callback, (XtPointer)app);
27}
28
29int main(int argc, char **argv) 
30{
31  char* display_name = NULL;
32  char my_app_name[] = "MyMotifApp";
33  char my_app_class[]= "MyMotifClass";
34
35  XtToolkitInitialize();
36  XtAppContext app = XtCreateApplicationContext();
37  Display* disp = XtOpenDisplay(app, display_name, my_app_name, my_app_class, NULL, 0, &argc, argv);
38
39  create_ui_widgets(app);
40
41  XEvent xevt;
42  while(!XtAppGetExitFlag(ctx))
43  {
44    XtAppNextEvent(app, &xevt);
45    XtDispatchEvent(&xevt);
46  }
47
48  XtCloseDisplay(disp);
49  XtDestroyApplicationContext(app);
50  return 0;
51}

Fazit

Meine Herausforderung ist es nun, für alle bereits existierenden GATE UI Controls eine Entsprechung in den Motif Widgets zu finden. Buttons und Labels sind wie üblich leicht. Schwieriger wird es dann vermutlich mit Treeviews und Listviews werden, die viele Daten verwalten müssen.

Aber nachdem mir das mit der WinAPI und GTK gelungen ist, ist es nur eine Frage der Zeit, bis ich das auch auf Motif portiert haben werden.

Das Problem ist heute eher die Dokumentation und Beispiele. Die Technik stammt aus den 90ern und früher und wird heute in neuen Projekten kaum mehr genutzt. Kein Wunder also, dass auch die Dokumentation dünner geworden ist.

In sofern also gut, dass das wieder einmal alles ausgegraben wird.