Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / X11

Programming Xlib with Mono Develop - Part 2: Athena widgets (proof of concept)

4.89/5 (4 votes)
14 Sep 2013CPOL12 min read 31.6K   231  
How to call native Xt API from Mono Develop C# ending up in a very little Athena widget application.

Introduction

This article shows how to access Xt/Athena widgets API from C# using Mono Develop. A lot of API calls are ready-to-use defined and tested and some challenges are mastered, e.g., using callbacks, incorporating third party widgets or providing grab exclusive dialog.

This article is aimed to verify that programming Xt/Athena widgets with C# can be easyly achieved (prove of concept). It provides a sample application's complete project for 32 bit and 64 bit.

Background   

This is the second article in a series of articles examining X* API calls from C# using Mono Develop.
The first Article was Programming Xlib with Mono Develop - Part 1: Low-level (proof of concept) and dealed with Xlib/X11 only. Now i want to go ahead to the Xt/Athena widgets and show how to use them from a modern language/IDE like C#/Mono. 

On the one hand, this article shows that there is no need to be afraid of using Xt API calls from C#, but on the other hand it shows the visual and functional poornes of the Athena widgets. Based on this knowledge it can't be recommended to use Athena widgets (Xaw) any longer - even it they are still maintained by the X.Org Foundation, except for a quick port of a C/C++ application to C#. While the Athena 3D widgets (Xaw3d) can improve the visual poornes, they can't improve the functional poornes of the Athena widgets.  

Using the code

The sample application was written with Mono Develop 2.4.1 for Mono 2.8.1 on OPEN SUSE 11.3 Linux 32 bit EN and GNOME desktop. Neither the port to any older nor to any newer version should be a problem. The
sample application's solution consists of four projects (the complete sources are provided for download):

  • X11Wrapper defines the function prototypes, structures and types for Xlib/X11 calls  to the libX11.so  (already known from the first article of this series)
  • XawNative contains a very little amount of native C code to access Athena widget class records and one sample that shows how to incorporate third party Xt widgets
  • XtWrapper defines the function prototypes, structures and types for Xt calls to the libXt.so and the function prototypes for Athena widget calls to the libXawNative.so
  • Xt contains the sample application based on Athena widgets and one exemplary third party Xt widget

The sample application is also tested with Mono Develop 3.0.6 for Mono 3.0.4 on OPEN SUSE 12.3 Linux 64 bit DE and GNOME desktop, IceWM, TWM und Xfce.

The only difference between the 32 bit and the 64 bit solution is the definition of some types, as already described in the first article of this series.

The application itself has 645 lines of code only. Some generic GUI code is released to several Xt* classes - not to wrap Athena widgets with C#, but to organize re-usable code. The function prototypes of the Xt/Athena and some necessary structures / enumerations are defined in the Xtlib class. The application shows:

  1. a menu bar with 4 buttons, where the first two buttons have a dropdown menu and the second two buttons have callbacks,
  2. a classical dropdown menu with two entries at the first menu button and a Xt Release 4 simple menu with two entries at the second menu button,
  3. a simple grab exclusive dialog at the third menu button,
  4. a drawing area and three buttons to draw random lines, ellipses or rectangles,
  5. an application icon with transparency (not all up to date windows managers display it, but Xfce does),
  6. a status bar with bitmap and label and
  7. a grab exclusive question dialog with bitmap, that can cancel application quit.

 

Look & feel on GNOME desktop 2.30.0 (32 bit) and on Xfce 4.10 (64 bit)

Initialize the application and set the fallback ressources

Most of the re-usable code of a Xt application is released to the class XtApplicationShell or it's base class XtWmShell that implements IDisposable.

The initialization of the application is done by the XtApplicationShell's constructor and calls XtAppInitialize(), without any consideration to command line arguments.

C#
/// <summary> The application's context. </summary>
protected IntPtr			_appContext		= IntPtr.Zero;
 
/// <summary> The shell widgets resource name. </summary>
private string				_shellResourceName	= "ApplicationShell";
 
...
 
TInt argc = 0;
_shell	= Xtlib.XtAppInitialize (ref _appContext, _shellResourceName, IntPtr.Zero, 0, ref argc,
                                 IntPtr.Zero, fallbackResources.Marshal (), IntPtr.Zero, 0);

 

