Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / X11

Programming Xlib with Mono Develop - Part 1: Low-level (proof of concept)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
21 Jan 2014CPOL17 min read 39.6K   944   14   7
How to call native X11 API from Mono Develop C# ending up in a very little application.

Introduction  

This article shows how to access Xlib/X11 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 transparent bitmap display or providing modal dialog.

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

Background 

A lot of little helpful Unix/Linux tools are, still today, coded in C/C++ against Xlib/X11. A migration to C# can simplify the maintenance and offer new opportunities. But developers tend to stick to their inherent development environment. On the one hand a change from the GUI development environment Xlib/X11 to GTK or Qt has many advantages, like rapid prototyping or ready-to-use high-level GUI elements. On the othrer hand it requires to leave old habits, to refuse problem-optimized solutions implemented with low-level API and to loose control over some beloved GUI implementation details. Further more programmers see GUI toolkits come and go in rapid succession, esecially on Windows platforms, and are careful with radical changes.

Those, who are willing to change from an old-fashioned language/IDE like C/GCC to a modern one like C#/Mono Develop, might be afraid of the effort or imponderables of switching the GUI development environment simultaneously. But to change the language and to stick to the approved GUI development environment is possible. 

If a programmer don't want to miss the control over the bits and bobs of his GUI, if he relies on the speed and zero overhead costs or if he just has fun or feels comfortable coding against the Xlib/X11, he will find some helpful information to communicate from C# to Xlib/X11 and vice versa provided with this little sample application. 

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 two projects (the complete sources are provided for download):

  • X11Wrapper defines the function prototypes, structures and types for Xlib/X11 calls  to the libX11.so
  • X11 contains the sample application based on a very lightweight widget set - called "Roma Widget Set" (Xrw)

C# restrictions

Since C# is a dynamically interpreted language, it doesn't support features a preprocessor adds to the compiled language C/C++. This mainly affects the type declarations like the specific X11 type names, that can not be emulated adequate.

#typedef unsigned long Pixel;

The compromise i found was to define enumerations like

public enum TPixel : int {};

but there are no automatic casts and all cast operation must be specified explicitly, like

TPixel p1 = (TPixel)0;

Currently  all casts are part of the application code. One approach to avoid tons of casts could be to wrap the Xlib/X11 API calls and to cast inside the wrapper. But this would work trouble-free only for types, that don't switch the bit size between 32 bit and 64 bit operation system.

32 bit vs. 64 bit

Furthermore C/C++ und C# use the same type specifier for types of different bit size. To avoid type mismatch during development those types, that might confuse through same names for different bit size or different names for same bit size, are also defined as enumeration. One sample is long , that is always 8 bit in C#, but might be 4 bit in C/C++ for 32 bit and 8 bit in C/C++ for 64 bit.

The only difference between the 32 bit and the 64 bit solution is the definition of some types:
C#
public enum TLong : int             {};  // X11    32 Bit: 4 Bytes:                    0 to 65535
//public enum TLong : long          {};  // X11 64    Bit: 8 Bytes: -9223372036854775807 to 9223372036854775807
public enum TUlong : uint           {};  // X11    32 Bit: 4 Bytes:          -2147483647 to 2147483647
//public enum TUlong : ulong        {};  // X11 64    Bit: 8 Bytes:                    0 to 18446744073709551615
public enum TPixel : uint           {};  // X11    32 Bit: 4 Bytes:          -2147483647 to 2147483647
//public enum TPixel : ulong        {};  // X11 64    Bit: 8 Bytes:                    0 to 18446744073709551615
public enum XtArgVal : int          {};  // X11    32 Bit: 4 Bytes:                    0 to 65535
//public enum XtArgVal : long       {};  // X11 64    Bit: 8 Bytes: -9223372036854775807 to 9223372036854775807
public enum XtVersionType : uint    {};  // X11    32 Bit: 4 Bytes:          -2147483647 to 2147483647
//public enum XtVersionType : ulong {};  // X11 64    Bit: 8 Bytes:                    0 to 18446744073709551615

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 sample application

