Gnoblin

Session locking

Gnoblin uses the standard ext-session-lock-v1 Wayland protocol in the Gnoblin session. The compositor owns the security boundary: it covers every output, stops normal input, replaces desktop capture with its lock scene, and stays locked if a locker exits. The locker owns its appearance and authentication. See the protocol XML.

The lock screen is a separate client. Bingux is one shell project with its own lock client.

Third-party lockers such as hyprlock, swaylock, gtklock and waylock can connect directly to the same standard protocol. Any client with access to this user's Wayland socket can request the lock; Gnoblin does not filter requests by process. The first request owns the lock, and concurrent requests receive finished.

In Bingux, use its Lock action or run bingux-lock. With another locker installed, run its command (for example, hyprlock) in your Gnoblin session. Your shell or idle daemon decides when to run that command; Gnoblin does not set a timeout or launch a locker for you.

The protocol is enabled by default in Gnoblin. To hide its global, set ext_session_lock = false under gnoblin.configure.protocols, then log out and back in; protocol globals are registered at compositor startup. See the protocol setting reference.

Request a lock

A lock request asks your shell to show its lock screen. Gnoblin sends the request as the gnoblin.session.lock-requested event to every shell client that subscribed to it, and to the Lua configuration when it listens for the event. The shell starts its locker, and the locker takes the lock with ext-session-lock-v1. Gnoblin binds no key and starts no locker.

These sources send the request:

  • gnoblinctl session lock
  • gnoblin.session.lock() in the Lua configuration
  • org.freedesktop.ScreenSaver.Lock, or SetActive(true), on the session bus
  • loginctl lock-session for your graphical session, and a lid or idle policy that asks logind to lock it

A request does not prove the session is locked. The call reports whether a shell received the request. Read lock_state in gnoblinctl status or gnoblin.session.status() to see the real state.

If no shell is subscribed, the call fails and says so. gnoblinctl exits with an error, and ScreenSaver.Lock returns the error no session-lock client is subscribed to lock requests. A logind request writes the same message to the journal. Gnoblin leaves the session unlocked in this case.

A request while the session is already locked succeeds and sends no second event. SetActive(false) returns NotSupported, because only the locker can unlock.

ScreenSaver.GetActive returns false for unlocked and true for covering, locked, or failsafe. These are the compositor's lock states; only locked confirms the lock transition.

Subscribe to org.freedesktop.ScreenSaver.ActiveChanged on the session bus at /org/freedesktop/ScreenSaver to receive that boolean when it changes. Read GetActive for the initial value. Changes between active lock states do not send duplicate signals. Use gnoblin.session.lock-state-changed when you need the individual state transitions.

If the compositor connection is interrupted, Gnoblin sends no unlock signal. It reconnects and reports a changed boolean from the current state. After the idle service restarts, read GetActive again for its initial value.

To lock with a key of your choice, bind a key in the Lua configuration:

Lua
gnoblin.configure {
    keybindings = {
        keyboard = {
            lock_screen = {
                binding = "<Super>l",
                callback = function()
                    gnoblin.session.lock()
                end,
            },
        },
    },
}

To receive the request in your Lua configuration, listen for the event and start the locker. This handler starts swaylock on each request and logs when the lock does not take effect:

Lua
gnoblin.on("gnoblin.session.lock-requested", function(event)
    gnoblin.process.spawn({"swaylock", "-f"})
    event.request:finished():on_complete(function(outcome)
        if outcome ~= "locked" then
            print("swaylock did not lock the session")
        end
    end)
end)

event.request is a Request of kind "lock". Its finished() Operation reports one of two outcomes:

  • "locked": the session locked.
  • "timeout": the session did not lock within 30 seconds.

A shell process that connects to the compositor bridge subscribes to the same event.

Check the result from a terminal:

Shell
gnoblinctl session lock
gnoblinctl status

lock_state changes from unlocked to locked after the locker takes the lock.

Portal and remote access while locked

Remote and portal streams are routed by the selected portal backend. While the session is locked, a monitor stream that is already running receives the compositor lock scene rather than normal desktop content.

Remote keyboard and pointer input is available after locked and lock-scene presentation, and is routed only to the active lock surface. It is refused during the transition and failsafe states. No lock-specific portal setting is required.

Locker requirements

A locker binds ext_session_lock_manager_v1 and sends lock. The compositor then sends locked or finished on the new lock object. finished means the request was not granted; do not show an authentication UI for that lock.

After requesting the lock, create one wl_surface per output and pass each to get_lock_surface before attaching or committing a buffer. An output can have only one lock surface for this lock. For every configure(serial, width, height), acknowledge its serial before committing a non-null buffer with those exact surface-local dimensions. If multiple configures arrive before a commit, acknowledging the last one is sufficient.

Interface Request or event Contract
ext_session_lock_manager_v1 destroy Release the manager object; existing lock objects remain valid.
ext_session_lock_manager_v1 lock Create a lock request; it later receives locked or finished.
ext_session_lock_v1 destroy Cancel or retire a lock only before locked; destroy its lock surfaces too.
ext_session_lock_v1 get_lock_surface(surface, output) Create the output's lock surface before the wl_surface has a role or buffer.
ext_session_lock_v1 locked The lock scene has been presented and it is safe to treat the session as locked.
ext_session_lock_v1 finished The compositor did not grant this lock request.
ext_session_lock_v1 unlock_and_destroy After authentication succeeds and locked was received, release the session lock.
ext_session_lock_surface_v1 ack_configure(serial) Acknowledge a configure before committing its buffer.
ext_session_lock_surface_v1 configure(serial, width, height) Resize to the exact output-local dimensions, acknowledge serial, then commit a matching buffer.
ext_session_lock_surface_v1 destroy Retire the lock surface. Removing one for an active output before unlock makes Gnoblin show a solid color there.

After locked, only the owner calls unlock_and_destroy; destroying the lock object instead is a protocol error. If the client exits immediately after unlocking, send wl_display.sync and wait for its callback before disconnecting so the compositor processes the request. A locker crash leaves the compositor covered and locked; another client may acquire the lock afterwards.

The protocol rejects a null lock-surface buffer, a commit before the first configure is acknowledged, a buffer with the wrong dimensions, duplicate output surfaces, and destroying the active lock without unlocking. See the protocol XML for the complete error enums.

Esc

Try