The seventh parameter fallbackRessources.Mashal() has to be passed as array of unmanaged code strings with a NULL pointer as last array element.

Convenient marshaling 1

To simplify the fallback ressources handling, the convenience class XtFallbackRessources takes care for definition (fallbackResources.Add()) and conversion (fallbackResources.Marshal()) of the resource strings. The managed to native string conversion is done by a call of Marshal.StringToHGlobalAnsi() for every resource string.

C#
Xt.XtFallbackRessources fallbackResources = new Xt.XtFallbackRessources ();
// Main application widgets.
fallbackResources.Add ("*RootForm.background:                #F0F0F0");
fallbackResources.Add ("*MenuBox.orientation:                horizontal");
 
...
 
fallbackResources.Add ("*FileMenuEntry2.translations:        <EnterWindow>:highlight()\\n" +
	                "<LeaveWindow>:unhighlight()\\n" +
	                "<BtnDown>:set() notify() unset() XtMenuPopdown(FileMenu)");

 

Two things should be mentiond in reference to translation table definition by fallback resources.

  • The translation tables are overwritten by default.
  • The notify() tansaltion will only call the callbacks registered to XtNcallback , if the widget's set()  translation was called previuosly.

Passing strings to native code

The sample application uses three different ways to pass strings from managed to unmanaged code:

  1. [MarshalAs(UnmanagedType.LPStr)], especially practical for single string API function input only parameter
  2. TChar[], especially practical for member of structures, marshalled between managed and unmanaged code
  3. Marshal.StringToHGlobalAnsi(), especially practical for arrays of strings to be passed to unmanaged code

The lowest manual effort is generated using [MarshalAs(UnmanagedType.LPStr)], because of the complete data handling by managed code (construction, garbage collection) and the fully hidden allocation, value transfer and deallocation of unmanaged memory. 

A better control of the marshaling steps as well as complete data handling by managed code (construction, garbage collection) is achived with TChar[], that can be used easyly with the convenience method X11Utils.StringToSByteArray() to convert managed string to Xt API friendly sbyte[]

A complete control of the marshaling steps is achived with Marshal.StringToHGlobalAnsi(), but data are not handled by managed code and Marshal.FreeHGlobal() must be called explicitly.

Create a managed widget 

