module ui_x11

module lib/ui_x11.mu

import "ui_x11"

ui_x11 is the Linux backend behind ui: widgets drawn by µ itself with the X11 core protocol — rectangles and server-side "fixed"-font text — and an event loop over x11.wait_event. Programs import ui, never this module. It runs on X.org sessions and, through Xwayland, on every Wayland desktop.

Where Cocoa hands out real controls, here a widget is only state: its frame, its text, its handler. Every expose repaints the window from that state, a button press hit-tests against it, and key presses edit whichever input holds focus. The "fixed" font is 6×13, which is where the metric constants below come from.

Imports

Functions

backend#

fn backend()

backend answers the function table ui dispatches through.

Source lib/ui_x11.mu:30
fn backend() {
    return {"init": init, "window": window, "label": label, "button": button, "input": input, "checkbox": checkbox, "textarea": textarea, "set_text": set_text, "get_text": get_text, "get_checked": get_checked, "set_checked": set_checked, "place": place, "remove": remove, "run": run}
}

button#

fn button(_, win, title, x, y, w, h, onclick)

Undocumented.

Source lib/ui_x11.mu:74
fn button(_, win, title, x, y, w, h, onclick) {
    return _add(win, {"kind": "button", "text": title, "x": x, "y": y, "w": w, "h": h, "win": win, "onclick": onclick})
}

checkbox#

fn checkbox(_, win, title, x, y, w, h, onchange)

Undocumented.

Source lib/ui_x11.mu:82
fn checkbox(_, win, title, x, y, w, h, onchange) {
    return _add(win, {"kind": "checkbox", "text": title, "checked": false, "x": x, "y": y, "w": w, "h": h, "win": win, "onchange": onchange})
}

get_checked#

fn get_checked(_, widget)

Undocumented.

Source lib/ui_x11.mu:90
fn get_checked(_, widget) {
    return widget["checked"]
}

get_text#

fn get_text(_, widget)

Undocumented.

Source lib/ui_x11.mu:134
fn get_text(_, widget) {
    if widget["kind"] == "window" {
        return widget["title"]
    }

    return widget["text"]
}

init#

fn init(_)

init defers connecting: the display is only touched when the first window is created, so importing ui stays harmless without one.

Source lib/ui_x11.mu:37
fn init(_) {
    return true
}

input#

fn input(_, win, text, x, y, w, h, onsubmit)

Undocumented.

Source lib/ui_x11.mu:78
fn input(_, win, text, x, y, w, h, onsubmit) {
    return _add(win, {"kind": "input", "text": text, "x": x, "y": y, "w": w, "h": h, "win": win, "onsubmit": onsubmit})
}

label#

fn label(_, win, text, x, y, w, h)

Undocumented.

Source lib/ui_x11.mu:70
fn label(_, win, text, x, y, w, h) {
    return _add(win, {"kind": "label", "text": text, "x": x, "y": y, "w": w, "h": h, "win": win})
}

place#

fn place(u, widget, x, y, w, h)

Undocumented.

Source lib/ui_x11.mu:142
fn place(u, widget, x, y, w, h) {
    widget["x"] = x
    widget["y"] = y
    widget["w"] = w
    widget["h"] = h
    return _redraw(u, widget["win"])
}

remove#

fn remove(u, widget)

remove drops a view from its window for good.

Source lib/ui_x11.mu:101
fn remove(u, widget) {
    win := widget["win"]
    kept := []
    views := win["views"]
    i := 0

    while i < len(views) {
        if !_same(views[i], widget) {
            kept = append(kept, views[i])
        }

        i = i + 1
    }

    win["views"] = kept

    if _same(win["focus"], widget) {
        win["focus"] = nil
    }

    return _redraw(u, win)
}

run#

fn run(u)

run multiplexes with the language's own tools: a reader task blocks in wait_event and feeds a channel, and this loop receives from it — with poll(ch, timeout_ms) bounding the wait when a timer is pending. poll(2) stays out of the µ syscall surface, as decided; the scheduler's timed receive is what µ has instead.