Most of the generic GUI code is released to several Xrw* classes - to provide a higher aggregation level and to organize re-usable code. The function prototypes of the Xlib/X11 and some necessary structures / enumerations are defined in the X11lib class. The application shows  

  1. a menu bar with 4 buttons, all with callbacks,
  2. a dropdown menu with two entries,
  3. the window manager's application decoration with title and icon title,
  4. an application icon with transparency,
  5. a status bar and
  6. a modal message box including icon with transparency. 

somple application on GNOME desktop 32 bit somple application on Xfce 64 bit

Original look & feel on GNOME desktop 32 bit                                 and on Xfce 64 bit

 

The updated application shows 

  1. a notebook with three pages - a menu page, ae radio button test page and a toggle button test page,
  2. a menu bar with 4 buttons, all with callbacks and three of them with transparent icons,
  3. a dropdown menu with two entries including transparent icons,
  4. the window manager's application decoration with title, icon title and transparent icon,  
  5. a status bar with transparent left and right icon and 
  6. a modal message box including transparent icons for label and buttons.  

Updated look & feel on GNOME desktop 32 bit                                 and on Xfce 64 bit 

Passing data between managed/unmanaged code

Although the memory management via [MarshalAs(UnmanagedType.*)] is very reliable in Mono, there are some rare cases in which the programmer will (or must) control the behaviour. Some simple rules dealing with the System.Runtime.InteropServices.Marshal class, we can state, are: 

  • all types are marshaled (in the meaning of a deep copy of the content) between unmanaged memory and managed memory - there is absolutely no safe way to access unmanaged memory data from managed code
  • System.IntPtr type wraps the unmanaged memory pointer  - again there is no direct access to unmanaged memory, but System.Runtime.InteropServices.Marshal dereferencing methods can be used
  • Marshal.AllocHGlobal() can be used successfully in relation to Xlib/X11 native calls

Marshaling data from Xlib/X11 to C#

The first sample to demonstrate one possible turnaround of passing complex structures between managed and unmanaged code is X11lib.XGetClassHint(). This call returns the XClassHint structure on success and the caller is responsible for release of the allocated memory after use - in other words: C# has to release the memory allocated during a X11lib.XGetClassHint()  call.  

C++
// Tested: O.K.
/// <summary> Return the class hint of the specified window to the members of the structure.
/// If the data returned by the server is in the Latin Portable Character Encoding, then the
/// returned strings are in the Host Portable Character Encoding. Otherwise, the result is
/// implementation dependent. To free res_name and res_class when finished with the strings,
/// use XFree() on each individually. </summary>
/// <param name="x11display"> The display pointer, that specifies the connection to the X
/// server. <see cref="System.IntPtr"/> </param>
/// <param name="x11window"> The window to get
/// the class hint for. <see cref="System.IntPtr"/>
/// </param>
/// <param name="classHint"> The class hint
/// to get. <see cref="X11._XClassHint"/> </param>
/// <returns> Nonzero on success, or zero otherwise. <see cref="X11.TInt"/> </returns>
[DllImport("libX11", EntryPoint = "XGetClassHint")]
extern private static TInt _XGetClassHint (IntPtr x11display, IntPtr x11window,
                                           ref _XClassHint classHint); 
// Tested: O.K.
/// <summary> Return a XClassHint structure of the specified window to the members of the
/// structure. If the data returned by the server is in the Latin Portable Character Encoding,
/// then the returned strings are in the Host Portable Character Encoding. Otherwise, the result
/// is implementation dependent. </summary
/// <param name="x11display"> The display pointer, that specifies the connection to the X server.
/// <see cref="System.IntPtr"/> </param>
/// <param name="x11window"> The window to get the class hint for. <see cref="System.IntPtr"/>
/// </param>
/// <param name="classHint"> The class hint to get. <see cref="X11.XClassHint"/> </param>
/// <returns> Nonzero on success, or zero otherwise. <see cref="System.Int32"/> </returns>
public static int XGetClassHint (IntPtr x11display, IntPtr x11window, out XClassHint classHint)
{
    _XClassHint _classHint = _XClassHint.Zero;
    
    if (_XGetClassHint (x11display, x11window, ref _classHint) == (TInt)0)
    {
        // Clean up unmanaged memory.
        // --------------------------
        // Typically: _classHint.res_name == IntPtr.Zero
        // Freeing a IntPtr.Zero is not prohibited.
        // Use first member (at offset 0) to free the structure itself.
        XFree (_classHint.res_name);
        
        classHint = XClassHint.Zero;
        return 0;
    }
    else
    {
        classHint = new XClassHint();
        // Marshal data from an unmanaged block of memory to a managed object.
        classHint.res_name  = (string)Marshal.PtrToStringAnsi (_classHint.res_name );
        classHint.res_class = (string)Marshal.PtrToStringAnsi (_classHint.res_class);
        
        // Clean up unmanaged memory.
        // --------------------------
        // Freeing a IntPtr.Zero is not prohibited.
        // First structure member (at offset 0) frees  the structure itself as well.
        XFree (_classHint.res_name);
        XFree (_classHint.res_class);
        
        return 1;
    }
} 