The next step after application initialization is to create the widget hierarchy. During this phase the widgets become created (are build up in memory including all resource initialization) and managed (registered to their respective parent widgets) but are neither realized (connected to a window, if they aren't gadgets) nor mapped (their window is made visible to the screen).

The split of widget hierarchy creation into one step create + manage and one step realize + map avoids massive communication between widgets and their windows during initial geometry and layout calculation, that would slow down the application start significantly.

The sample application uses the XtCreateManagedWidget() only to create + manage widgets. The very first call to XtCreateManagedWidget() looks like:

C#
// Root composite: Use a Form widget, it supports rubber-band resizing
// and fromVert / fromHorz constraints.
rootForm			= Xtlib.XtCreateManagedWidget(rootFormName,
						Xtlib.XawFormWidgetClass(), _shell,
						Arg.Zero, 0);

 

The first parameter, rootFormName, is the widget's resource name. It controls the resource assignemnt, e. g. for the fallback resources discussed above. The second parameter, Xtlib.XawFormWidgetClass(), is the widget's class record, which is shared by all instances of a certain widget class. The class record of any widget class is integral part of it's implementation file and therefore already defined within the libXaw.so. The sample application must utilize the widget class records from the libXaw.so but native code structures are inaccessible from C#.

The third parameter, _shell, is the widget's parent widget and the last two parameters (Arg.Zero, 0) offer widget resource initialization, that is unused here.

Additional native code

The turnarount to overcome the restriction (that an application must utilize the widget class records from the libXaw.so but native code structures are inaccessible from C#) is to acces the widget class records via little native bridge code. Just two lines of native code are required to provide access to a certain widget class record. The three samples are taken from the solution's project XawNative.c file:

C++
//Provide access to the widget's class record from C#.
void* XawAsciiTextWidgetClass ()
{	return (void*) asciiTextWidgetClass;	}
 
void* XawBoxWidgetClass ()
{	return (void*) boxWidgetClass;	}
 
void* XawCanvasWidgetClass ()
{	return (void*) canvasWidgetClass;	}
 
...

 

Furthermore the solution's project XawNative contains the third party widget MoreWidgets/Canvas to demonstrate utilization of an Xt compliant non-Athena widgets.

Initialize and manipulate widget resources

The XtCreateManagedWidget() method is, in combination with the last two parameters Args[] args and XCardinal numArgs, one of the two possible ways to create Athena widgets and set resources simultaneously and it is widely tested by the sample application.

The second, XtVaCreateManagedWidget(), doesn't provide type safety to the aruments and is not used for the sample application.

A call to XtCreateManagedWidget() including simultaneous resorce setting looks like:

C#
// Menu bar: Use a Box widget, this is common.
Arg[] menuBoxArgs		= {	new Arg(XtNames.XtNwidth, (XtArgVal)CANVAS_WIDTH ),
					new Arg(XtNames.XtNhSpace, (XtArgVal)2) };
menuBox				= Xtlib.XtCreateManagedWidget(menuBoxName,
					Xtlib.XawBoxWidgetClass(), rootForm,
					menuBoxArgs, (XCardinal)2);

 

Alternatively to the use of the two parameters Args[] args and XCardinal numArgs during a call to XtCreateManagedWidget(), the XtGetValues() and XtSetValues() methods can be used to get or set Athena widget resources.

Using XtGetValues  and XtSetValues

It is important to pass unmanaged code place holder to receive the return values of XtGetValues(). A call to Marshal.AllocHGlobal() allocates such a unmanaged code place holder and a call to Marshal.FreeHGlobal() releases the unmanaged code place holder after usage. To get the value from the unmanaged code place holder, a Marshal.Read*() must be applied.

The sample application uses this technique to determine the popup menu position:

C#
// Determine file menu command widget's height.
IntPtr			hHeight		= Marshal.AllocHGlobal (2); // XDimension is 2 bytes
Arg[]			parentRetArgs	= { new Arg(XtNames.XtNheight, hHeight) };
Xtlib.XtGetValues (parent, parentRetArgs, (XCardinal)1);
XDimension		height		= (XDimension)Marshal.ReadInt16 (hHeight);
Marshal.FreeHGlobal (hHeight);
menuY += (int)height;

 

The XtSetValues() call should be used as rare as possible, because Athena widgets don't do any geometry update after a resource value change. Therefore geometry update must be done explicitly.

The sample application updates the geometry explicitly for the status label widget after the label text has been set to a new value:

C#
/// <summary> Implement the SetStatusLabel event handler. </summary>
/// <param name="label"> The label to set to the status bar. <see cref="System.String"/> </param>
public void HandleSetStatusLabel (string label)
{
  if (!(Xtlib.XtIsRealized (_statusLabel) == (TBoolean)0))
  {
    Arg[] statusLabelArgs = { new Arg(XtNames.XtNlabel, X11.X11Utils.StringToSByteArray (label + "\0")) };
    Xtlib.XtSetValues (_statusLabel, statusLabelArgs, (XCardinal)1);
 
    XtWidgetGeometry preferred = new XtWidgetGeometry();
    XtGeometryResult result = Xtlib.XtQueryGeometry (_statusLabel, IntPtr.Zero, ref preferred);
    Xtlib.XtResizeWidget (_statusLabel, preferred.width, preferred.height, preferred.border_width);
  }
}

 

A call to XtQueryGeometry() gets the new geometry from and a call to XtResizeWidget() applies the new geometry to the status label widget, whose resource value has just been changed. But this doesn't affect the parent widget's geometry and it might be necessary to traverse the widget hierarchy back to the root widget explicitly.

Manage the WM_DELETE_WINDOW signal 

Most applications do not only close unquestioned, they ask for save of unsaved data and sometimes they give the user the possibility to abort closing. To achive this, the WM_DELETE_WINDOW signal must be receiced and processed by the application. The WM_DELETE_WINDOW signal is not sent to a window by default (where it could be processed by the event loop), but occures at windows manager only. To receive and process the signal by the application, it must be transferred from the windows manager to the application's event loop and a translation to a registered action must be set to the top level window.

C#
/// <summary> Register a "delete window action" to application context, translate the "delete
/// window action", add/overwrite the shell widget's translation table and set windows manager
/// protocol hook for the shell widget. </summary>
/// <param name="appContext">  The application's context to register the action to.
/// <see cref="System.IntPtr"/> </param>
/// <param name="deleteWindowAction"> The action to register to the application's context.
/// <see cref="XtActionProc"/> </param>
/// <remarks> This must be done *** AFTER *** XtRealizeWidget (). </remarks>
public void RegisterDeleteWindowAction (IntPtr appContext, XtActionProc deleteWindowAction)
{
  try
  {
    // Register (instance method) action procedure to runtime action marshaler
    // and let it map the signal to the (global static) action procedure..
    IntPtr deleteWindowActionPtr = ActionMarshaler.Add (_shell,
        X11.XEventName.ClientMessage, deleteWindowAction);
		
    // Create an actions record to provide the application's context
    // with a "action-name" to "action-procedure" translation.
    XtActionsRec[] actionProcs= new XtActionsRec[] {
        new XtActionsRec (X11Utils.StringToSByteArray (XtWmShell.DELETE_WINDOW_ACTION_NAME + "\0"),
                          deleteWindowActionPtr) };
		
    // Register the actions record to the application's context.
    Xtlib.XtAppAddActions (appContext, actionProcs, (XCardinal)1);
		
    // Create a compiled translation table, to provide the widget with
    // a "message" to "action-name" translation.
    IntPtr translationTable = Xtlib.XtParseTranslationTable("<Message>WM_PROTOCOLS: " +
        XtWmShell.DELETE_WINDOW_ACTION_NAME + "()");
		
    // Merge new translations to the widget, overriding existing ones.
    Xtlib.XtOverrideTranslations (_shell, translationTable);
	
    /// The delete message from the windows manager. Closing an app via window
    /// title functionality doesn't generate a window message - it only generates a
    /// window manager message, thot must be routed to the window (message loop).
    IntPtr wmDeleteMessage = IntPtr.Zero;
		
    // Hook the closing event from windows manager.
    // Must be done *** AFTER *** XtRealizeWidget () to determine display and window!
    wmDeleteMessage = X11lib.XInternAtom (Xtlib.XtDisplay(_shell), "WM_DELETE_WINDOW", false);
    if (X11lib.XSetWMProtocols (Xtlib.XtDisplay(_shell), Xtlib.XtWindow(_shell),
        ref wmDeleteMessage, (X11.TInt)1) == 0)
    {
      Console.WriteLine (CLASS_NAME + "::RegisterDeleteWindowAction() " +
          "WARNING: Failed to register 'WM_DELETE_WINDOW' event.");
    }
  }
  catch (Exception e)
  {
    Console.WriteLine (e.Message);
    Console.WriteLine (e.StackTrace);
  }
}

 

Convenient marshaling 2

In Xt all callback and action procedures are processed as global functions, because Xt doesn't know anything about objects (in that way C# does). To simplify the mapping of callback and action procedures to class methods, the static convenience classes CallBackMarshaler and ActionMarshaler implement dictionaries of either callback procedures or action procedures and provide automatic mapping to the appropriate class method.

The static method CallBackMarshaler.Add(...) returns an callback pointer that can be used to register a widget's callback procedure via Xtlib.XtAddCallback(...).

C#
Xtlib.XtAddCallback (closeCommand, XtNames.XtNcallback,
                     CallBackMarshaler.Add (closeCommand, this.ApplicationCloseClick), IntPtr.Zero);

 

The static method ActionMarshaler.Add(...) returns an action pointer that can be used to register a widget's action procedure via an XtActionsRec and Xtlib.XtAppAddActions(...).

C#
IntPtr deleteWindowActionPtr	= ActionMarshaler.Add (_shell,
    X11.XEventName.ConfigureNotify, deleteWindowAction);

XtActionsRec[]	actionProcs    = new XtActionsRec[] {
    new XtActionsRec (X11Utils.StringToSByteArray (XtWmShell.DELETE_WINDOW_ACTION_NAME + "\0"),
                      deleteWindowActionPtr) };

Xtlib.XtAppAddActions (appContext, actionProcs, (XCardinal)1);

 

Manage the root shell's CONFIGURE_NOTIFY signal

To receive and process the COFIGURE_NOTIFY signal (which reports changes to the window's state - such as size, position, border, and stacking order), a translation to a registered action must be set to the top level window.

C#
// Register (instance method) action procedure to runtime action marshaller and
// let it map the signal to the (global static) action procedure.
IntPtr configureNotifyActionPtr	= ActionMarshaler.Add (_shell, X11.XEventName.ConfigureNotify,
                                                       configureNotifyAction);

// Create an actions record to provide the application's context with a "action-name" to
// "action-procedure" translation.
XtActionsRec[]	actionProcs	= new XtActionsRec[] {
    new XtActionsRec (X11Utils.StringToSByteArray (XtWmShell.COFIGURE_NOTIFY_ACTION_NAME + "\0"),
                      configureNotifyActionPtr) };

// Register the actions record to the application's context.
Xtlib.XtAppAddActions (appContext, actionProcs, (XCardinal)1);

// Create a compiled translation table, to provide the widget with a "message" to "action-name" translation.
IntPtr	translationTable	= Xtlib.XtParseTranslationTable("<Configure>: " +
                                                                XtWmShell.COFIGURE_NOTIFY_ACTION_NAME + "()");

// Merge new translations to the widget, overriding existing ones.
Xtlib.XtOverrideTranslations (_shell, translationTable); 

Again we use the static method ActionMarshaler.Add(...) for registering the action procedure and the static class ActionMarshaler for convenient signal marshaling.  

Set the application icon 

The basic work to create an application icon is done by the XrwGraphic class, already dicussed in the first article of this series, sub-chapter Marshaling data from C# to Xlib/X11. Since the application icon's graphic and mask are XPixmaps, they must be created at X server and his requires a display (connection to the X server) and a window associated to the shell. In other words, the application icon can not be set after the shell widget has been created + managed, the shell widget must be realized + maped as well to permit successful calls to Xtlib.XtDisplay() and Xtlib.XtWindow().

C#
/// <summary> Load the icon from indicated path and set it as shell icon. </summary>
/// <returns> <c>true</c>, if icon was set, <c>false</c> otherwise. </returns>
/// <param name='iconPath'> The icon path. </param>
public bool SetShellIcon (string iconPath)
{
  bool result = false;
	
  if (_shell == IntPtr.Zero)
  {
    Console.WriteLine (CLASS_NAME + "::SetShellIcon() ERROR: Member attribute '_shell' null.");
    return result;
  }
  if (string.IsNullOrEmpty (iconPath))
  {
    Console.WriteLine (CLASS_NAME + "::SetShellIcon() ERROR: Paramerter 'iconPath' null or empty.");
    return result;
  }
	
  IntPtr display = Xtlib.XtDisplay (_shell);
  IntPtr window  = Xtlib.XtWindow  (_shell);
  TInt   screen  = Xtlib.XDefaultScreen (display);
  using (X11Graphic appIcon = new X11Graphic (display, screen, iconPath))
  {
    IntPtr     appGraphicPixMap	= appIcon.CreateIndependentGraphicPixmap (display, window);
    IntPtr     appMaskPixMap	= appIcon.CreateIndependentMaskPixmap (display, window);
    if (appGraphicPixMap != IntPtr.Zero && appMaskPixMap != IntPtr.Zero)
    {
      X11lib.XWMHints wmHints = X11lib.XAllocWMHints ();
      wmHints.flags       = X11lib.XWMHintMask.IconPixmapHint   |
		 	    X11lib.XWMHintMask.IconPositionHint |
		 	    X11lib.XWMHintMask.IconMaskHint;
      wmHints.icon_pixmap = appGraphicPixMap;
      wmHints.icon_mask   = appMaskPixMap;
      wmHints.icon_x      = 0;
      wmHints.icon_y      = 0;
      X11lib.XSetWMHints (display, window, ref wmHints);
			
      result = true;
    }
    else
      Console.WriteLine (CLASS_NAME + "::SetShellIcon () ERROR: Can not create application icon.");
  }
 
  return result;
}

 

The ultimate application icon assignment to the shell is done by X11lib.XSetWMHints().

All this is centralized at the XtWmShell class mehod SetShellIcon(). The method can be called for application shells ...

C#
Xtlib.XtRealizeWidget (_shell);
SetShellIcon (IconPath);

 

... as well as for popup shells ...

C#
Xtlib.XtPopup (_shell, XtGrabKind.XtGrabExclusive);					
if (firstRun && XtApplicationShell.Instance != null)
{
    SetShellIcon (XtApplicationShell.Instance.IconPath);
}

 

Grab exclusive dialog

There sample application contains two grab exclusive dialogs, XtGrabExclusiveMessageBox and XtGrabExclusiveAthenaDialog. Both call XtPopup (xtPopupShell, grabKind) with XtGrabKind.XtGrabExclusive to realize exclusive event handling.

A last word to the color handling 

Almost 99% of Xt sample code available in the internet use XAllocColor / XFreeColors for color handling. These functions are fine if the X server uses an undecomposed colormap, which is the case for StaticGray, GrayScale, StaticColor and PseudoColor.

But nowadays most of the X server run TrueColor or DirectColor mode with decomposed colormap and a call to XFreeColors can confuse the X server's color handling. The static class X11Color implements the static helper methods IsDirectColorVisual(...) and IsTrueColorVisual(...) to identify the modes with decomposed colormap. 

Now the color handling can be implemented straightforward for all types of X server's color handling.

C#
IntPtr			display	 = Xtlib.XtDisplay (_canvas);
TInt			scrnID   = X11lib.XDefaultScreen (display);
IntPtr			colormap = X11lib.XDefaultColormap (display, scrnID);
bool			fastCol  = X11Color.IsDirectColorVisual (display, scrnID) ||
                                   X11Color.IsTrueColorVisual (display, scrnID);

// Only TrueColor and DirectColor visuals can directly access colors via RGB,
// all other visuals must alloc/free colors. 
if (fastCol)
{
	_rectBuchColors[cont].pixel = (TPixel)((uint)((int)_rectBuchColors[cont].red)<<16) +
                                              ((uint)((int)_rectBuchColors[cont].green)<<8) +
                                              ((uint)_rectBuchColors[cont].blue);
}
else
{
	string color = string.Format ("#{0,2:X}{1,2:X}{2,2:X}", _rectBuchColors[cont].red,
	                              _rectBuchColors[cont].green, _rectBuchColors[cont].blue);
	X11lib.XParseColor (display, colormap, X11Utils.StringToSByteArray(color + "\0"),
                            ref _rectBuchColors[cont]);
	X11lib.XAllocColor (display, colormap, ref _rectBuchColors[cont]);
}
			

...			

// Only TrueColor and DirectColor visuals can directly access colors via RGB,
// all other visuals must alloc/free colors. 
if (!fastCol)
{
	for (int cont = 0; cont < LINES_BUNCH_SIZE; cont++)
	{
		TPixel[] pixels = new TPixel[1];
		pixels[0] = _lineBuchColors[cont].pixel;
		X11lib.XFreeColors (display, colormap, pixels, (TInt)1, 0);
	}
}

Points of Interest

Register callback and action procedures  

This sample application demonstrates, that porting a C/C++ Athena widget project to C# can be straightforward. Depending on the widget argument handling, that has been used for a project to port, it can be also a task with less ( XtCreateManagedWidget()) or more effort (XtVaCreateManagedWidget() must be converted to Arg[] and XtCreateManagedWidget()).

The real challange might be to give the port an object oriented structure. The helper classes CallBackMarshaler and ActionMarshaler show one possible practical approach to bring C# objects and Xt global functions in accordance.

Visual and functional poorness

This sample application also demonstrates the utilization of an Xt compliant non-Athena widget. There are several open source or community projects, that rework the Athena widgets, e. g. neXtaw, XawM, Xaw-Xpm or XawPlus, and there are several additional widgets, that can be used in combination with Athena widgets, e. g. Mowitz, EuroBridge widget setGridbox and oher Widgets at ftp://ftp.x.org/contrib/widgets or Siag Office. All of them try to overcome the visual and functional poornes, but nevertheless it stays a hard work to create a modern style GUI using Xt/Athena widgets.

History

  • 08. June 2013, This is the second article about native calls from C# and Mono Develop to X11 API. It deals with the Xt/Athena widgets primarily. The first article was about the Xlib only. Further articles are planned, that deal with Xt/Motif and dive deeper into Xlib, Xt/Athena or Xt/Motif. 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)