Download sample project for MonoDevelop on Linux x86_64 (source code and precompiled binaries)
Introduction
I am very interested in rich GUI frameworks for C# (see my articles regarding Xlib, Athena, Motif, FWF or the Roma Widget Set). However, since WPF has entered the stage, it is clear that classic widget sets are outdated.
The most powerful and cross-platform technologies, to achieve something comparable to WPF, are Pango+Cairo and even more OpenGL. But it is a very hard job to create a feature-rich GUI framework based on OpenGL (see my articles regarding getting started and OpenGL/OpenTK).
Recently, I accidentally stumbled upon NoesisGUI, which is also available in a free-of-charge INDIE edition (all you must guarantee is a gross annual revenue < 100k€). And I was immediately fascinated.
Background
The Noeses GIU is advertised as "Multiplatform Game UI Middleware Based On XAML" - but it can also have the look and feel of a serious desktop application. Unfortunately, none of the samples included in the download NoesisGUI-ManagedSDK-2.0.2f1.zip or the Noeses GIU home page is prepared to run on Linux.
This article provides a ready-to-run C# project, created with MonoDevelop 5.10 on openSuse Leap 42.1 64bit. But I'm sure - it will be easy to make this project run on a little older or newer Linux version as well as on different distributions.
The project provides the NoesisGUI IntegrationSample reworked for Linux and its dependencies are:
- current version of FreeGLUT and GLU
- current version of OpenGL (Mesa)
- MonoDevelop
- the managed wrapper (C#) and the Linux x86_64 native library for NoesisGUI
This is what the NoesisGUI IntegrationSample looks like, if NoesisStyle.xaml is used:
Alternatively the installation packages includes the SimpleStyle.xaml stule, that looks like:
And the WindowsStyle.xaml stule, that looks like:
Hint: Do not forget to include the style file to the compilation target.
The implementation is based on GLUT. While the GLUT library is abandoned, the FreeGLUT implementation is still maintained.
This first step tutorial will not discuss the drawbacks of GLUT (no main loop control, no access to the display pointer, ...) or the advantages of a 'state of the art' OpenGL wrapper like GLFW. It will just show how to avoid the pitfalls and make the NoesisGUI IntegrationSample run on Linux.
Using the Code
The downloaded NoesisGUI-ManagedSDK-2.0.2f1.zip includes, among other things:
- the library binaries for various platforms (see image)
- the managed wrapper DLL and
- the Windows C# version of the NoesisGUI IntegrationSample at Src/Samples/GL
The Bin/Linux_x86_64/libNoesis.so, Bin/NoesisManaged.dll and the Src/Samples/GL/* files are the base of this article's project.
Note: Almost daily, a new/updated NoesisGUI-ManagedSDK
is available for download with significant changes.
This article is based on the NoesisGUI-ManagedSDK-2.0.2f1.zip.
The C# Projects
This article provides two projects, NoesisGUI-GLUTWrapperLib
and NoesisGUI-IntegrationSample
.
The NoesisGUI-GLUTWrapperLib
project compiles the C++ glue code for GLUT calls to a dynamic library libGLUTErapper.so.
The NoesisGUI-IntegrationSample
project compiles the NoesisGUI IntegrationSample against the framework MONO/.NET 4.5. The files are:
- libNoesis.so, contains the NoesisGUI framework
- libGLUTWrapper.so, contains the glue code for GLUT calls from the
NoesisGUI-GLUTWrapperLib
project - libGLUTWrapper.cs, contains the managed wrapper for libGLUTWrapper.so calls and alternatively completely managed glue code for GLUT calls, based on OpenGL.cs
- GLUTWrapper.cs, contains prototypes and methods for convenient application creation based on libGLUTWrapper.cs
- OpenGL.cs, contains the GLUT, GLU, GL, libX11 and llibc API prototypes, used by calls into these libraries from managed code, and
- Program.cs, contains the application code, based on GLUTWrapper.cs and OpenGL.cs
It is easy to switch the libGLUTWrapper.cs behaviour between managed wrapper for libGLUTWrapper.so calls and completely managed glue code for GLUT calls, based on OpenGL.cs. At the top of the file, you'll find these three lines:
#define USE_FULLY_MANAGED
Message Processing Problems
The Windows C# version of the NoesisGUI IntegrationSample, provided by Noesis, contains C++ glue code for GLUT calls at Src/Samples/GL/GLUTWrapper/GLUTWrapper.cpp. On my environment, this C++ glue code is responsible for:
- the dropdown of a
combobox
is displayed only by a forced redraw (e.g., a change of the window size) and - the application hangs after two or three such
combobox
actions
That's why I transferred the C++ code into the completely managed alternative C# code within the class libGLUTWrapper
, and the NoesisGUI IntegrationSample works fine with that.
There is only one thing that could not be transferred: The call of the C runtime function atexit()
.
But by using FreeGLUT instead of GLUT, it's much better to call:
GLUT.SetOption(FreeGLUT.GLUT_ACTION_ON_WINDOW_CLOSE, FreeGLUT.GLUT_ACTION_CONTINUE_EXECUTION);
and clean up after exiting:
GLUT.MainLoop();
instead of calling atexit()
.
The IntegrationSample
doesn't need a clean up after exiting the GLUT.MainLoop()
, but if your application will need it, use the additional possibilities, FreeGLUT has compared to GLUT.
Here is the affected method from libGLUTWrapper
class (see comment block):
internal static void GLUT_Run()
{
GLUT.DisplayFunc(GLUT_Display);
GLUT.ReshapeFunc(GLUT_Resize);
GLUT.MouseFunc(GLUT_MouseButton);
GLUT.MotionFunc(GLUT_MouseMove);
GLUT.PassiveMotionFunc(GLUT_MouseMove);
GLUT.KeyboardFunc(GLUT_KeyboardDown);
GLUT.KeyboardUpFunc(GLUT_KeyboardUp);
GLUT.SpecialFunc(GLUT_KeyboardSpecialDown);
GLUT.SpecialUpFunc(GLUT_KeyboardSpecialUp);
GLUT.MainLoop();
}
Input Method Problems
I'm German. The German language has umlauts.
- I cannot enter any umlauts into the text box with the NoesisGUI IntegrationSample original example code.
- The key down/up callbacks, registered to FreeGLUT, receive no umlauts.
This is because the NoesisGUI and FreeGLUT currently don't process unicode characters as expected/required (see Get UTF-8 input with X11 Display for details).
For FreeGLUT, the code is open source - and we can take a look at it:
...
len = XLookupString(&event.xkey, asciiCode, sizeof(asciiCode),
&keySym, &composeStatus);
if (len > 0)
{
if (keyboard_cb)
{
fgSetWindow( window );
fgState.Modifiers = fgPlatformGetModifiers( event.xkey.state );
keyboard_cb(asciiCode[ 0 ],
event.xkey.x, event.xkey.y);
fgState.Modifiers = INVALID_MODIFIERS;
}
}
else
...
Since there are no XOpenIM()
, XGetIMValues()
, XCreateIC()
, XSetICFocus()
and Xutf8LookupString()
calls, while the locale setting on my environment is de_DE.UTF-8
, the UTF-8 key code is processed wrong with the XLookupString()
call.
To avoid this pitfall, I check and adjust the locale within the Run()
method of the application's MainClass
according to the NoesisGUI and FreeGLUT key processing, because the NoesisGUI and FreeGLUT key processing can't be adjusted to the locale.
private void Run(string[] args)
{
sbyte[] requestedLocale = C.StringToSByteArray("");
IntPtr currentLocale = C.setlocale((int)C.LocaleCategory.LC_ALL, requestedLocale);
if (currentLocale == IntPtr.Zero)
{
Console.WriteLine("The c runtime library localization is currently not supported. " +
"This might result in unexpected key code to character translation.");
}
else
{
String localeString = currentLocale != IntPtr.Zero ?
System.Runtime.InteropServices.Marshal.PtrToStringAuto (currentLocale) : "";
if (string.IsNullOrEmpty(localeString))
Console.WriteLine("The c runtime library locale is currently not configured. This " +
"might result in unexpected key code to character translation.");
else if (localeString == "C" || localeString == "POSIX")
Console.WriteLine("The c runtime library locale is currently configured as '" +
currentLocale +
"'. This might result in unexpected key code to character translation.");
if (localeString == "de_DE.UTF-8")
{
requestedLocale = C.StringToSByteArray("de_DE");
currentLocale = C.setlocale((int)C.LocaleCategory.LC_ALL, requestedLocale);
localeString = currentLocale != IntPtr.Zero ?
System.Runtime.InteropServices.Marshal.PtrToStringAuto (currentLocale) : "";
}
}
if (!X11.XSupportsLocale())
{
Console.WriteLine("The X11 runtime library localization is currently not supported. " +
"This might result in unexpected key code to character translation.");
}
else
{
sbyte[] requestedModifier = new sbyte[] {(sbyte)'\0'};
IntPtr currentModifier = X11.XSetLocaleModifiers(requestedModifier);
if (currentModifier == IntPtr.Zero)
{
Console.WriteLine("The X11 runtime library locale modifier for the XIM is currently" +
" not set. This might result in unexpected key code to character translation.");
}
else
{
String modifierString = (currentModifier != IntPtr.Zero ?
System.Runtime.InteropServices.Marshal.PtrToStringAuto(currentModifier) : "");
if (string.IsNullOrEmpty(modifierString))
Console.WriteLine("The X11 runtime library locale modifier for the XIM is currently" +
" not set. This might result in unexpected key code to character translation.");
else if (modifierString != "@im=local")
Console.WriteLine("The X11 runtime library locale modifier for the XIM is currently" +
" not set. This might result in unexpected key code to character translation.");
}
}
GLUT.Init(args.Length, args);
...
Essentially, I set the locale from de_DE.UTF-8
to de_DE
, which switches the supported key code range from UTF-8 to ISO-8859-1. I'm sure, this approach can be adopted for other languages as well. A sample:
- With
de_DE.UTF-8
the umlaut 'ö' is processed with the wrong key code 195 - this results in the letter 'Ã'. - With
de_DE
the umlaut 'ö' is processed with the correct key code 246 - this results in the letter 'ö'.
With these changes, the NoesisGUI IntegrationSample works fine using the NoesisStyle.xaml style.
The SimpleStyle.xaml and WindowsStyle.xaml styles use the fallback font (currently I din't figure out where it is configured), that doesn't provide glyphs for umlauts (while the NoesisStyle.xaml style uses the Roboto
font).
All I've done to solve this, is adding a very small ContentControl
style to the *.xml style file after the last ColorBrush
definition, e. g.:
<SolidColorBrush x:Key="WindowBackgroundBrush" Color="#FFF"/>
<Style x:Key="RootContainerStyle" TargetType="{x:Type ContentControl}">
<Setter Property="FontFamily" Value="Fonts/#Roboto"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="FontStyle" Value="Normal"/>
<Setter Property="FontWeight" Value="Normal"/>
</Style>
Consequently the adjusted WindowsStyle.xaml style results in:
Hint: Do not forget to include the font file to the compilation target.
Points of Interest
It looks like I finally found a modern (XAML based and 3D/accelerated graphics supporting) cross-platform GUI framework. I'll keep an eye on this and most likely, I'll dive deeper into the possibilities of NoesisGUI.
History
- 15.03.2018: Initial version
- 18.03.2018: Added screenshots for alternatively styles and added information about fonts used by styles.