The solution i've been chosen divides the call into two parts. The C# method XGetClassHint() calls the native code via the renamed function _XGetClassHint() and postprocesses the returned structure. To be able to release the allocated memory, the structure returned from native code is defined using the two memory pointer res_name and res_class.  The structure itself will be created by managed code, marshalled to unmanaged code and provided with the two memory pointers to res_name and res_class (the caller is responsible to release), marshalled back to managed code and destroyed by the garbage collector.

C#
/// <summary> Internal memory mapping structure for XClassHint structure. </summary>
/// <remarks> First structure element is on offset 0.
/// This can be used to free the structute itself. </remarks>
[StructLayout(LayoutKind.Sequential)]
private struct _XClassHint
{
    /// <summary> The application name (might be changed during runtime). </summary>
    /// <remarks> Must be freed separately. </remarks>
    public IntPtr res_name;
    /// <summary> The application class name (should be constant during runtime). </summary>
    /// <remarks> Must be freed separately. </remarks>
    public IntPtr res_class;
    
    public static _XClassHint Zero = new _XClassHint ();
}

And the structure returned to the application is defined using two managed strings of the same name.

C#
[StructLayout(LayoutKind.Sequential)]
public struct XClassHint
{
    [MarshalAs(UnmanagedType.LPStr)] public string res_name;
    [MarshalAs(UnmanagedType.LPStr)] public string res_class;
    
    public static XClassHint Zero = new XClassHint ();
}

The C# method XGetClassHint() manages conversion and memory release explicit and offers full control over the two memory pointer res_name and res_class.

Marshaling data from C# to Xlib/X11

