git.lucas.co / cce-color-editor
color picker and palette editor
git clone https://git.lucas.co/cce-color-editor.git

commitd9d4125abf031a3ee0280d79b1c4c8f711413e55
parentaea8f1b3c1
authorLucas Galante <[email protected]>
date2026-05-25 00:04
Migrate from winit to smithay-client-toolkit and calloop

 Cargo.toml  |   7 +-
 src/main.rs | 894 ++++++++++++++++++++++++++++++++++++++++++------------------
 2 files changed, 634 insertions(+), 267 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 91d9b81..63fd9e9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,7 +5,12 @@ edition = "2021"
 
 [dependencies]
 clear-ui = { path = "../clear-ui" }
-winit = "0.30"
+smithay-client-toolkit = "0.19.2"
+calloop = "0.13.0"
+calloop-wayland-source = "0.3.0"
+wayland-client = { version = "0.31", features = ["system"] }
+xkeysym = "0.2"
+raw-window-handle = "0.6"
 wgpu = "24"
 bytemuck = { version = "1", features = ["derive"] }
 pollster = "0.4"
diff --git a/src/main.rs b/src/main.rs
index 0f46b0c..0c1707d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,16 +1,37 @@
 use std::sync::Arc;
-
-use winit::application::ApplicationHandler;
-use winit::event::{ElementState, MouseButton, WindowEvent};
-use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
-use winit::window::{Window, WindowAttributes};
-
-use clear_ui::color;
-use winit::platform::wayland::WindowAttributesExtWayland;
 use glyphon::{
     Attrs, Buffer, Cache, FontSystem, Metrics, Resolution, SwashCache, TextArea, TextAtlas,
     TextBounds, TextRenderer, Viewport,
 };
+use clear_ui::color;
+
+use smithay_client_toolkit::{
+    compositor::{CompositorHandler, CompositorState},
+    delegate_compositor, delegate_keyboard, delegate_pointer, delegate_registry,
+    delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, delegate_output,
+    registry::{ProvidesRegistryState, RegistryState},
+    output::{OutputHandler, OutputState},
+    seat::{
+        keyboard::KeyboardHandler,
+        pointer::PointerHandler,
+        Capability, SeatHandler, SeatState,
+    },
+    shell::{
+        xdg::{
+            window::{Window as XdgWindow, WindowConfigure, WindowHandler, WindowDecorations},
+            XdgShell,
+        },
+        WaylandSurface,
+    },
+    shm::{Shm, ShmHandler},
+};
+use wayland_client::{
+    globals::registry_queue_init,
+    protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface},
+    Connection, QueueHandle, Proxy,
+};
+use calloop::EventLoop;
+use calloop_wayland_source::WaylandSource;
 
 #[repr(C)]
 #[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
