diff options
| author | Ivan Molodetskikh <yalterz@gmail.com> | 2024-02-06 09:01:26 +0400 |
|---|---|---|
| committer | Ivan Molodetskikh <yalterz@gmail.com> | 2024-02-06 09:40:45 +0400 |
| commit | 122afff7d10ac96250f428a3f06dca6288d3d0b8 (patch) | |
| tree | 1c6d89f1ee1cf08c42be102907a9e4594dedbb1a /niri-visual-tests/src | |
| parent | d2a4e6a0cbc839813c6d4ef68b75820d87cfc5b0 (diff) | |
| download | niri-122afff7d10ac96250f428a3f06dca6288d3d0b8.tar.gz niri-122afff7d10ac96250f428a3f06dca6288d3d0b8.tar.bz2 niri-122afff7d10ac96250f428a3f06dca6288d3d0b8.zip | |
Add niri-visual-tests
Diffstat (limited to 'niri-visual-tests/src')
| -rw-r--r-- | niri-visual-tests/src/cases/mod.rs | 21 | ||||
| -rw-r--r-- | niri-visual-tests/src/cases/tile.rs | 83 | ||||
| -rw-r--r-- | niri-visual-tests/src/cases/window.rs | 57 | ||||
| -rw-r--r-- | niri-visual-tests/src/main.rs | 142 | ||||
| -rw-r--r-- | niri-visual-tests/src/smithay_view.rs | 245 | ||||
| -rw-r--r-- | niri-visual-tests/src/test_window.rs | 206 |
6 files changed, 754 insertions, 0 deletions
diff --git a/niri-visual-tests/src/cases/mod.rs b/niri-visual-tests/src/cases/mod.rs new file mode 100644 index 00000000..b7d71cd0 --- /dev/null +++ b/niri-visual-tests/src/cases/mod.rs @@ -0,0 +1,21 @@ +use std::time::Duration; + +use smithay::backend::renderer::element::RenderElement; +use smithay::backend::renderer::gles::GlesRenderer; +use smithay::utils::{Physical, Size}; + +pub mod tile; +pub mod window; + +pub trait TestCase { + fn resize(&mut self, width: i32, height: i32); + fn are_animations_ongoing(&self) -> bool { + false + } + fn advance_animations(&mut self, _current_time: Duration) {} + fn render( + &mut self, + renderer: &mut GlesRenderer, + size: Size<i32, Physical>, + ) -> Vec<Box<dyn RenderElement<GlesRenderer>>>; +} diff --git a/niri-visual-tests/src/cases/tile.rs b/niri-visual-tests/src/cases/tile.rs new file mode 100644 index 00000000..cdf1e5f6 --- /dev/null +++ b/niri-visual-tests/src/cases/tile.rs @@ -0,0 +1,83 @@ +use std::rc::Rc; +use std::time::Duration; + +use niri::layout::tile::Tile; +use niri::layout::Options; +use niri_config::Color; +use smithay::backend::renderer::element::RenderElement; +use smithay::backend::renderer::gles::GlesRenderer; +use smithay::utils::{Logical, Physical, Point, Scale, Size}; + +use super::TestCase; +use crate::test_window::TestWindow; + +pub struct JustTile { + window: TestWindow, + tile: Tile<TestWindow>, +} + +impl JustTile { + pub fn freeform(size: Size<i32, Logical>) -> Self { + let window = TestWindow::freeform(0); + let mut rv = Self::with_window(window); + rv.tile.request_tile_size(size); + rv.window.communicate(); + rv + } + + pub fn fixed_size(size: Size<i32, Logical>) -> Self { + let window = TestWindow::fixed_size(0); + let mut rv = Self::with_window(window); + rv.tile.request_tile_size(size); + rv.window.communicate(); + rv + } + + pub fn fixed_size_with_csd_shadow(size: Size<i32, Logical>) -> Self { + let window = TestWindow::fixed_size(0); + window.set_csd_shadow_width(64); + let mut rv = Self::with_window(window); + rv.tile.request_tile_size(size); + rv.window.communicate(); + rv + } + + pub fn with_window(window: TestWindow) -> Self { + let options = Options { + border: niri_config::FocusRing { + off: false, + width: 32, + active_color: Color::new(255, 163, 72, 255), + ..Default::default() + }, + ..Default::default() + }; + let tile = Tile::new(window.clone(), Rc::new(options)); + Self { window, tile } + } +} + +impl TestCase for JustTile { + fn resize(&mut self, width: i32, height: i32) { + self.tile.request_tile_size(Size::from((width, height))); + self.window.communicate(); + } + + fn advance_animations(&mut self, current_time: Duration) { + self.tile.advance_animations(current_time, true); + } + + fn render( + &mut self, + renderer: &mut GlesRenderer, + size: Size<i32, Physical>, + ) -> Vec<Box<dyn RenderElement<GlesRenderer>>> { + let tile_size = self.tile.tile_size().to_physical(1); + let location = Point::from(((size.w - tile_size.w) / 2, (size.h - tile_size.h) / 2)); + + self.tile + .render(renderer, location, Scale::from(1.)) + .map(|elem| Box::new(elem) as _) + .collect() + } +} diff --git a/niri-visual-tests/src/cases/window.rs b/niri-visual-tests/src/cases/window.rs new file mode 100644 index 00000000..869cbf45 --- /dev/null +++ b/niri-visual-tests/src/cases/window.rs @@ -0,0 +1,57 @@ +use niri::layout::LayoutElement; +use smithay::backend::renderer::element::RenderElement; +use smithay::backend::renderer::gles::GlesRenderer; +use smithay::utils::{Logical, Physical, Point, Scale, Size}; + +use super::TestCase; +use crate::test_window::TestWindow; + +pub struct JustWindow { + window: TestWindow, +} + +impl JustWindow { + pub fn freeform(size: Size<i32, Logical>) -> Self { + let window = TestWindow::freeform(0); + window.request_size(size); + window.communicate(); + Self { window } + } + + pub fn fixed_size(size: Size<i32, Logical>) -> Self { + let window = TestWindow::fixed_size(0); + window.request_size(size); + window.communicate(); + Self { window } + } + + pub fn fixed_size_with_csd_shadow(size: Size<i32, Logical>) -> Self { + let window = TestWindow::fixed_size(0); + window.set_csd_shadow_width(64); + window.request_size(size); + window.communicate(); + Self { window } + } +} + +impl TestCase for JustWindow { + fn resize(&mut self, width: i32, height: i32) { + self.window.request_size(Size::from((width, height))); + self.window.communicate(); + } + + fn render( + &mut self, + renderer: &mut GlesRenderer, + size: Size<i32, Physical>, + ) -> Vec<Box<dyn RenderElement<GlesRenderer>>> { + let win_size = self.window.size().to_physical(1); + let location = Point::from(((size.w - win_size.w) / 2, (size.h - win_size.h) / 2)); + + self.window + .render(renderer, location, Scale::from(1.)) + .into_iter() + .map(|elem| Box::new(elem) as _) + .collect() + } +} diff --git a/niri-visual-tests/src/main.rs b/niri-visual-tests/src/main.rs new file mode 100644 index 00000000..99919043 --- /dev/null +++ b/niri-visual-tests/src/main.rs @@ -0,0 +1,142 @@ +#[macro_use] +extern crate tracing; + +use std::env; +use std::sync::atomic::Ordering; + +use adw::prelude::{AdwApplicationWindowExt, NavigationPageExt}; +use cases::tile::JustTile; +use cases::window::JustWindow; +use gtk::prelude::{ + AdjustmentExt, ApplicationExt, ApplicationExtManual, BoxExt, GtkWindowExt, WidgetExt, +}; +use gtk::{gdk, gio, glib}; +use niri::animation::ANIMATION_SLOWDOWN; +use smithay::utils::{Logical, Size}; +use smithay_view::SmithayView; +use tracing_subscriber::EnvFilter; + +use crate::cases::TestCase; + +mod cases; +mod smithay_view; +mod test_window; + +fn main() -> glib::ExitCode { + let directives = + env::var("RUST_LOG").unwrap_or_else(|_| "niri-visual-tests=debug,niri=debug".to_owned()); + let env_filter = EnvFilter::builder().parse_lossy(directives); + tracing_subscriber::fmt() + .compact() + .with_env_filter(env_filter) + .init(); + + let app = adw::Application::new(None::<&str>, gio::ApplicationFlags::NON_UNIQUE); + app.connect_startup(on_startup); + app.connect_activate(build_ui); + app.run() +} + +fn on_startup(_app: &adw::Application) { + // Load our CSS. + let provider = gtk::CssProvider::new(); + provider.load_from_string(include_str!("../resources/style.css")); + if let Some(display) = gdk::Display::default() { + gtk::style_context_add_provider_for_display( + &display, + &provider, + gtk::STYLE_PROVIDER_PRIORITY_APPLICATION, + ); + } +} + +fn build_ui(app: &adw::Application) { + let stack = gtk::Stack::new(); + + struct S { + stack: gtk::Stack, + } + + impl S { + fn add<T: TestCase + 'static>( + &self, + make: impl Fn(Size<i32, Logical>) -> T + 'static, + title: &str, + ) { + let view = SmithayView::new(make); + self.stack.add_titled(&view, None, title); + } + } + + let s = S { + stack: stack.clone(), + }; + + s.add(JustWindow::freeform, "Freeform Window"); + s.add(JustWindow::fixed_size, "Fixed Size Window"); + s.add( + JustWindow::fixed_size_with_csd_shadow, + "Fixed Size Window - CSD Shadow", + ); + s.add(JustTile::freeform, "Freeform Tile"); + s.add(JustTile::fixed_size, "Fixed Size Tile"); + s.add( + JustTile::fixed_size_with_csd_shadow, + "Fixed Size Tile - CSD Shadow", + ); + + let content_headerbar = adw::HeaderBar::new(); + + let anim_adjustment = gtk::Adjustment::new(1., 0., 10., 0.1, 0.5, 0.); + anim_adjustment + .connect_value_changed(|adj| ANIMATION_SLOWDOWN.store(adj.value(), Ordering::SeqCst)); + let anim_scale = gtk::Scale::new(gtk::Orientation::Horizontal, Some(&anim_adjustment)); + anim_scale.set_hexpand(true); + + let anim_control_bar = gtk::Box::new(gtk::Orientation::Horizontal, 6); + anim_control_bar.add_css_class("anim-control-bar"); + anim_control_bar.append(>k::Label::new(Some("Slowdown"))); + anim_control_bar.append(&anim_scale); + + let content_view = adw::ToolbarView::new(); + content_view.set_top_bar_style(adw::ToolbarStyle::RaisedBorder); + content_view.set_bottom_bar_style(adw::ToolbarStyle::RaisedBorder); + content_view.add_top_bar(&content_headerbar); + content_view.add_bottom_bar(&anim_control_bar); + content_view.set_content(Some(&stack)); + let content = adw::NavigationPage::new( + &content_view, + stack + .page(&stack.visible_child().unwrap()) + .title() + .as_deref() + .unwrap(), + ); + + let sidebar_header = adw::HeaderBar::new(); + let stack_sidebar = gtk::StackSidebar::new(); + stack_sidebar.set_stack(&stack); + let sidebar_view = adw::ToolbarView::new(); + sidebar_view.add_top_bar(&sidebar_header); + sidebar_view.set_content(Some(&stack_sidebar)); + let sidebar = adw::NavigationPage::new(&sidebar_view, "Tests"); + + let split_view = adw::NavigationSplitView::new(); + split_view.set_content(Some(&content)); + split_view.set_sidebar(Some(&sidebar)); + + stack.connect_visible_child_notify(move |stack| { + content.set_title( + stack + .visible_child() + .and_then(|c| stack.page(&c).title()) + .as_deref() + .unwrap_or_default(), + ) + }); + + let window = adw::ApplicationWindow::new(app); + window.set_title(Some("niri visual tests")); + window.set_content(Some(&split_view)); + window.present(); +} diff --git a/niri-visual-tests/src/smithay_view.rs b/niri-visual-tests/src/smithay_view.rs new file mode 100644 index 00000000..db8eb9ec --- /dev/null +++ b/niri-visual-tests/src/smithay_view.rs @@ -0,0 +1,245 @@ +use gtk::glib; +use gtk::subclass::prelude::*; +use smithay::utils::{Logical, Size}; + +use crate::cases::TestCase; + +mod imp { + use std::cell::{Cell, OnceCell, RefCell}; + use std::ptr::null; + + use anyhow::{ensure, Context}; + use gtk::gdk; + use gtk::prelude::*; + use niri::utils::get_monotonic_time; + use smithay::backend::egl::ffi::egl; + use smithay::backend::egl::EGLContext; + use smithay::backend::renderer::gles::{Capability, GlesRenderer}; + use smithay::backend::renderer::{Frame, Renderer, Unbind}; + use smithay::utils::{Physical, Rectangle, Scale, Transform}; + + use super::*; + + type DynMakeTestCase = Box<dyn Fn(Size<i32, Logical>) -> Box<dyn TestCase>>; + + #[derive(Default)] + pub struct SmithayView { + gl_area: gtk::GLArea, + size: Cell<(i32, i32)>, + renderer: RefCell<Option<Result<GlesRenderer, ()>>>, + pub make_test_case: OnceCell<DynMakeTestCase>, + test_case: RefCell<Option<Box<dyn TestCase>>>, + } + + #[glib::object_subclass] + impl ObjectSubclass for SmithayView { + const NAME: &'static str = "NiriSmithayView"; + type Type = super::SmithayView; + type ParentType = gtk::Widget; + + fn class_init(klass: &mut Self::Class) { + klass.set_layout_manager_type::<gtk::BinLayout>(); + } + } + + impl ObjectImpl for SmithayView { + fn constructed(&self) { + let obj = self.obj(); + + self.parent_constructed(); + + self.gl_area.set_allowed_apis(gdk::GLAPI::GLES); + self.gl_area.set_parent(&*obj); + + self.gl_area.connect_resize({ + let imp = self.downgrade(); + move |_, width, height| { + if let Some(imp) = imp.upgrade() { + imp.resize(width, height); + } + } + }); + + self.gl_area.connect_render({ + let imp = self.downgrade(); + move |_, gl_context| { + if let Some(imp) = imp.upgrade() { + if let Err(err) = imp.render(gl_context) { + warn!("error rendering: {err:?}"); + } + } + glib::Propagation::Stop + } + }); + + obj.add_tick_callback(|obj, _frame_clock| { + let imp = obj.imp(); + + if let Some(case) = &mut *imp.test_case.borrow_mut() { + if case.are_animations_ongoing() { + imp.gl_area.queue_draw(); + } + } + + glib::ControlFlow::Continue + }); + } + + fn dispose(&self) { + self.gl_area.unparent(); + } + } + + impl WidgetImpl for SmithayView { + fn unmap(&self) { + self.test_case.replace(None); + self.parent_unmap(); + } + + fn unrealize(&self) { + self.renderer.replace(None); + self.parent_unrealize(); + } + } + + impl SmithayView { + fn resize(&self, width: i32, height: i32) { + self.size.set((width, height)); + + if let Some(case) = &mut *self.test_case.borrow_mut() { + case.resize(width, height); + } + } + + fn render(&self, _gl_context: &gdk::GLContext) -> anyhow::Result<()> { + // Set up the Smithay renderer. + let mut renderer = self.renderer.borrow_mut(); + let renderer = renderer.get_or_insert_with(|| { + unsafe { create_renderer() } + .map_err(|err| warn!("error creating a Smithay renderer: {err:?}")) + }); + let Ok(renderer) = renderer else { + return Ok(()); + }; + + let size = self.size.get(); + + // Create the test case if missing. + let mut case = self.test_case.borrow_mut(); + let case = case.get_or_insert_with(|| { + let make = self.make_test_case.get().unwrap(); + make(Size::from(size)) + }); + + case.advance_animations(get_monotonic_time()); + + let rect: Rectangle<i32, Physical> = Rectangle::from_loc_and_size((0, 0), size); + + let elements = unsafe { + with_framebuffer_save_restore(renderer, |renderer| { + case.render(renderer, Size::from(size)) + }) + }?; + + let mut frame = renderer + .render(rect.size, Transform::Normal) + .context("error creating frame")?; + + frame + .clear([0.3, 0.3, 0.3, 1.], &[rect]) + .context("error clearing")?; + + for element in elements.iter().rev() { + let src = element.src(); + let dst = element.geometry(Scale::from(1.)); + + if let Some(mut damage) = rect.intersection(dst) { + damage.loc -= dst.loc; + element + .draw(&mut frame, src, dst, &[damage]) + .context("error drawing element")?; + } + } + + Ok(()) + } + } + + unsafe fn create_renderer() -> anyhow::Result<GlesRenderer> { + smithay::backend::egl::ffi::make_sure_egl_is_loaded() + .context("error loading EGL symbols in Smithay")?; + + let egl_display = egl::GetCurrentDisplay(); + ensure!(egl_display != egl::NO_DISPLAY, "no current EGL display"); + + let egl_context = egl::GetCurrentContext(); + ensure!(egl_context != egl::NO_CONTEXT, "no current EGL context"); + + // There's no config ID on the EGL context and there's no current EGL surface, but we don't + // really use it anyway so just get some random one. + let mut egl_config_id = null(); + let mut num_configs = 0; + let res = egl::GetConfigs(egl_display, &mut egl_config_id, 1, &mut num_configs); + ensure!(res == egl::TRUE, "error choosing EGL config"); + ensure!(num_configs != 0, "no EGL config"); + + let egl_context = EGLContext::from_raw(egl_display, egl_config_id as *const _, egl_context) + .context("error creating EGL context")?; + let capabilities = GlesRenderer::supported_capabilities(&egl_context) + .context("error getting supported renderer capabilities")? + .into_iter() + .filter(|c| *c != Capability::ColorTransformations); + + GlesRenderer::with_capabilities(egl_context, capabilities) + .context("error creating GlesRenderer") + } + + unsafe fn with_framebuffer_save_restore<T>( + renderer: &mut GlesRenderer, + f: impl FnOnce(&mut GlesRenderer) -> T, + ) -> anyhow::Result<T> { + let mut framebuffer = 0; + renderer + .with_context(|gl| unsafe { + gl.GetIntegerv( + smithay::backend::renderer::gles::ffi::FRAMEBUFFER_BINDING, + &mut framebuffer, + ); + }) + .context("error running closure in GL context")?; + ensure!(framebuffer != 0, "error getting the framebuffer"); + + let rv = f(renderer); + + renderer.unbind().context("error unbinding")?; + renderer + .with_context(|gl| unsafe { + gl.BindFramebuffer( + smithay::backend::renderer::gles::ffi::FRAMEBUFFER, + framebuffer as u32, + ); + }) + .context("error running closure in GL context")?; + + Ok(rv) + } +} + +glib::wrapper! { + pub struct SmithayView(ObjectSubclass<imp::SmithayView>) + @extends gtk::Widget; +} + +impl SmithayView { + pub fn new<T: TestCase + 'static>( + make_test_case: impl Fn(Size<i32, Logical>) -> T + 'static, + ) -> Self { + let obj: Self = glib::Object::builder().build(); + + let make = move |size| Box::new(make_test_case(size)) as Box<dyn TestCase>; + let make_test_case = Box::new(make) as _; + let _ = obj.imp().make_test_case.set(make_test_case); + + obj + } +} diff --git a/niri-visual-tests/src/test_window.rs b/niri-visual-tests/src/test_window.rs new file mode 100644 index 00000000..2523fe60 --- /dev/null +++ b/niri-visual-tests/src/test_window.rs @@ -0,0 +1,206 @@ +use std::cell::RefCell; +use std::cmp::{max, min}; +use std::rc::Rc; + +use niri::layout::{LayoutElement, LayoutElementRenderElement}; +use niri::render_helpers::NiriRenderer; +use smithay::backend::renderer::element::solid::{SolidColorBuffer, SolidColorRenderElement}; +use smithay::backend::renderer::element::Kind; +use smithay::output::Output; +use smithay::reexports::wayland_server::protocol::wl_surface::WlSurface; +use smithay::utils::{Logical, Point, Scale, Size, Transform}; + +#[derive(Debug)] +struct TestWindowInner { + id: usize, + size: Size<i32, Logical>, + requested_size: Option<Size<i32, Logical>>, + min_size: Size<i32, Logical>, + max_size: Size<i32, Logical>, + buffer: SolidColorBuffer, + pending_fullscreen: bool, + csd_shadow_width: i32, + csd_shadow_buffer: SolidColorBuffer, +} + +#[derive(Debug, Clone)] +pub struct TestWindow(Rc<RefCell<TestWindowInner>>); + +impl TestWindow { + pub fn freeform(id: usize) -> Self { + let size = Size::from((100, 200)); + let min_size = Size::from((0, 0)); + let max_size = Size::from((0, 0)); + let buffer = SolidColorBuffer::new(size, [0.15, 0.64, 0.41, 1.]); + + Self(Rc::new(RefCell::new(TestWindowInner { + id, + size, + requested_size: None, + min_size, + max_size, + buffer, + pending_fullscreen: false, + csd_shadow_width: 0, + csd_shadow_buffer: SolidColorBuffer::new((0, 0), [0., 0., 0., 0.3]), + }))) + } + + pub fn fixed_size(id: usize) -> Self { + let rv = Self::freeform(id); + rv.set_min_size((200, 400).into()); + rv.set_max_size((200, 400).into()); + rv.set_color([0.88, 0.11, 0.14, 1.]); + rv.communicate(); + rv + } + + pub fn set_min_size(&self, size: Size<i32, Logical>) { + self.0.borrow_mut().min_size = size; + } + + pub fn set_max_size(&self, size: Size<i32, Logical>) { + self.0.borrow_mut().max_size = size; + } + + pub fn set_color(&self, color: [f32; 4]) { + self.0.borrow_mut().buffer.set_color(color); + } + + pub fn set_csd_shadow_width(&self, width: i32) { + self.0.borrow_mut().csd_shadow_width = width; + } + + pub fn communicate(&self) -> bool { + let mut rv = false; + let mut inner = self.0.borrow_mut(); + + let mut new_size = inner.size; + + if let Some(size) = inner.requested_size.take() { + assert!(size.w >= 0); + assert!(size.h >= 0); + + if size.w != 0 { + new_size.w = size.w; + } + if size.h != 0 { + new_size.h = size.h; + } + } + + if inner.max_size.w > 0 { + new_size.w = min(new_size.w, inner.max_size.w); + } + if inner.max_size.h > 0 { + new_size.h = min(new_size.h, inner.max_size.h); + } + if inner.min_size.w > 0 { + new_size.w = max(new_size.w, inner.min_size.w); + } + if inner.min_size.h > 0 { + new_size.h = max(new_size.h, inner.min_size.h); + } + + if inner.size != new_size { + inner.size = new_size; + inner.buffer.resize(new_size); + rv = true; + } + + let mut csd_shadow_size = new_size; + csd_shadow_size.w += inner.csd_shadow_width * 2; + csd_shadow_size.h += inner.csd_shadow_width * 2; + inner.csd_shadow_buffer.resize(csd_shadow_size); + + rv + } +} + +impl PartialEq for TestWindow { + fn eq(&self, other: &Self) -> bool { + self.0.borrow().id == other.0.borrow().id + } +} + +impl LayoutElement for TestWindow { + fn size(&self) -> Size<i32, Logical> { + self.0.borrow().size + } + + fn buf_loc(&self) -> Point<i32, Logical> { + (0, 0).into() + } + + fn is_in_input_region(&self, _point: Point<f64, Logical>) -> bool { + false + } + + fn render<R: NiriRenderer>( + &self, + _renderer: &mut R, + location: Point<i32, Logical>, + scale: Scale<f64>, + ) -> Vec<LayoutElementRenderElement<R>> { + let inner = self.0.borrow(); + + vec![ + SolidColorRenderElement::from_buffer( + &inner.buffer, + location.to_physical_precise_round(scale), + scale, + 1., + Kind::Unspecified, + ) + .into(), + SolidColorRenderElement::from_buffer( + &inner.csd_shadow_buffer, + (location - Point::from((inner.csd_shadow_width, inner.csd_shadow_width))) + .to_physical_precise_round(scale), + scale, + 1., + Kind::Unspecified, + ) + .into(), + ] + } + + fn request_size(&self, size: Size<i32, Logical>) { + self.0.borrow_mut().requested_size = Some(size); + self.0.borrow_mut().pending_fullscreen = false; + } + + fn request_fullscreen(&self, _size: Size<i32, Logical>) { + self.0.borrow_mut().pending_fullscreen = true; + } + + fn min_size(&self) -> Size<i32, Logical> { + self.0.borrow().min_size + } + + fn max_size(&self) -> Size<i32, Logical> { + self.0.borrow().max_size + } + + fn is_wl_surface(&self, _wl_surface: &WlSurface) -> bool { + false + } + + fn set_preferred_scale_transform(&self, _scale: i32, _transform: Transform) {} + + fn has_ssd(&self) -> bool { + false + } + + fn output_enter(&self, _output: &Output) {} + + fn output_leave(&self, _output: &Output) {} + + fn is_fullscreen(&self) -> bool { + false + } + + fn is_pending_fullscreen(&self) -> bool { + self.0.borrow().pending_fullscreen + } +} |