The second sample i will show is the implementation of XrwGraphic class, which provides the application with bitmap graphics that are very easy to handle and support transparency (actually based on white color pixel #FFFFFF). The implemented procedure to apply a transparent bitmap to a drawable includes:

  • creation of the color graphic XImage structure from the bitmap graphic file
  • creation of the transparency mask XImage structure from the same file (using white pixel)
  • creation of the transparency mask XPixmap structure
  • application of the transparency mask XImage structure to the transparency mask XPixmap structure
  • creation of a cliping graphic context based on the transparency mask XImage structure
  • application of the color graphic XImage to the drawable that should show the graphic using the clipping graphic context
  • release of all obsolete resources

Although the XrwGraphic class is not optimized for speed, memory usage or different color depth (currently tested only with true color 24 bit display) this class took the longest time of all provided classes to get it work - because of insufficient information about the procedure to be implemented and the details to be considered. 

The sample application uses XrwGraphic class multiple times and shows transparent graphics for application icon as well as for label, command toggle, radio and menu widgets. To provide a transparent graphic for application icon some of the steps mentioned above are done by the X11lib.XSetWMHints() automatically, but the procedure in general is the same.

The creation of the two XImage structures for color graphic and transparency mask from a System.Drawing.Bitmap uses managed byte[] to process all computation in managed code and Marshal.AllocHGlobal as well as Marshal.Copy to transfer the conversion result to unmanaged memory.

C#
// ### Allocate bitmap conversion data.
// The bitmap color data. Use a temporary managed byte array for speed.
byte[]    graphicData            = new byte[_height * _width * colorPixelBytes];
// The bitmap transparency data. Use a temporary managed byte array for speed.
byte[]    maskData            = new byte[_height * maskLineBytes];
// Quick access to current line's color pixel.
int        graphicPixelIndex    = 0;
// Quick access to current line's mask pixel.
int        maskPixelIndex        = 0;
// Reduce slow calls to bmp.GetPixel (x,y).
Color   pixelColor            = Color.Black;
// Determine whether transparency is required.
bool    transparency        = false;
 
// ### Convert bitmap.
for (int y = 0; y < _height; y++)
{
    for (int x = 0; x < _width; x++)
    {
        graphicPixelIndex = (y * _width + x) << 2;
        maskPixelIndex    = y * maskLineBytes + (x >> 3);
        pixelColor = bmp.GetPixel (x,y);
 
        graphicData[graphicPixelIndex + 0] = pixelColor.B; // B
        graphicData[graphicPixelIndex + 1] = pixelColor.G; // G
        graphicData[graphicPixelIndex + 2] = pixelColor.R; // R
        graphicData[graphicPixelIndex + 3] = pixelColor.A; // A
            
        if (pixelColor. B == 255 && pixelColor.G == 255 && pixelColor.R == 255)
        {
            byte summand = (byte)(1<<(x % 8));
            maskData[maskPixelIndex] = (byte)(maskData[maskPixelIndex] + summand);
            transparency               = true;
        }
    }
}
 
// ### Create XImage.
// The bitmap color data.
IntPtr    graphicDataHandle = Marshal.AllocHGlobal(graphicData.Length);
// Allocate not movable memory.
Marshal.Copy (graphicData, 0, graphicDataHandle, graphicData.Length);
// Client side XImage storage.
_graphicXImage = X11lib.XCreateImage (_display, visual, (TUint)_graphicDepth,
                                      X11lib.TImageFormat.ZPixmap, (TInt)0,
                                      graphicDataHandle, (TUint)_width, (TUint)_height,
                                      IMAGE_PADDING, ASSUME_CONTIGUOUS);
if (_graphicXImage == IntPtr.Zero)
    throw new OperationCanceledException ("Image creation for graphic failed.");
            
if (transparency == true)
{
    IntPtr    maskDataHandle = Marshal.AllocHGlobal(maskData.Length);
    // The bitmap bitmap transparency data.
    Marshal.Copy (maskData, 0, maskDataHandle, maskData.Length);
    // Allocate not movable memory.
    // Client side storage.
    _transpXImage = X11lib.XCreateImage (_display, visual, 
       (TUint)_clipDepth, X11lib.TImageFormat.XYBitmap,
       (TInt)0, maskDataHandle, (TUint)_width, (TUint)_height,
       IMAGE_PADDING, ASSUME_CONTIGUOUS);
    if (_transpXImage == IntPtr.Zero)
        throw new OperationCanceledException (
           "Image creation for transparency mask failed.");
}

The managed byte[] for color graphic and transparency mask  are disposed by the garbage collector. The Xlib/X11 resources like XImages, XPixmaps and GCs must be destroyed manually via  X11lib.XDestroyImage(), X11lib.XFreePixmap() and X11lib.XFreeGC().  

Widget Set 

To prevent a big code mixup and to "don't repeat myself", generic GUI code is released to several Xrw* classes. The widget names and functionality follow the ideas of the "Athena Widget Set" with a little influence of the "Motif Widget Set" and GTK.

The widget set is neiter complete nor fully functional. The widget hierarchy shows the state of Xrw's version 0.10. To get a complete overview or the latest information see the fifth article about native calls from C# and Mono Develop to X11 API, Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework. Enhancements will continue there. The widget hierarchy looks as follows: <td"> <td"> 
XrwObject:IDisposableultimate base object[1, ∅]
 ⌊ XrwRectObject fundamental base object[1, ∅] with geometry 
    ⌊ XrwVisibleRectObject fundamental base object[1, ∅] with drawing
       ⌊ XrwCore universal base gadget/widget[2/3, ∅]
          ⌊ XrwCompositecontainer[2/3, ∅], managing many children
          |  ⌊ XrwConstraintcontainer[2/3, ∅], with geometry management
          |  |  ⌊ XrwBox container[2/3], arranging children horiz./vert.  
          |  |  |  ⌊ XrwNotebook container[2/3], arranging children on pages
          |  |  |  ⌊ XrwRadioBox gadget[2], arranging XrwRadio children horiz./vert.
          |  |  |  ⌊ XrwSpinBoxggadget[2], spinning invisible children to visible
          |  |  ⌊ XrwPortholegadget[2], display only ***one*** child at any time  
          |  |  ⌊ XrwViewportcontainer[2/3], enabling ***one*** child to scroll 
          |  ⌊ XrwShell fundamental shell widget[3] 
          |     ⌊ XrwOverrideShellbase popup shell, not interacting with the WM[4]
          |     |  ⌊ XrwSimpleMenu popup menu shell, handling XrwSme gadgets[2] 
          |     ⌊ XrwWmShell base shell, interacting with the WM[4] 
          |        ⌊ XrwApplicationShellthe common code of an X11 application 
          |        ⌊ XrwTransientShellbase class for popups, interacting with WM[4]
          |           ⌊ XrwDialogShellbase class for dialogs, interacting with WM[4]
          ⌊ XrwLabelBase fundamental static label[2/3, ∅] 
          |  ⌊ XrwLabelstatic label[2/3] 
          |  |  ⌊ XrwCommandcommand button widget[3] 
          |  |  |  ⌊ XrwMenuButtoncommand button widget[3], pop up a simple menu
          |  |  ⌊ XrwSmesimple menu entry gadget[2]
          |  ⌊ XrwToggle toggle button widget[3]
          |     ⌊ XrwRadioradio button widget[3]
          ⌊ XrwSimpleuniversal widget[3]
             ⌊ XrwList list widget[3]
             ⌊ XrwScrollbar scroll bar widget[3]
             ⌊ XrwText single line text edit widget[3]
             ⌊ XrwTree tree widget[3]

[1] object = invisible and windowless, uses neither the ***parent*** window nor an ***onw*** window
[2] gadget = uses the ***parent*** window instead of an ***onw*** window, saves resources compared to widget, but can receive events only if forwarded from the widget it is contained in
[3] widget = has an ***onw*** window, can creceive events directly from the WM[4]
[4] WM = Windows Manager
[∅] do not instantiate  

Geometry management 

The geometry management starts with the shell's ConfigureEvent event, that contains the given shell geometry.

The XrwApplicationShell passes the event to it's own OnConfigure() method, which stores the given geometry and calls SetAssignedGeometry() method for every with the given geometry.

The SetAssignedGeometry() of a child widget, which stores the given geometry,  substracts the border and calls the own CalculateChildLayout() method with the new given geometry.

The CalculateChildLayout() method calls the PreferredSize() method for every child to determine the total preferred size of all children and distributes on this base the given geometry to the children calling the SetAssignedGeometry() method.

The PreferredSize() method call on composites is passed to the children and returns the total preferred size of all children. This call can be  understood as recursively call.

The SetAssignedGeometry() method calls the CalculateChildLayout() method and this calls the SetAssignedGeometry() method for all children. This call sequence  can be  understood as recursively call.  

Event loop and events 

The application's event loop is implemented in the XrwApplicationShell class. I would call it "experimental" but it works fine for the current requirements.  The event loop is divided into the infinite loop RunMessageLoop() and the single event processing DoEvent().

C#
/// <summary> Run the application's message loop. </summary>
protected void RunMessageLoop ()
{
    bool proceed = true;
    while(proceed == true)
    {
        try
        {
            proceed = DoEvent();
        }
        catch (Exception e)
        {
            Console.WriteLine (CLASS_NAME + ":: RunMessageLoop () ERROR: " + e.StackTrace);
        }
    }
    return;
}
 
/// <summary> Process the topmost event and remove it from the event queue. </summary>
/// <returns> True if efent processing must contionue, false otherwise. </returns>
public bool DoEvent()
{
    ...
} 

The DoEvent() method currently processes ConfigureNotify, Expose, KeyPress, KeyRelease, ButtonPress, ButtonRelease, FocusIn, FocusOut, <code><code><code>EnterNotify, LeaveNotify, MotionNotify and ClientMessage events.

Every windowed widget can receive the X11 events that are registered for the underlying window via X11lib.XSelectInput(). Currently XrwSimple (including its derived simple widgets), XrwOverrideShell (including its derived XrwSimpleMenu), XrwTransientShell (including its derived XrwDialogShell and XrwMessageBox) and XrwApplicationShell register X11 events for their underlying windows. XrwTransientShell and XrwApplicationShell interact with the windows manager and register eleven X11 events. 

C#
X11lib.XSelectInput(_display, _window,
                    EventMask.StructureNotifyMask    | EventMask.ExposureMask       |
                    EventMask.ButtonPressMask        | EventMask.ButtonReleaseMask  |
                    EventMask.KeyPressMask           | EventMask.KeyReleaseMask     |
                    EventMask.FocusChangeMask        | EventMask.EnterWindowMask    |
                    EventMask.LeaveWindowMask        | EventMask.PointerMotionMask  |
                    EventMask.SubstructureNotifyMask);

The XrwSimple and the XrwOverrideShell register the same X11 events except EventMask.StructureNotifyMask. They don't neet to receive ConfigureNotify X11 events because all configuration, mainly the size adaption, is done

  1. starting at the shell widget that interacts with the windows manager receiving the ConfigureNotify event,
  2. goes forward through the complete widget hierarchy - including all XrwSimple or XrwOverrideShell and derived widgets as well as all windowless widgets - to calculate the preferred size of all widgets calling PreferredSize() and
  3. goes back through the complete widget hierarchy of this shell to set the assigned geometry calling SetAssignedGeometry().

The DoEvent() method determines the best receiver of the X11 event and translates the X11 event into a C# event, that is invoked. The ClientMessage event is an easy sample for this proceeding.

C#
else if (xevent.type == XEventName.ClientMessage &&
         xevent.ClientMessageEvent.ptr1 == _wmDeleteMessage)
{
    // Window menager is clowing a window.
    bool childAddressed = false;
    
    // ClientMessageEvent events might go to associated transient shells.
    foreach (XrwTransientShell transientShell in _associatedTransientShells)
    {
        if (xevent.ClientMessageEvent.window == transientShell.Window)
        {    
            transientShell.OnClose (new XrwClientMessageEvent (ref xevent.ClientMessageEvent));
            childAddressed = true;
            break;
        }
    }
    if (childAddressed == true)
        return true;
    // Subsequent ClientMessageEvent events might go to windowed children.
    if (xevent.ClientMessageEvent.window == this.Window)
        OnClose (new XrwClientMessageEvent (ref xevent.ClientMessageEvent));
}

The event processing checks if the event is addressed to any transient shell of the application first. Otherwise it processes the event for the application shell. The transformation from an X11 event to a C# event is done by instantiation of the appropriate wrapper class, e. g. new XrwClientMessageEvent (ref xevent.ClientMessageEvent), and this instance is passed to the invocation of the C# event, e. g. transientShell.OnClose()

To make sure, that all transient shells of an application are captured by the event processing, the registration of a transient shell is essential. Currently neither the XrwTransientShell class nor any derived class does this automatically, so the programmer is responsible for it.

C#
// <summary> Handle the ButtonPress event. </summary>
/// <param name="source"> The widget, the ButtonPress event
/// is assigned to. <see cref="XrwRectObj"/> </param>
/// <param name="e"> The event data. <see cref="XawButtonEvent"/> </param>
/// <remarks> Set XawButtonEvent. Set result to nonzero to stop further event processing. </remarks>
void HandleMessageBoxButtonPress (XrwRectObj source, XrwButtonEvent e)
{
    ...
    this.AddTransientShell (messageBox);
    ...
} 

Specifics of widgets, that interact with the windows manager 

All shell widgets, that interact with the windows manager, are decorated with a frame and can be closed with the application's icon menu item "Close" on the left or the close button on the right of the windows title bar.

To ensure a controlled and complete shell close, that includes the disposal of unmanaged resources, the shell must be notified about the close event triggered by the frame. To achieve this, the XrwTransientShell and the XrwApplicationShell register the _wmDeleteMessage.

C#
/* Hook the closing event from windows manager. */
_wmDeleteMessage = X11lib.XInternAtom (_display,
                                       X11Utils.StringToSByteArray ("WM_DELETE_WINDOW\0"), false);
if (X11lib.XSetWMProtocols (_display, _window, ref _wmDeleteMessage, (X11.TInt)1) == 0)
{
    Console.WriteLine (CLASS_NAME + "::InitializeApplicationShellResources () WARNING: " +
                       "Failed to register 'WM_DELETE_WINDOW' event.");
}

Actually the windows manager supports three protocols, WM_TAKE_FOCUS, WM_SAVE_YOURSELF, WM_DELETE_WINDOW, and the X11lib.XInternAtom() obtains the protocol to register it with X11lib.XSetWMProtocols() to the shell's underlying window. 

General widget event handling

All widgets, that register X11 events with X11lib.XSelectInput(), are notified about these X11 events, translated to C# events by the DoEvent() method. If the application want to process events, callback methods can be registered to the event handler introduced by the XrwRectObj widget. 

C#
/// <summary> Register expose event handler. </summary>
public event ExposeDelegate Expose;
/// <summary> Register KeyPress event handler. </summary>
public event KeyPressDelegate KeyPress;
/// <summary> Register KeyRelease event handler. </summary>
public event KeyReleaseDelegate KeyRelease;
/// <summary> Register ButtonPress event handler. </summary>
public event ButtonPressDelegate ButtonPress;
/// <summary> Register KeyPress event handler. </summary>
public event ButtonReleaseDelegate ButtonRelease;
/// <summary> Register FocusIn event handler. </summary>
public event FocusInDelegate FocusIn;
/// <summary> Register FocusOut event handler. </summary>
public event FocusOutDelegate FocusOut;
/// <summary> Register Enter event handler. </summary>
public event EnterDelegate Enter;
/// <summary> Register Leave event handler. </summary>
public event LeaveDelegate Leave;
/// <summary> Register Motion event handler. </summary>
public event MotionDelegate Motion;

Now it is straight forward to obtain event notification as usual in C#, for example cbw1.ButtonPress += HandleCloseMenuButtonPress.

Specifics of widgets, that don't interact with the windows manager

Some widgets have predefined/build in event handler. The XrwSimpleMenu widget, for example, uses the FocusOut and ButtonRelease events to hide its override shell, the Motion event to set/unset the mouse-over effect for the menu entries and the ButtonPress event to start a menu action. The Motion, ButtonPress and ButtonRelease events are handled by the XrwSimpleMenu widget instead of the XrwSme widgets, besause the XrwSme widgets have no own window (to safe resources) and therefore they don't receive events. 

Or the XrwCommand widget, as another example, uses the Enter and Leave events to to set/unset the mouse-over effect.

C#
/// <summary> Initialize local ressources for all constructors. </summary>
private void InitializeCommandRessources ()
{
    ...
            
    Enter += HandleEnter;
    Leave += HandleLeave;
} 

Triggering X11 events

To achieve the mouse-over effect, XrwSme widgets, XrwCommand widgets and XrwMenuButton widgets change the fill color. To make this change visible, an X11 Expose event has to be sent. The static convenience method SendExposeEvent() of the XrwObject widget makes this very easy to handle.

C#
/// <summary> Send an expose event to the indicated window. </summary>
/// <param name="display">The display pointer, that specifies
/// the connection to the X server. <see cref="IntPtr"/> </param>
/// <param name="receiverWindow"> The window to adress
/// the expose event to. <see cref="IntPtr"/> </param>
/// <param name="dispatcherWindow"> The window to send
/// the expose event to. <see cref="IntPtr"/> </param>
/// <returns> Nonzero on success, or zero otherwise (if the conversion
/// to wire protocol format failed). <see cref="X11.TInt"/> </returns>
public static TInt SendExposeEvent (IntPtr display, IntPtr receiverWindow,
                                    IntPtr dispatcherWindow)
{
    XEvent       eE         = new XEvent();
    eE.ExposeEvent.count    = 0;
    eE.ExposeEvent.display    = display;
    eE.ExposeEvent.window    = receiverWindow;
    eE.ExposeEvent.type        = XEventName.Expose;
    
    return X11lib.XSendEvent (display, dispatcherWindow, (TBoolean)1,
                              (TLong)EventMask.ExposureMask, ref eE);
}

Modal dialog

The division of the event loop into RunMessageLoop() and DoEvent() enables modal shells to take over the event handling by implementing their own infinite loop, e. g., the XrwMessageBox.

C#
/// <summary> Run the message box modal. </summary>
/// <returns> The pressed button. <see cref="XrwDialogShell.Result"/> </returns>
/// <remarks> To use the message box non-modal register at least event handler
/// to WmShellClose and to DialogShellEnd and call Show(). </remarks>
public XrwDialogShell.Result Run ()
{
    this.Show();
    
    // Take over the event loop processing.
    while (_result == XrwDialogShell.Result.None)
        ApplicationShell.DoEvent ();
 
    return _result;
}

Points of Interest

It was challenging, instructive and funny driving the same road as Qt, GTK, or wxWidget developers did years before. It offers a smart migration path from C/C++ to C# where developers can keep achievements they don't want miss and conquer new ground simultaneously.

The "Roma Widget Set" (Xrw)  shows how easy OOP can be achived upon the old Xlib/X11 C programming interface.

History  

  • 30. April 2013, This is the first article about native calls from C# and Mono Develop to X11 API. It deals with the Xlib only. Further articles are planned, that deal with Xt/Athena and Xt/Motif.
  • 08. July 2013, the second article about native calls from C# and Mono Develop to X11 API, Programming Xlib with Mono Develop - Part 2: Athena widgets (proof of concept), was posted.
  • 18. September 2013, the third article about native calls from C# and Mono Develop to X11 API, Programming Xlib with Mono Develop - Part 3: Motif widgets (proof of concept), was posted.
  • 07. November 2013, the fourth article about native calls from C# and Mono Develop to X11 API, Programming Xlib with Mono Develop - Part 4: FWF Xt widgets, was posted.
  • 18. November 2013, this article was updated. New features are:
    • Clean differentiation between border and frame.
    • New frame types "Chiseled" and "Ledged".
    • Maintenance of XrwExposeEvent.Result within all OnExpose calls.
    • XrwOverrideShell now bases on an independent window instead in an XrwApplicationShell sub-window.
    • New widgets XrwToggle and XrwRadio, XrwRadioBox, XrwPorthole and XrwNotebook.
  • 20. January 2013, This article was splitted - widget references have been moved to the fifth article about native calls from C# and Mono Develop to X11 API, Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework. Enhancements will continue there. To get a complete overview or the latest information see the fifth article of this series. Additionally some errors have been fixed and a lot of new functionality has been added, for instance:
    • The XEvent's member time is originally of type/bit size ulong (C) that corresponds to uint (C# 32 BIT).
    • The popup menu position is now calculated with XGetWindowAttributes() and XTranslateCoordinates().
    • Gneric theme support including two predefined themes XrwTheme.GeneralStyle.Win95 and XrwTheme.GeneralStyle.Gtk2Clearlooks.

License

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


Written By
Team Leader Celonis SA
Germany Germany
I am currently the CEO of Symbioworld GmbH and as such responsible for personnel management, information security, data protection and certifications. Furthermore, as a senior programmer, I am responsible for the automatic layout engine, the simulation (Activity Based Costing), the automatic creation of Word/RTF reports and the data transformation in complex migration projects.

The main focus of my work as a programmer is the development of Microsoft Azure Services using C# and Visual Studio.

Privately, I am interested in C++ and Linux in addition to C#. I like the approach of open source software and like to support OSS with own contributions.

Comments and Discussions

 
BugBit or rather byte? Pin
Stefan Haubenthal19-May-14 7:31
Stefan Haubenthal19-May-14 7:31 
GeneralRe: Bit or rather byte? Pin
Markus Wolters19-May-14 20:44
Markus Wolters19-May-14 20:44 
I guess he's talking about bytes instead of bits Wink | ;-)
GeneralRe: Bit or rather byte? Pin
Steffen Ploetz19-May-14 20:45
mvaSteffen Ploetz19-May-14 20:45 
QuestionDifficulty besides wrapping the C++ class Pin
Shawn-USA1-May-13 17:49
Shawn-USA1-May-13 17:49 
AnswerRe: Difficulty besides wrapping the C++ class Pin
Steffen Ploetz1-May-13 19:56
mvaSteffen Ploetz1-May-13 19:56 
AnswerRe: Difficulty besides wrapping the C++ class Pin
Y-ME5-Jul-14 18:57
Y-ME5-Jul-14 18:57 
GeneralRe: Difficulty besides wrapping the C++ class Pin
Steffen Ploetz7-Jul-14 1:26
mvaSteffen Ploetz7-Jul-14 1:26 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.