elkowar/eww · critical

could not get default display

Error message

could not get default display

What it means

This panic comes from gtk::gdk::Display::default() returning None, meaning GDK could not open a connection to the display server. eww's daemon expects a valid default display (X11 or Wayland) so it can watch for monitor connect/disconnect events and reload the configuration when one appears.

Solutions

  1. Ensure a display server is reachable: verify `echo $DISPLAY` (X11) or `XDG_SESSION_TYPE=wayland` and that the daemon inherits the session environment (`dbus-launch`, `systemctl --user import-environment DISPLAY WAYLAND_DISPLAY`).
  2. Start eww from inside the graphical session (autostart/desktop entry) rather than from a login shell or system service.
  3. Under Wayland, use `eww daemon` with GDK Wayland support, or force the X11 backend via `GDK_BACKEND=x11` with XWayland running.
  4. If running headless intentionally, guard the call: fall back to skipping monitor watching when Display::default() is None instead of panicking.
  5. Check that GDK was compiled with a usable backend (`GDK_BACKEND=x11,wayland` debug) and that libgtk is the expected version.

Example fix

// before
let display = gtk::gdk::Display::default().expect("could not get default display");
display.connect_monitor_added(...);
// after
let display = match gtk::gdk::Display::default() {
    Some(d) => d,
    None => {
        log::warn!("No default display available; monitor watching disabled");
        return;
    }
};
display.connect_monitor_added(...);
Defensive patterns

Strategy: fallback

Validate before calling

// before starting the daemon
if std::env::var("DISPLAY").is_err() && std::env::var("WAYLAND_DISPLAY").is_err() {
    eprintln!("No DISPLAY/WAYLAND_DISPLAY set; eww needs a graphical session");
    std::process::exit(1);
}

Type guard

fn has_default_display() -> bool { gtk::gdk::Display::default().is_some() }

Try / catch

// Rust panics cannot be caught idiomatically here; use catch_unwind only as a last resort
let result = std::panic::catch_unwind(connect_monitor_added_safe);

Prevention

When it happens

Trigger: Calling connect_monitor_added (during initialize_server) in an environment where no display is available: DISPLAY unset or pointing at a dead X server, Wayland without XWayland/GDK backend, running under systemd before graphical.target, SSH session without X forwarding, or DISPLAY set to a display that has shut down.

Common situations: Users starting `eww daemon` from cron/systemd user units before login, SSH shells, CI containers, or after logging out of the graphical session while the daemon keeps running. Wayland-only setups where GDK was built without the wayland backend.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of elkowar/eww@48f5aa8b37 (2026-09-08). Data as JSON: /api/errors/24805d36cce36374. Report an issue: GitHub.

Appendix: source

Thrown at crates/eww/src/server.rs:142

                Some(ui_event) = ui_recv.recv() => {
                    app.handle_command(ui_event).await;
                }
                else => break,
            }
        }
    });

    // allow the GTK main thread to do tokio things
    let _g = tokio_handle.enter();

    gtk::main();
    log::info!("main application thread finished");

    Ok(ForkResult::Child)
}

fn connect_monitor_added(ui_send: UnboundedSender<DaemonCommand>) {
    let display = gtk::gdk::Display::default().expect("could not get default display");
    display.connect_monitor_added({
        move |_display: &gtk::gdk::Display, _monitor: &gtk::gdk::Monitor| {
            log::info!("New monitor connected, reloading configuration");
            let _ = reload_config_and_css(&ui_send);
        }
    });
}

fn reload_config_and_css(ui_send: &UnboundedSender<DaemonCommand>) -> Result<()> {
    let (daemon_resp_sender, mut daemon_resp_response) = daemon_response::create_pair();
    ui_send.send(DaemonCommand::ReloadConfigAndCss(daemon_resp_sender))?;
    tokio::spawn(async move {
        match daemon_resp_response.recv().await {
            Some(daemon_response::DaemonResponse::Success(_)) => log::info!("Reloaded config successfully"),
            Some(daemon_response::DaemonResponse::Failure(e)) => eprintln!("{}", e),
            None => log::error!("No response to reload configuration-reload request"),
        }
    });

View on GitHub (pinned to 48f5aa8b37)