@@ -182,8 +203,9 @@ struct HitButton {
 }
 
 struct ColorApp {
-    window: Arc<Window>,
-    surface: wgpu::Surface<'static>,
+    window: XdgWindow,
+    surface: wl_surface::WlSurface,
+    wgpu_surface: wgpu::Surface<'static>,
     device: wgpu::Device,
     queue: wgpu::Queue,
     config: wgpu::SurfaceConfiguration,
@@ -221,19 +243,35 @@ struct ColorApp {
 }
 
 impl ColorApp {
-    async fn new(window: Arc<Window>, red: f32, green: f32, blue: f32) -> Self {
-        let size = window.inner_size();
-        let sw = size.width as f32;
-        let sh = size.height as f32;
+    async fn new(
+        conn: &Connection,
+        qh: &QueueHandle<AppState>,
+        compositor_state: &CompositorState,
+        xdg_shell_state: &XdgShell,
+        red: f32,
+        green: f32,
+        blue: f32,
+    ) -> Self {
+        let surface = compositor_state.create_surface(qh);
+        let window = xdg_shell_state.create_window(surface.clone(), WindowDecorations::None, qh);
+        window.set_title("Clear Color Interface");
+        window.set_app_id("clear-color-interface");
+        window.set_min_size(Some((WIN_W as u32, WIN_H as u32)));
+        window.commit();
+
+        let wayland_handle = Box::leak(Box::new(clear_ui::wayland::WaylandSurfaceHandle {
+            display_ptr: conn.backend().display_id().as_ptr() as *mut std::ffi::c_void,
+            surface_ptr: surface.id().as_ptr() as *mut std::ffi::c_void,
+        }));
 
         let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
             backends: wgpu::Backends::VULKAN,
             ..Default::default()
         });
-        let surface = instance.create_surface(window.clone()).expect("surface");
+        let wgpu_surface = instance.create_surface(wayland_handle).expect("surface");
         let adapter = instance.request_adapter(&wgpu::RequestAdapterOptions {
             power_preference: wgpu::PowerPreference::HighPerformance,
-            compatible_surface: Some(&surface),
+            compatible_surface: Some(&wgpu_surface),
             force_fallback_adapter: false,
         }).await.expect("adapter");
         let (device, queue) = adapter.request_device(&wgpu::DeviceDescriptor {
@@ -242,8 +280,15 @@ impl ColorApp {
             required_limits: wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits()),
             memory_hints: wgpu::MemoryHints::MemoryUsage,
         }, None).await.expect("device");
-        let config = surface.get_default_config(&adapter, size.width.max(1), size.height.max(1)).expect("config");
-        surface.configure(&device, &config);
+
+        let scale_factor = 2.0;
+        let width = (WIN_W * scale_factor as f32) as u32;
+        let height = (WIN_H * scale_factor as f32) as u32;
+
+        let mut config = wgpu_surface.get_default_config(&adapter, width, height).expect("config");
+        config.width = width;
+        config.height = height;
+        wgpu_surface.configure(&device, &config);
 
         let shader_code = clear_ui::SHADER;
         let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
@@ -295,7 +340,7 @@ impl ColorApp {
         let mut text_atlas = TextAtlas::new(&device, &queue, &cache, config.format);
         let text_renderer = TextRenderer::new(&mut text_atlas, &device, wgpu::MultisampleState::default(), None);
         let mut text_viewport = Viewport::new(&device, &cache);
-        text_viewport.update(&queue, Resolution { width: size.width, height: size.height });
+        text_viewport.update(&queue, Resolution { width, height });
 
         let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
             label: Some("Vertex Buffer"),
@@ -304,11 +349,9 @@ impl ColorApp {
             mapped_at_creation: false,
         });
 
-        let scale_factor = (window.scale_factor() as f32).max(2.0) as f64;
-
         let (hue, saturation, lightness) = rgb_to_hsl(red, green, blue);
         let mut app = Self {
-            window, surface, device, queue, config, render_pipeline,
+            window, surface, wgpu_surface, device, queue, config, render_pipeline,
             vertex_buffer, vertex_count: 0,
             red, green, blue,
             hue, saturation, lightness,
@@ -321,10 +364,10 @@ impl ColorApp {
             dragging: None,
             action_requested: None,
             scale_factor,
-            width: size.width, height: size.height,
+            width, height,
             needs_rebuild: true,
         };
-        app.rebuild_layout(sw, sh);
+        app.rebuild_layout(width as f32, height as f32);
         app
     }
 
@@ -578,33 +621,180 @@ impl ColorApp {
         text_renderer.prepare(device, queue, font_system, text_atlas, text_viewport, areas, swash_cache).unwrap();
     }
 
-    fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
-        if size.width > 0 && size.height > 0 {
-            self.width = size.width; self.height = size.height;
-            self.config.width = size.width; self.config.height = size.height;
-            self.surface.configure(&self.device, &self.config);
-            self.needs_rebuild = true;
+    fn handle_cursor_moved(&mut self, cx: f32, cy: f32) {
+        self.cursor_x = cx;
+        self.cursor_y = cy;
+        if let Some(drag) = self.dragging {
+            let i = match drag {
+                DragTarget::Red => 0,
+                DragTarget::Green => 1,
+                DragTarget::Blue => 2,
+                DragTarget::Hue => 3,
+                DragTarget::Saturation => 4,
+                DragTarget::Lightness => 5,
+            };
+            let s = self.scale_factor as f32;
+            let (tx, _, tw, _) = Self::slider_physical_rect(i, s);
+            let new_val = ((self.cursor_x - tx) / tw).clamp(0.0, 1.0);
+            let old = match i {
+                0 => self.red,
+                1 => self.green,
+                2 => self.blue,
+                3 => self.hue,
+                4 => self.saturation,
+                _ => self.lightness,
+            };
+            if (new_val - old).abs() > 0.002 {
+                match i {
+                    0 => {
+                        self.red = new_val;
+                        let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
+                        self.saturation = sat;
+                        self.lightness = l;
+                        if sat > 0.001 && l > 0.001 && l < 0.999 {
+                            self.hue = h;
+                        }
+                    }
+                    1 => {
+                        self.green = new_val;
+                        let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
+                        self.saturation = sat;
+                        self.lightness = l;
+                        if sat > 0.001 && l > 0.001 && l < 0.999 {
+                            self.hue = h;
+                        }
+                    }
+                    2 => {
+                        self.blue = new_val;
+                        let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
+                        self.saturation = sat;
+                        self.lightness = l;
+                        if sat > 0.001 && l > 0.001 && l < 0.999 {
+                            self.hue = h;
+                        }
+                    }
+                    3 => {
+                        self.hue = new_val;
+                        let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
+                        self.red = r;
+                        self.green = g;
+                        self.blue = b;
+                    }
+                    4 => {
+                        self.saturation = new_val;
+                        let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
+                        self.red = r;
+                        self.green = g;
+                        self.blue = b;
+                    }
+                    _ => {
+                        self.lightness = new_val;
+                        let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
+                        self.red = r;
+                        self.green = g;
+                        self.blue = b;
+                    }
+                }
+                self.needs_rebuild = true;
+            }
         }
     }
 
-    fn handle_event(&mut self, event: &WindowEvent) -> bool {
-        match event {
-            WindowEvent::CursorMoved { position, .. } => {
-                self.cursor_x = position.x as f32;
-                self.cursor_y = position.y as f32;
-                if let Some(drag) = self.dragging {
-                    let i = match drag {
-                        DragTarget::Red => 0,
-                        DragTarget::Green => 1,
-                        DragTarget::Blue => 2,
-                        DragTarget::Hue => 3,
-                        DragTarget::Saturation => 4,
-                        DragTarget::Lightness => 5,
-                    };
-                    let s = self.scale_factor as f32;
-                    let (tx, _, tw, _) = Self::slider_physical_rect(i, s);
-                    let new_val = ((self.cursor_x - tx) / tw).clamp(0.0, 1.0);
-                    let old = match i {
+    fn handle_mouse_input(&mut self, state: clear_ui::widget::ElementState) {
+        match state {
+            clear_ui::widget::ElementState::Pressed => {
+                let s = self.scale_factor as f32;
+                let (px, py) = (self.cursor_x, self.cursor_y);
+                for i in 0..6 {
+                    let (tx, ty, tw, th) = Self::slider_physical_rect(i, s);
+                    if px >= tx && px <= tx + tw && py >= ty && py <= ty + th {
+                        let val = ((px - tx) / tw).clamp(0.0, 1.0);
+                        match i {
+                            0 => {
+                                self.red = val;
+                                let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
+                                self.saturation = sat;
+                                self.lightness = l;
+                                if sat > 0.001 && l > 0.001 && l < 0.999 {
+                                    self.hue = h;
+                                }
+                                self.dragging = Some(DragTarget::Red);
+                            }
+                            1 => {
+                                self.green = val;
+                                let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
+                                self.saturation = sat;
+                                self.lightness = l;
+                                if sat > 0.001 && l > 0.001 && l < 0.999 {
+                                    self.hue = h;
+                                }
+                                self.dragging = Some(DragTarget::Green);
+                            }
+                            2 => {
+                                self.blue = val;
+                                let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
+                                self.saturation = sat;
+                                self.lightness = l;
+                                if sat > 0.001 && l > 0.001 && l < 0.999 {
+                                    self.hue = h;
+                                }
+                                self.dragging = Some(DragTarget::Blue);
+                            }
+                            3 => {
+                                self.hue = val;
+                                let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
+                                self.red = r;
+                                self.green = g;
+                                self.blue = b;
+                                self.dragging = Some(DragTarget::Hue);
+                            }
+                            4 => {
+                                self.saturation = val;
+                                let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
+                                self.red = r;
+                                self.green = g;
+                                self.blue = b;
+                                self.dragging = Some(DragTarget::Saturation);
+                            }
+                            _ => {
+                                self.lightness = val;
+                                let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
+                                self.red = r;
+                                self.green = g;
+                                self.blue = b;
+                                self.dragging = Some(DragTarget::Lightness);
+                            }
+                        }
+                        self.needs_rebuild = true;
+                        return;
+                    }
+                }
+                for btn in &self.action_buttons {
+                    if px >= btn.x && px <= btn.x + btn.w && py >= btn.y && py <= btn.y + btn.h {
+                        self.action_requested = Some(btn.action);
+                        self.needs_rebuild = true;
+                        return;
+                    }
+                }
+            }
+            clear_ui::widget::ElementState::Released => {
+                if self.dragging.is_some() {
+                    self.dragging = None;
+                }
+            }
+        }
+    }
+
+    fn handle_scroll(&mut self, _scroll_amount_x: f32, scroll_amount_y: f32) {
+        let s = self.scale_factor as f32;
+        let (px, py) = (self.cursor_x, self.cursor_y);
+        let scroll_amount = scroll_amount_y;
+        if scroll_amount.abs() > 0.0001 {
+            for i in 0..6 {
+                let (tx, ty, tw, th) = Self::slider_physical_rect(i, s);
+                if px >= tx && px <= tx + tw && py >= ty - 4.0 * s && py <= ty + th + 4.0 * s {
+                    let step = 0.02;
+                    let old_val = match i {
                         0 => self.red,
                         1 => self.green,
                         2 => self.blue,
@@ -612,7 +802,8 @@ impl ColorApp {
                         4 => self.saturation,
                         _ => self.lightness,
                     };
-                    if (new_val - old).abs() > 0.002 {
+                    let new_val = (old_val + scroll_amount * step).clamp(0.0, 1.0);
+                    if (new_val - old_val).abs() > 0.0001 {
                         match i {
                             0 => {
                                 self.red = new_val;
@@ -666,176 +857,18 @@ impl ColorApp {
                         self.needs_rebuild = true;
                     }
                 }
-                false
-            }
-            WindowEvent::MouseWheel { delta, .. } => {
-                let s = self.scale_factor as f32;
-                let (px, py) = (self.cursor_x, self.cursor_y);
-                let scroll_amount = match delta {
-                    winit::event::MouseScrollDelta::LineDelta(_x, y) => *y,
-                    winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.y as f32) / 60.0,
-                };
-                if scroll_amount.abs() > 0.0001 {
-                    for i in 0..6 {
-                        let (tx, ty, tw, th) = Self::slider_physical_rect(i, s);
-                        if px >= tx && px <= tx + tw && py >= ty - 4.0 * s && py <= ty + th + 4.0 * s {
-                            let step = 0.02;
-                            let old_val = match i {
-                                0 => self.red,
-                                1 => self.green,
-                                2 => self.blue,
-                                3 => self.hue,
-                                4 => self.saturation,
-                                _ => self.lightness,
-                            };
-                            let new_val = (old_val + scroll_amount * step).clamp(0.0, 1.0);
-                            if (new_val - old_val).abs() > 0.0001 {
-                                match i {
-                                    0 => {
-                                        self.red = new_val;
-                                        let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
-                                        self.saturation = sat;
-                                        self.lightness = l;
-                                        if sat > 0.001 && l > 0.001 && l < 0.999 {
-                                            self.hue = h;
-                                        }
-                                    }
-                                    1 => {
-                                        self.green = new_val;
-                                        let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
-                                        self.saturation = sat;
-                                        self.lightness = l;
-                                        if sat > 0.001 && l > 0.001 && l < 0.999 {
-                                            self.hue = h;
-                                        }
-                                    }
-                                    2 => {
-                                        self.blue = new_val;
-                                        let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
-                                        self.saturation = sat;
-                                        self.lightness = l;
-                                        if sat > 0.001 && l > 0.001 && l < 0.999 {
-                                            self.hue = h;
-                                        }
-                                    }
-                                    3 => {
-                                        self.hue = new_val;
-                                        let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
-                                        self.red = r;
-                                        self.green = g;
-                                        self.blue = b;
-                                    }
-                                    4 => {
-                                        self.saturation = new_val;
-                                        let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
-                                        self.red = r;
-                                        self.green = g;
-                                        self.blue = b;
-                                    }
-                                    _ => {
-                                        self.lightness = new_val;
-                                        let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
-                                        self.red = r;
-                                        self.green = g;
-                                        self.blue = b;
-                                    }
-                                }
-                                self.needs_rebuild = true;
-                                return true;
-                            }
-                        }
-                    }
-                }
-                false
             }
-            WindowEvent::MouseInput { state, button, .. } => {
-                if *button != MouseButton::Left { return false; }
-                match state {
-                    ElementState::Pressed => {
-                        let s = self.scale_factor as f32;
-                        let (px, py) = (self.cursor_x, self.cursor_y);
-                        for i in 0..6 {
-                            let (tx, ty, tw, th) = Self::slider_physical_rect(i, s);
-                            if px >= tx && px <= tx + tw && py >= ty && py <= ty + th {
-                                let val = ((px - tx) / tw).clamp(0.0, 1.0);
-                                match i {
-                                    0 => {
-                                        self.red = val;
-                                        let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
-                                        self.saturation = sat;
-                                        self.lightness = l;
-                                        if sat > 0.001 && l > 0.001 && l < 0.999 {
-                                            self.hue = h;
-                                        }
-                                        self.dragging = Some(DragTarget::Red);
-                                    }
-                                    1 => {
-                                        self.green = val;
-                                        let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
-                                        self.saturation = sat;
-                                        self.lightness = l;
-                                        if sat > 0.001 && l > 0.001 && l < 0.999 {
-                                            self.hue = h;
-                                        }
-                                        self.dragging = Some(DragTarget::Green);
-                                    }
-                                    2 => {
-                                        self.blue = val;
-                                        let (h, sat, l) = rgb_to_hsl(self.red, self.green, self.blue);
-                                        self.saturation = sat;
-                                        self.lightness = l;
-                                        if sat > 0.001 && l > 0.001 && l < 0.999 {
-                                            self.hue = h;
-                                        }
-                                        self.dragging = Some(DragTarget::Blue);
-                                    }
-                                    3 => {
-                                        self.hue = val;
-                                        let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
-                                        self.red = r;
-                                        self.green = g;
-                                        self.blue = b;
-                                        self.dragging = Some(DragTarget::Hue);
-                                    }
-                                    4 => {
-                                        self.saturation = val;
-                                        let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
-                                        self.red = r;
-                                        self.green = g;
-                                        self.blue = b;
-                                        self.dragging = Some(DragTarget::Saturation);
-                                    }
-                                    _ => {
-                                        self.lightness = val;
-                                        let (r, g, b) = hsl_to_rgb(self.hue, self.saturation, self.lightness);
-                                        self.red = r;
-                                        self.green = g;
-                                        self.blue = b;
-                                        self.dragging = Some(DragTarget::Lightness);
-                                    }
-                                }
-                                self.needs_rebuild = true;
-                                return true;
-                            }
-                        }
-                        for btn in &self.action_buttons {
-                            if px >= btn.x && px <= btn.x + btn.w && py >= btn.y && py <= btn.y + btn.h {
-                                self.action_requested = Some(btn.action);
-                                self.needs_rebuild = true;
-                                return true;
-                            }
-                        }
-                    }
-                    ElementState::Released => {
-                        if self.dragging.is_some() {
-                            self.dragging = None;
-                            return true;
-                        }
-                    }
-                }
-                false
-            }
-            _ => false,
+        }
+    }
+
+    fn resize(&mut self, width: u32, height: u32) {
+        if width > 0 && height > 0 {
+            self.width = width;
+            self.height = height;
+            self.config.width = width;
+            self.config.height = height;
+            self.wgpu_surface.configure(&self.device, &self.config);
+            self.needs_rebuild = true;
         }
     }
 
@@ -850,10 +883,10 @@ impl ColorApp {
 
         self.prepare_text();
 
-        let output = match self.surface.get_current_texture() {
+        let output = match self.wgpu_surface.get_current_texture() {
             Ok(t) => t,
             Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
-                self.surface.configure(&self.device, &self.config);
+                self.wgpu_surface.configure(&self.device, &self.config);
                 return;
             }
             Err(wgpu::SurfaceError::Timeout) => return,
@@ -889,65 +922,333 @@ impl ColorApp {
         }
 
         self.queue.submit(std::iter::once(encoder.finish()));
-        self.window.pre_present_notify();
         output.present();
     }
 }
 
-struct AppWrapper {
+struct AppState {
+    registry_state: RegistryState,
+    compositor_state: CompositorState,
+    xdg_shell_state: XdgShell,
+    shm_state: Shm,
+    seat_state: SeatState,
+    output_state: OutputState,
+
+    seats: Vec<wl_seat::WlSeat>,
+    pointer: Option<wl_pointer::WlPointer>,
+    keyboard: Option<wl_keyboard::WlKeyboard>,
+
+    window: XdgWindow,
+    surface: wl_surface::WlSurface,
+
     state: Option<ColorApp>,
-    initial_red: f32,
-    initial_green: f32,
-    initial_blue: f32,
+    exit: bool,
+    redraw: bool,
+}
+
+impl CompositorHandler for AppState {
+    fn scale_factor_changed(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        scale_factor: i32,
+    ) {
+        if let Some(state) = &mut self.state {
+            state.scale_factor = (scale_factor as f32).max(2.0) as f64;
+            state.resize((WIN_W * state.scale_factor as f32) as u32, (WIN_H * state.scale_factor as f32) as u32);
+            self.redraw = true;
+        }
+    }
+
+    fn transform_changed(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _new_transform: wl_output::Transform,
+    ) {
+    }
+
+    fn frame(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _time: u32,
+    ) {
+    }
+
+    fn surface_enter(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _output: &wl_output::WlOutput,
+    ) {
+    }
+
+    fn surface_leave(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _surface: &wl_surface::WlSurface,
+        _output: &wl_output::WlOutput,
+    ) {
+    }
 }
 
-impl AppWrapper {
-    fn new(red: f32, green: f32, blue: f32) -> Self {
-        Self { state: None, initial_red: red, initial_green: green, initial_blue: blue }
+impl OutputHandler for AppState {
+    fn output_state(&mut self) -> &mut OutputState {
+        &mut self.output_state
+    }
+
+    fn new_output(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _output: wl_output::WlOutput,
+    ) {
+    }
+
+    fn update_output(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _output: wl_output::WlOutput,
+    ) {
+    }
+
+    fn output_destroyed(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _output: wl_output::WlOutput,
+    ) {
     }
 }
 
-impl ApplicationHandler for AppWrapper {
-    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
-        if self.state.is_some() { return; }
-        let size = winit::dpi::LogicalSize::new(WIN_W, WIN_H);
-        let window = Arc::new(event_loop.create_window(
-            WindowAttributes::default()
-                .with_name("clear-color-interface", "clear-color-interface")
-                .with_title("Clear Color Interface")
-                .with_inner_size(size)
-                .with_min_inner_size(size),
-        ).unwrap());
-        let state = pollster::block_on(ColorApp::new(
-            window, self.initial_red, self.initial_green, self.initial_blue,
-        ));
-        self.state = Some(state);
-        self.state.as_ref().unwrap().window.request_redraw();
-    }
-
-    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: winit::window::WindowId, event: WindowEvent) {
-        let redraw = match &event {
-            WindowEvent::CloseRequested => { event_loop.exit(); true }
-            WindowEvent::Resized(s) => { if let Some(st) = &mut self.state { st.resize(*s); } true }
-            WindowEvent::RedrawRequested => {
-                if let Some(st) = &mut self.state { st.render(); st.window.request_redraw(); }
-                true
-            }
-            _ => self.state.as_mut().map(|st| st.handle_event(&event)).unwrap_or(false)
-        };
-        if redraw { if let Some(st) = &mut self.state { st.window.request_redraw(); } }
-        if let Some(st) = &self.state {
-            if let Some(action) = st.action_requested {
-                match action {
-                    Action::Apply => { println!("{}", st.hex()); }
-                    Action::Cancel => {}
+impl SeatHandler for AppState {
+    fn seat_state(&mut self) -> &mut SeatState {
+        &mut self.seat_state
+    }
+
+    fn new_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
+        self.seats.push(seat);
+    }
+
+    fn new_capability(
+        &mut self,
+        _conn: &Connection,
+        qh: &QueueHandle<Self>,
+        seat: wl_seat::WlSeat,
+        capability: Capability,
+    ) {
+        if capability == Capability::Pointer && self.pointer.is_none() {
+            let pointer = self.seat_state.get_pointer(qh, &seat).unwrap();
+            self.pointer = Some(pointer);
+        }
+        if capability == Capability::Keyboard && self.keyboard.is_none() {
+            let keyboard = self
+                .seat_state
+                .get_keyboard(qh, &seat, None)
+                .unwrap();
+            self.keyboard = Some(keyboard);
+        }
+    }
+
+    fn remove_capability(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _seat: wl_seat::WlSeat,
+        capability: Capability,
+    ) {
+        if capability == Capability::Pointer {
+            self.pointer = None;
+        }
+        if capability == Capability::Keyboard {
+            self.keyboard = None;
+        }
+    }
+
+    fn remove_seat(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, seat: wl_seat::WlSeat) {
+        self.seats.retain(|s| s != &seat);
+    }
+}
+
+impl ShmHandler for AppState {
+    fn shm_state(&mut self) -> &mut Shm {
+        &mut self.shm_state
+    }
+}
+
+impl PointerHandler for AppState {
+    fn pointer_frame(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _pointer: &wl_pointer::WlPointer,
+        events: &[smithay_client_toolkit::seat::pointer::PointerEvent],
+    ) {
+        use smithay_client_toolkit::seat::pointer::PointerEventKind;
+        for event in events {
+            let (x, y) = event.position;
+            match &event.kind {
+                PointerEventKind::Motion { .. } => {
+                    if let Some(st) = &mut self.state {
+                        let cx = (x * st.scale_factor) as f32;
+                        let cy = (y * st.scale_factor) as f32;
+                        st.handle_cursor_moved(cx, cy);
+                        self.redraw = true;
+                    }
                 }
-                event_loop.exit();
+                PointerEventKind::Press { button, .. } => {
+                    if *button == 272 {
+                        if let Some(st) = &mut self.state {
+                            let cx = (x * st.scale_factor) as f32;
+                            let cy = (y * st.scale_factor) as f32;
+                            st.cursor_x = cx;
+                            st.cursor_y = cy;
+                            st.handle_mouse_input(clear_ui::widget::ElementState::Pressed);
+                            self.redraw = true;
+                        }
+                    }
+                }
+                PointerEventKind::Release { button, .. } => {
+                    if *button == 272 {
+                        if let Some(st) = &mut self.state {
+                            let cx = (x * st.scale_factor) as f32;
+                            let cy = (y * st.scale_factor) as f32;
+                            st.cursor_x = cx;
+                            st.cursor_y = cy;
+                            st.handle_mouse_input(clear_ui::widget::ElementState::Released);
+                            self.redraw = true;
+                        }
+                    }
+                }
+                PointerEventKind::Axis { horizontal, vertical, .. } => {
+                    if let Some(st) = &mut self.state {
+                        let h_scroll = horizontal.absolute as f32;
+                        let v_scroll = vertical.absolute as f32;
+                        st.handle_scroll(-h_scroll / 10.0, -v_scroll / 10.0);
+                        self.redraw = true;
+                    }
+                }
+                _ => {}
+            }
+        }
+    }
+}
+
+impl KeyboardHandler for AppState {
+    fn enter(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _surface: &wl_surface::WlSurface,
+        _serial: u32,
+        _raw_modifiers: &[u32],
+        _keysyms: &[xkeysym::Keysym],
+    ) {}
+
+    fn leave(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _surface: &wl_surface::WlSurface,
+        _serial: u32,
+    ) {}
+
+    fn press_key(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _serial: u32,
+        _event: smithay_client_toolkit::seat::keyboard::KeyEvent,
+    ) {}
+
+    fn release_key(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _serial: u32,
+        _event: smithay_client_toolkit::seat::keyboard::KeyEvent,
+    ) {}
+
+    fn update_modifiers(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _keyboard: &wl_keyboard::WlKeyboard,
+        _serial: u32,
+        _modifiers: smithay_client_toolkit::seat::keyboard::Modifiers,
+        _layout: u32,
+    ) {}
+}
+
+impl WindowHandler for AppState {
+    fn configure(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _window: &XdgWindow,
+        configure: WindowConfigure,
+        _serial: u32,
+    ) {
+        let (w, h) = configure.new_size;
+        if let (Some(w), Some(h)) = (w, h) {
+            let width = w.get();
+            let height = h.get();
+            if let Some(state) = &mut self.state {
+                state.resize(width, height);
             }
         }
+        self.redraw = true;
+    }
+
+    fn request_close(&mut self, _conn: &Connection, _qh: &QueueHandle<Self>, _window: &XdgWindow) {
+        self.exit = true;
     }
 }
 
+impl ProvidesRegistryState for AppState {
+    fn registry(&mut self) -> &mut RegistryState {
+        &mut self.registry_state
+    }
+    
+    fn runtime_add_global(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _name: u32,
+        _interface: &str,
+        _version: u32,
+    ) {}
+    
+    fn runtime_remove_global(
+        &mut self,
+        _conn: &Connection,
+        _qh: &QueueHandle<Self>,
+        _name: u32,
+        _interface: &str,
+    ) {}
+}
+
+delegate_compositor!(AppState);
+delegate_xdg_shell!(AppState);
+delegate_xdg_window!(AppState);
+delegate_shm!(AppState);
+delegate_seat!(AppState);
+delegate_pointer!(AppState);
+delegate_keyboard!(AppState);
+delegate_registry!(AppState);
+delegate_output!(AppState);
+
 fn main() {
     let args: Vec<String> = std::env::args().collect();
     let (r, g, b) = if args.len() > 1 {
@@ -956,7 +1257,68 @@ fn main() {
         (0.5, 0.5, 0.5)
     };
 
-    let event_loop = EventLoop::new().unwrap();
-    event_loop.set_control_flow(ControlFlow::Poll);
-    event_loop.run_app(&mut AppWrapper::new(r, g, b)).unwrap();
+    let conn = Connection::connect_to_env().unwrap();
+    let (globals, mut event_queue) = registry_queue_init(&conn).unwrap();
+    let qh = event_queue.handle();
+
+    let compositor_state = CompositorState::bind(&globals, &qh).unwrap();
+    let xdg_shell_state = XdgShell::bind(&globals, &qh).unwrap();
+    let shm_state = Shm::bind(&globals, &qh).unwrap();
+    let seat_state = SeatState::new(&globals, &qh);
+    let output_state = OutputState::new(&globals, &qh);
+
+    let state = pollster::block_on(ColorApp::new(
+        &conn,
+        &qh,
+        &compositor_state,
+        &xdg_shell_state,
+        r, g, b
+    ));
+
+    let mut app = AppState {
+        registry_state: RegistryState::new(&globals),
+        compositor_state,
+        xdg_shell_state,
+        shm_state,
+        seat_state,
+        output_state,
+        seats: Vec::new(),
+        pointer: None,
+        keyboard: None,
+        window: state.window.clone(),
+        surface: state.surface.clone(),
+        state: Some(state),
+        exit: false,
+        redraw: true,
+    };
+
+    let mut event_loop = EventLoop::try_new().unwrap();
+    let loop_handle = event_loop.handle();
+    WaylandSource::new(conn, event_queue).insert(loop_handle).unwrap();
+
+    loop {
+        event_loop
+            .dispatch(std::time::Duration::from_millis(16), &mut app)
+            .unwrap();
+        if app.exit {
+            break;
+        }
+        if let Some(ref state) = app.state {
+            if let Some(action) = state.action_requested {
+                match action {
+                    Action::Apply => {
+                        println!("{}", state.hex());
+                    }
+                    Action::Cancel => {}
+                }
+                break;
+            }
+        }
+        if app.redraw {
+            app.redraw = false;
+            if let Some(state) = &mut app.state {
+                state.render();
+            }
+        }
+    }
 }