Calling native Linux APIs

You can call into Linux APIs to use methods and properties that are not built into the framework by using the Declare command. To create a Declare statement you first need to track down the API you want to use using Linux documentation.

The Linux API is largely based on the C/C++ programming language and makes heavy use of structures.

Window opacity

As a simple example, the gtk_widget_set_opacity method in libgtk-3 can be used to change the window opacity so that it appears more transparent. This is what the method declaration looks like in the Gnome docs:

gtk_widget_set_opacity (GtkWidget *widget,
                        double opacity);

This tells you it is a method (sub) because the "void" at the beginning indicates it does not return a value. The first parameter is a pointer to a GtkWidget, which in this case is the handle to the Xojo window. The opacity is a Double in the range of 0 to 1. So the above method call translates to a Declare that looks like this:

Declare Sub gtk_widget_set_opacity Lib "libgtk-3" (handle As Ptr, opacity As Double)

To set the window to 75% opacity you can call it like this:

gtk_widget_set_opacity(Self.Handle, 0.75)

Because this method is called for a window, you can put it in a Xojo Extension Method to make it easier to call. To do this, create a global method on a module like this:

Public Sub Opacity(Extends w As DesktopWindow, value As Double)
  #If TargetLinux Then
    Declare Sub gtk_widget_set_opacity Lib "libgtk-3" (handle As Ptr, opacity As Double)
     gtk_widget_set_opacity(w, value)
End Sub

Note the use of the "#If TargetLinux" line. This prevents this code from being compiled into Windows or macOS builds of your app where the code could possible crash your app.

This now allows you to have code like this on a window's Opening event to center the window:


Default control sizes

With the many different Window Managers and themes on Linux, and personal preferences, you can be assured that your UI will look different from one Linux user to the next. The main challenge of being a native Linux app is trying to normalize the UI experience across different platforms (yes, even different Linux distros).

As each platform has their own native default control sizes and such, your app will most likely need to be adjusted accordingly. On Windows and macOS this almost never changes, so thankfully a 22 pixel high Button would look just fine on macOS, as it does on Windows. The problem on Linux is that you have different themes that dictate how much padding should go into a control, and the much larger default font size.

Obtaining the default control size

A solution to this problem is to use Linux APs to get the default control size. This information is provided by GTK+ in conjunction with the Window Manager.

To start you first have to ask the Window Manager to let you know when the control has been "realized" (made available) so that you can request its default size. You will typically do that on the Opening event of the control. Here is the code, which you can put on the Opening event of a Button that has been added to a Window:

#If TargetLinux Then
  Declare Sub g_signal_connect_data Lib "libgobject-2.0" (instance As Ptr, signal As CString, callback As Ptr, data As Ptr, destroy_data As Ptr, connectFlags As Integer)

  g_signal_connect_data(Button1.Handle, "realize", AddressOf RealizeCallback, Nil, Nil, 0)

The actual Declare calls g_signal_connect_data in the libgobject library which takes a reference to the control, the text "realize" to indicate we want to know when the control is available and then the address of a method to call (the callback method) with the information.

So now you need to create the callback method, which is just a shared method on the Window. Here is its code:

Private Shared Sub RealizeCallback(widget As Ptr, data As Ptr)
  #If TargetLinux Then
    Declare Sub gtk_widget_get_preferred_size Lib "libgtk-3" _
    (widget As Ptr, ByRef minSize As GtkRequisition, ByRef naturalSize As GtkRequisition)
    Var minSize, naturalSize As GtkRequisition
    gtk_widget_get_preferred_size(widget, minSize, naturalSize)

    For Each aControl As DesktopControl in Window1.Controls
      If aControl IsA DesktopUIControl Then
        Var dc As DesktopUIControl = DesktopUIControl(aControl)
        If dc.Handle = widget Then
          dc.Width = minSize.Width
          dc.Height = minSize.Height
        End If
      End If
End Sub

This method has a Declare that calls gtk_widget_get_preferred_size in the libgtk-3 library. This method takes a reference to the control and then has two parameters that contain size information. These parameters use the GtkRequisition structure which you will create in a moment. Looking at the code you can see that it is looping through all the controls on the Window and updating any that are DesktopUIControls to use the size reported back from the gtk_widget_get_preferred_size Declare call.

The last thing you need to add is the GtkRequisition structure so add a Structure to the Window and call it GtkRequisition, with two properties: Width As Int32 and Height As Int32.

Structure GtkRequisition
  Width As Int32
  Height As Int32
End Structure

One thing to keep in mind with this technique is that since it is adjusting the size of controls it will alter the look of your layout. You may want to make sure your layout has enough room for control sizes to change (probably they will increase) without causing them to overlap.