X11

An Xlib interface for modern Fortran makes it possible to interact with the X Window System. Creating windows using raw Xlib works on all systems that have an X server running. A widget toolkit, like GTK+, Qt, or Tk, is not required, albeit no visual components (buttons, labels, menus, …) are included in Xlib.

For this purpose, Xt was often used in the past, but a Fortran interface to it is not available yet. As a consequence, programmers only have access to simple drawing functions for text, geometric shapes, and images. The GTK+ 2 bindings for Fortran can be used instead to facilitate the implementation of full-featured graphical user interfaces.

Fortran Xlib
Fig. 1: The example X11 program written in Fortran

Xlib programming in Fortran does not differ from other programming languages, as xlib.f90 is solely an ISO C binding interface. The example below draws some text and fills a rectangle (fig. 1).

! xwin.f90
program main
    use, intrinsic :: iso_c_binding
    use :: xlib
    use :: xlib_consts
    use :: xlib_types
    implicit none

    integer,          parameter :: width      = 320             ! Window width.
    integer,          parameter :: height     = 200             ! Window height.
    character(len=*), parameter :: title      = 'Fortran X11'   ! Window title.
    character(len=*), parameter :: color_name = 'Sea Green'     ! X11 colour.
    character(len=*), parameter :: font_name  = '-*-helvetica-bold-r-*-*-14-*-*-*-*-*-*-*'
    character(len=*), parameter :: text       = 'X11 window with Fortran'

    type(c_ptr)         :: display
    type(c_ptr)         :: gc
    type(x_color)       :: color
    type(x_event)       :: event
    type(x_font_struct) :: font
    type(x_gc_values)   :: values
    type(x_size_hints)  :: size_hints
    integer             :: screen
    integer             :: rc
    integer(kind=8)     :: root
    integer(kind=8)     :: colormap
    integer(kind=8)     :: black
    integer(kind=8)     :: white
    integer(kind=8)     :: window
    integer(kind=8)     :: wm_delete_window
    integer(kind=8)     :: long(5)

    ! Open display.
    display  = x_open_display(c_null_char)
    screen   = x_default_screen(display)
    root     = x_default_root_window(display)
    colormap = x_default_colormap(display, screen)

    ! Define colours.
    black = x_black_pixel(display, screen)
    white = x_white_pixel(display, screen)
    rc    = x_alloc_named_color(display, &
                                colormap, &
                                color_name // c_null_char, &
                                color, &
                                color)

    ! Create window.
    window = x_create_simple_window(display, root, 0, 0, width, height, 0, black, white)
    call x_store_name(display, window, title // c_null_char)

    ! Register closing of window.
    wm_delete_window = x_intern_atom(display, &
                                     'WM_DELETE_WINDOW' // c_null_char, &
                                     .false._c_bool)
    rc = x_set_wm_protocols(display, window, wm_delete_window, 1)

    ! Set hints to prevent resizing.
    size_hints%flags      = ior(p_min_size, p_max_size)
    size_hints%min_width  = width
    size_hints%min_height = height
    size_hints%max_width  = width
    size_hints%max_height = height

    call x_set_wm_normal_hints(display, window, size_hints)

    ! Create graphics context.
    gc = x_create_gc(display, window, 0, values)

    ! Load and set font.
    font = x_load_query_font(display, font_name // c_null_char)
    call x_set_font(display, gc, font%fid)

    ! Show window.
    call x_select_input(display, window, ior(exposure_mask, structure_notify_mask));
    call x_map_window(display, window)

    ! Event loop.
    do
        call x_next_event(display, event)

        select case (event%type)
            case (expose)
                ! Redraw the window.
                call draw(display, window, gc, font, 25, 25, text)
            case (client_message)
                ! Quit if window is closed.
                long = transfer(event%x_client_message%data, long)

                if (long(1) == wm_delete_window) &
                    exit
        end select
    end do

    ! Clean up and quit.
    call x_free_font(display, font)
    call x_free_colors(display, colormap, (/ color%pixel /), 1, int8(0))
    call x_free_gc(display, gc)
    call x_destroy_window(display, window)
    call x_close_display(display)

    contains
        subroutine draw(display, window, gc, font, x, y, string)
            use :: xlib_types
            implicit none
            type(c_ptr),         intent(in) :: display
            integer(kind=8),     intent(in) :: window
            type(c_ptr),         intent(in) :: gc
            type(x_font_struct), intent(in) :: font
            integer,             intent(in) :: x
            integer,             intent(in) :: y
            character(len=*),    intent(in) :: string
            integer                         :: direction, ascent, descent
            type(x_char_struct)             :: overall

            call x_text_extents(font, string, len(string), direction, ascent, &
                                descent, overall)
            call x_set_foreground(display, gc, color%pixel)

            ! Draw text.
            call x_draw_string(display, window, gc, x, y, string, len(string))
            ! Fill rectangle.
            call x_fill_rectangle(display, window, gc, x, y + 25, 100, 100)
        end subroutine draw
end program main

Compile the Xlib binding and the example program with:

$ gfortran7 -c xlib.f90
$ gfortran7 -Wl,-rpath=/usr/local/lib/gcc7/ -I/usr/local/include/ -L/usr/local/lib/ \
  -o xwin xwin.f90 xlib.o -lX11
$ ./xwin

Please see the GitHub repository for further examples (Mandelbrot set, wire-frame graphics, images).