Source lib/ui_x11.mu:159
fn run(u) {
    x := u["x"]

    if x == nil {
        return error("ui: no window was created before run()")
    }

    events := chan(64)
    u["_x_stop"] = false
    task(fn() {
        while !u["_x_stop"] {
            ev := x11.wait_event(u["x"])
            // Bounded retry-push: the reader must never be stranded in a
            // blocking send after the loop has gone away.
            delivered := false

            while !delivered && !u["_x_stop"] {
                delivered = push(events, ev)

                if !delivered {
                    time.sleep(0.01)
                }
            }

            if is_error(ev) {
                return nil
            }
        }

        return nil
    })

    result := nil

    while u["running"] {
        _show_new_windows(u)
        // Fire due timers; the budget until the next one bounds the wait —
        // poll(ch, ms) is the scheduler's own timed receive, so a timer
        // deadline costs no waker tasks and wakes exactly on time.
        tick := u["tick"]
        budget := tick()
        ev := nil

        if budget == nil {
            ev = recv(events)
        } else {
            if budget < 1 {
                budget = 1
            }

            got := poll(events, budget)

            if !got[0] {
                // The budget elapsed: timers are due again.
                continue
            }

            ev = got[1]
        }

        if is_error(ev) {
            result = ev
            u["running"] = false
        } else {
            _handle(u, ev)
        }

        if !_any_window_open(u) {
            u["running"] = false
        }
    }

    // Tear down: closing the connection unblocks the reader, which sees the
    // dead socket and exits — nothing is left to keep the process alive.
    // The windows die with the connection, which real programs never see:
    // run() is their last act.
    u["_x_stop"] = true
    x11.close(u["x"])
    u["x"] = nil
    wins := u["windows"]
    i := 0

    while i < len(wins) {
        wins[i]["_closed"] = true
        i = i + 1
    }

    return result
}

set_checked#

fn set_checked(u, widget, v)

Undocumented.

Source lib/ui_x11.mu:94
fn set_checked(u, widget, v) {
    widget["checked"] = v == true
    return _redraw(u, widget["win"])
}

set_text#

fn set_text(u, widget, text)

Undocumented.

Source lib/ui_x11.mu:124
fn set_text(u, widget, text) {
    if widget["kind"] == "window" {
        widget["title"] = text
        return x11.set_title(u["x"], widget["wid"], text)
    }

    widget["text"] = text
    return _redraw(u, widget["win"])
}

textarea#

fn textarea(_, win, content, x, y, w, h)

Undocumented.

Source lib/ui_x11.mu:86
fn textarea(_, win, content, x, y, w, h) {
    return _add(win, {"kind": "textarea", "text": content, "x": x, "y": y, "w": w, "h": h, "win": win})
}

window#

fn window(u, title, w, h)

window creates (and lazily connects to) an X11 top-level window.

Source lib/ui_x11.mu:43
fn window(u, title, w, h) {
    if u["x"] == nil {
        x := x11.connect()

        if is_error(x) {
            return x
        }

        u["x"] = x
    }

    x := u["x"]
    wid := x11.create_window(x, w, h, title)

    if is_error(wid) {
        return wid
    }

    gc := x11.create_gc(x, wid, _BLACK, _WHITE)

    if is_error(gc) {
        return gc
    }

    return {"kind": "window", "wid": wid, "gc": gc, "title": title, "w": w, "h": h, "views": [], "focus": nil, "_shown": false, "_closed": false, "onclose": nil}
}

Internal helpers

Underscore-prefixed names are implementation detail. They are listed so the module's source reads without surprises, not as API — they may change at any time.

_ACCENT0x3580c4: focus ring
_ASCENT
_BLACK
_CHARW
_FACE0xe9e9e9: button face
_WHITE
_add(win, widget)
_any_window_open(u)
_draw_view(x, wid, gc, v, focus)
_find_window(u, wid)
_handle(u, ev)
_hit(win, px, py)
_ids
_redraw(u, win)
_same(a, b)
_show_new_windows(u)
_type_key(u, win, key)