slint-ui / slint

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.
https://slint.dev
Other
17.67k stars 611 forks source link

Window property always-on-top does not work on linux #6691

Open michaelrampl opened 3 weeks ago

michaelrampl commented 3 weeks ago

On Linux (nixos) both under x11 and wayland (tested using KDE), the always-on-top property doesn't seem to work

Cargo.toml

[package]
name = "example"
version = "0.1.0"
edition = "2021"

[dependencies]
slint = "1.8.0"

[build-dependencies]
slint-build = "1.8.0"

main.slint

import { Button } from "std-widgets.slint";

export component MainWindow inherits Window {
    always-on-top: true;
}

main.rs

slint::include_modules!();
fn main() {
    MainWindow::new().unwrap().run().unwrap();
}

It looks like SLINT uses the winit crate which should support the feature tough https://docs.rs/winit/latest/winit/window/enum.WindowLevel.html#variant.AlwaysOnTop

Vadoola commented 3 weeks ago

If I recall correctly the always on top works(ed) on X11 but didn't on Wayland. My project that uses this features has taken a back sear the past couple of months, but I thought that's what I remember from using it.

When you tested it using X11 was it a full X11 instance, or running under XWayland?

michaelrampl commented 3 weeks ago

I've had full X11 and XWayland sessions running individually. When starting the X11 sessions, loginctl show-session 2 -p Type also shows x11 correctly. If wayland is not supported (yet), It should be mentioned in the documentation.

Vadoola commented 3 weeks ago

If wayland is not supported (yet), It should be mentioned in the documentation.

The documentation does says on Window Managers that support it. I don't know enough about Wayland to know if that's a Wayland issue or lack of Support for a Wayland protocol. One of the actual Slint developers might have more to offer here.

I was testing it on an Arch install under Wayland with Gnome and it didn't work. I thought I remember testing on X11 as well and it did work, but it's been a while.

I'll see if I can load up my system, as well as get a KDE install and maybe some other to try my program on. Might get some more info out of it.

michaelrampl commented 3 weeks ago

Having a plain simple winit example featuring with_window_level(winit::window::WindowLevel::AlwaysOnTop) seems to work on X11

use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::window::{Window, WindowId};

#[derive(Default)]
struct App {
    window: Option<Window>,
}

impl ApplicationHandler for App {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        println!("resumed");
        self.window = Some(event_loop.create_window(Window::default_attributes().with_window_level(winit::window::WindowLevel::AlwaysOnTop)).unwrap());
    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) {
        match event {
            WindowEvent::CloseRequested => {
                println!("The close button was pressed; stopping");
                event_loop.exit();
            },
            WindowEvent::RedrawRequested => {
                self.window.as_ref().unwrap().request_redraw();
            }
            _ => (),
        }
    }
}

fn main() {
    let event_loop = EventLoop::new().unwrap();
    event_loop.set_control_flow(ControlFlow::Poll);
    event_loop.set_control_flow(ControlFlow::Wait);
    let mut app = App::default();
    let _ = event_loop.run_app(&mut app);
}
michaelrampl commented 3 weeks ago

Also https://rust-windowing.github.io/winit/winit/window/enum.WindowLevel.html says it's currently unsupported on wayland

Vadoola commented 3 weeks ago

Having a plain simple winit example featuring with_window_level(winit::window::WindowLevel::AlwaysOnTop) seems to work on X11

Certainly sounds like a Slint issue then. I'll try and test mine and report back. My project is still back on Slint 1.6...although I want to say the last version I tested on X11 was back on v1.2 or 1.4. I wonder if this problem cropped up I'm one of the newer versions.

ogoffart commented 3 weeks ago

I try to reproduce this and there is indeed a bug. With winit, backend, I verified that we do indeed call winit::Window::set_window_level with the right level. I also noticed that if you have something like CheckBox { toggled => { root.always-on-top = self.checked; } } it works. But for some reason, it doesn't work at startup.

With Qt, disabling the always-on-top actually hide the window.

To me, it looks like what we pass to the underlying library (Qt, winit) is correct, and something wrong happens in these library or in the window manager.

Vadoola commented 3 weeks ago

I appear to be having some instability issues with X11 on my setup at the moment, but doing some quick testing I am experiencing this bug in the current version of Tomotroid which is using Slint 1.7.2. I checked out an older version compiling with Slint 1.6, and the always on top seemed to be working (as well as I could test with the instability issues).

So it looks like the bug cropped up somewhere between Slint 1.6 and 1.7.2. Hopefully that helps narrow down where the issue might lie.

ogoffart commented 2 weeks ago

Maybe this is caused by the update of winit to winit 0.30

michaelrampl commented 2 weeks ago

I can confirm that the set_window_level function is also being called correctly in internal/backends/winit/winitwindowadapter.rs:update_window_properties (at least the first time)

I also can confirm that the checkbox hack works.

As a dirty workaround one could use a single-shot timer like this:

Timer {
  interval: 100ms;
  running: true;
  triggered => {
    root.always-on-top = true;
    self.running = false;
  }
}

Without the hack the following happens: Upon start, the code

if self.window_level.replace(new_window_level) != new_window_level {
  winit_window_or_none.set_window_level(new_window_level);
}

is being called 3 times. The first time the level will be set to the winit::window::Window object. The second and third time it will not be set because the value stays the same for WinitWindowAdapter. So i would assume that somewhere after the first call, the level of the underlying winit::window::Window gets changed without WinitWindowAdapter noticing it.

Removing the condition, thus always setting the level seems to solve the issue

set_window_level(new_window_level);

This most likely will not be the fix we want as it might affect https://github.com/slint-ui/slint/issues/4225. But for me this confirms the theory that something is happening to the state underneath after the first call.