{"record":{"id":"373316b0f6c0f2cc","repo":"elkowar/eww","slug":"failed-to-obtain-toplevel-window","errorCode":null,"errorMessage":"Failed to obtain toplevel window","messagePattern":"Failed to obtain toplevel window","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/eww/src/widgets/systray.rs","lineNumber":199,"sourceCode":"        // TODO this is a lot of code duplication unfortunately, i'm not really sure how to\n        // refactor without making the borrow checker angry\n\n        // set status\n        match item.status().await? {\n            notifier_host::Status::Passive => widget.hide(),\n            notifier_host::Status::Active | notifier_host::Status::NeedsAttention => widget.show(),\n        }\n\n        // set title\n        widget.set_tooltip_text(Some(&item.sni.title().await?));\n\n        // set icon\n        let scale = icon.scale_factor();\n        load_icon_for_item(&icon, &item, *icon_size.borrow_and_update(), scale).await;\n\n        let item = Rc::new(item);\n        let window =\n            widget.toplevel().expect(\"Failed to obtain toplevel window\").downcast::<Window>().expect(\"Failed to downcast window\");\n        widget.add_events(gdk::EventMask::BUTTON_PRESS_MASK);\n        widget.connect_button_press_event(glib::clone!(@strong item => move |_, evt| {\n            let (x, y) = (evt.root().0 as i32 + window.x(), evt.root().1 as i32 + window.y());\n            let item_is_menu = run_async_task(async { item.sni.item_is_menu().await });\n            let have_item_is_menu = item_is_menu.is_ok();\n            let item_is_menu = item_is_menu.unwrap_or(false);\n            log::debug!(\n                \"mouse click button={}, x={}, y={}, have_item_is_menu={}, item_is_menu={}\",\n                evt.button(),\n                x,\n                y,\n                have_item_is_menu,\n                item_is_menu\n            );\n\n            let result = match (evt.button(), item_is_menu) {\n                (gdk::BUTTON_PRIMARY, false) => {\n                    let result = run_async_task(async { item.sni.activate(x, y).await });","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/elkowar/eww/blob/48f5aa8b379adf29da0b0bb9ca04164f65d8bdaa/crates/eww/src/widgets/systray.rs#L181-L217","documentation":"For systray items, widget.toplevel() returns None until the widget is realized and mapped into a window; eww expects it and additionally downcasts to gtk::Window. Panicking here means the tray icon widget had no toplevel GTK window at the moment `maintain` ran.","triggerScenarios":"maintain() processing a StatusNotifier item while the containing widget is not yet realized/mapped — e.g. the tray is created before the bar window exists, the bar was re-created during a reload, or the item arrived during window teardown — so toplevel() yields None; a non-Window toplevel also fails the downcast.","commonSituations":"Eww bars reloaded or re-created while a systray item connects, tray widgets in popup/unmapped containers, and race conditions on startup where items register faster than the bar window maps.","solutions":["Defer the work until the widget is mapped: only call toplevel() inside a connect_map/realize handler, or skip the item if toplevel() is None and retry on the next maintain pass.","Restart/reload the bar so the tray widget is realized before items are processed.","Update eww — hardening this race (items arriving before the window maps) is a known class of systray bug worth reporting upstream.","Replace both expects with logging and early-return so a single bad item cannot kill the daemon."],"exampleFix":"// before\nlet window = widget\n    .toplevel().expect(\"Failed to obtain toplevel window\")\n    .downcast::<Window>().expect(\"Failed to downcast window\");\n// after\nlet window = match widget.toplevel().and_then(|t| t.downcast::<Window>().ok()) {\n    Some(w) => w,\n    None => {\n        log::warn!(\"Systray item has no toplevel window yet; skipping\");\n        return;\n    }\n};","handlingStrategy":"type-guard","validationCode":"// only proceed once the widget is realized and mapped\nif !widget.is_realized() || widget.toplevel().is_none() { return; }","typeGuard":"fn toplevel_window(widget: &gtk::Widget) -> Option<gtk::Window> {\n    widget.toplevel().and_then(|t| t.downcast::<gtk::Window>().ok())\n}","tryCatchPattern":"// replace expect with early return\nlet window = match toplevel_window(&widget) {\n    Some(w) => w,\n    None => { log::warn!(\"no toplevel window for tray item yet\"); return; }\n};","preventionTips":["Initialize the systray only after the bar window is mapped (connect_map)","Handle reload/recreate races by re-running maintain on map events","Never .expect() on toplevel(); it is legitimately None pre-mapping","Keep eww updated; this is a known race class in tray code"],"tags":["gtk","systray","window","race-condition"],"backgroundTag":"null-argument","analyzedSha":"48f5aa8b379adf29da0b0bb9ca04164f65d8bdaa","analyzedAt":"2026-09-08T03:13:26.897Z","contentChangedAt":"2026-09-08T03:13:26.897Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}