atagen

monolith

public
Star0
Watch0
f00961b08216f587ac2ef74fe48fe69c9b901fde
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git
crates/monolith/src/drm.rsRust716 lines23.2 KB
1
//! Bare metal. A libseat session, udev for the primary GPU and its
2
//! connectors, libinput for input, and one smithay DRM output per connector,
3
//! each rendered when its own vblank lands.
4
5
use crate::scene::Draw;
6
use crate::state::{State, now_ns};
7
use monolith_layout::{OutputId, Rect};
8
use smithay::backend::allocator::Fourcc;
9
use smithay::backend::allocator::gbm::{GbmAllocator, GbmBufferFlags, GbmDevice};
10
use smithay::backend::drm::compositor::FrameFlags;
11
use smithay::backend::drm::exporter::gbm::GbmFramebufferExporter;
12
use smithay::backend::drm::output::{DrmOutput, DrmOutputManager, DrmOutputRenderElements};
13
use smithay::backend::drm::{
14
    DrmDevice, DrmDeviceFd, DrmEvent, DrmEventMetadata, DrmEventTime, DrmNode, NodeType,
15
};
16
use smithay::backend::input::{
17
    AbsolutePositionEvent, Axis, AxisSource, Event, InputEvent, KeyboardKeyEvent, PointerAxisEvent,
18
    PointerButtonEvent, PointerMotionEvent,
19
};
20
use smithay::backend::libinput::{LibinputInputBackend, LibinputSessionInterface};
21
use smithay::backend::session::libseat::LibSeatSession;
22
use smithay::backend::session::{Event as SessionEvent, Session};
23
use smithay::backend::udev::{UdevBackend, UdevEvent, all_gpus, primary_gpu};
24
use smithay::input::pointer::AxisFrame;
25
use smithay::output::{Mode, Output, PhysicalProperties, Scale, Subpixel};
26
use smithay::reexports::calloop::timer::{TimeoutAction, Timer};
27
use smithay::reexports::calloop::{EventLoop, LoopHandle, RegistrationToken};
28
use smithay::reexports::drm::Device as _;
29
use smithay::reexports::drm::control::{ModeTypeFlags, connector, crtc};
30
use smithay::reexports::input::Libinput;
31
use smithay::reexports::rustix::fs::OFlags;
32
use smithay::utils::{DeviceFd, Point, Transform};
33
use smithay::wayland::drm_syncobj::{DrmSyncobjState, supports_syncobj_eventfd};
34
use smithay_drm_extras::drm_scanner::{DrmScanEvent, DrmScanner};
35
use std::collections::{HashMap, VecDeque};
36
use std::path::{Path, PathBuf};
37
use std::time::Duration;
38
39
const CLEAR: [f32; 4] = [0.07, 0.075, 0.09, 1.0];
40
41
type Allocator = GbmAllocator<DrmDeviceFd>;
42
type Exporter = GbmFramebufferExporter<DrmDeviceFd>;
43
type Manager = DrmOutputManager<Allocator, Exporter, (), DrmDeviceFd>;
44
type Out = DrmOutput<Allocator, Exporter, (), DrmDeviceFd>;
45
46
pub struct Drm {
47
    pub session: LibSeatSession,
48
    libinput: Libinput,
49
    handle: LoopHandle<'static, State>,
50
    node: DrmNode,
51
    manager: Manager,
52
    scanner: DrmScanner,
53
    nvidia: bool,
54
    frame_flags: FrameFlags,
55
    surfaces: HashMap<crtc::Handle, Surface>,
56
}
57
58
struct Surface {
59
    output: Output,
60
    id: OutputId,
61
    drm: Out,
62
    /// A frame is on its way to the screen and its vblank has not landed.
63
    queued: bool,
64
    redraw: bool,
65
    last_vblank_ns: Option<u64>,
66
    refresh_ns: u64,
67
    /// The pending render, timed to start just in time for `target_ns`.
68
    scheduled: Option<RegistrationToken>,
69
    /// The vblank the frame being drawn or queued is meant for.
70
    target_ns: Option<u64>,
71
    budget: Budget,
72
    /// Frames presented since `stats_since`, for the once-a-second debug line.
73
    presented: u32,
74
    stats_since: u64,
75
}
76
77
impl Surface {
78
    /// The first vblank a render starting at `now` can still make, and the
79
    /// latest moment to start it.
80
    fn next_deadline(&self, now: u64) -> Option<(u64, u64)> {
81
        let last = self.last_vblank_ns?;
82
        let refresh = self.refresh_ns;
83
        if refresh == 0 {
84
            return None;
85
        }
86
        let budget = self.budget.ns().min(refresh);
87
        let periods = (now + budget).saturating_sub(last).div_ceil(refresh).max(1);
88
        let vblank = last + periods * refresh;
89
        Some((vblank, vblank - budget))
90
    }
91
}
92
93
/// How long before a vblank rendering must start. A high percentile of
94
/// recent CPU render times covers the CPU side without one spike inflating
95
/// it, and a slack that grows on every missed vblank and shrinks over seconds
96
/// while frames land covers GPU time and the commit.
97
struct Budget {
98
    renders: VecDeque<u64>,
99
    slack_ns: u64,
100
    /// The slack that last proved enough after a miss. Decay stops there
101
    /// for `floor_frames` frames, so it does not keep rediscovering the same
102
    /// limit one dropped frame at a time.
103
    floor_ns: u64,
104
    floor_frames: u64,
105
}
106
107
impl Budget {
108
    const KEEP: usize = 32;
109
    const MIN_SLACK_NS: u64 = 1_000_000;
110
    const STEP_NS: u64 = 500_000;
111
    const DECAY_NS: u64 = 1_000;
112
    const FLOOR_NS: u64 = 30_000_000_000;
113
114
    fn new() -> Self {
115
        Self {
116
            renders: VecDeque::with_capacity(Self::KEEP),
117
            slack_ns: 2_000_000,
118
            floor_ns: 0,
119
            floor_frames: 0,
120
        }
121
    }
122
123
    fn ns(&self) -> u64 {
124
        let mut renders: Vec<u64> = self.renders.iter().copied().collect();
125
        renders.sort_unstable();
126
        let p90 = renders
127
            .get(renders.len().saturating_sub(1) * 9 / 10)
128
            .copied()
129
            .unwrap_or(0);
130
        p90 + self.slack_ns
131
    }
132
133
    fn record(&mut self, render_ns: u64) {
134
        if self.renders.len() == Self::KEEP {
135
            self.renders.pop_front();
136
        }
137
        self.renders.push_back(render_ns);
138
    }
139
140
    fn missed(&mut self, refresh_ns: u64) {
141
        self.slack_ns = (self.slack_ns + Self::STEP_NS).min(refresh_ns / 2);
142
        self.floor_ns = self.slack_ns;
143
        self.floor_frames = Self::FLOOR_NS / refresh_ns.max(1);
144
    }
145
146
    fn landed(&mut self) {
147
        self.floor_frames = self.floor_frames.saturating_sub(1);
148
        let floor = if self.floor_frames > 0 {
149
            self.floor_ns
150
        } else {
151
            Self::MIN_SLACK_NS
152
        };
153
        self.slack_ns = self.slack_ns.saturating_sub(Self::DECAY_NS).max(floor);
154
    }
155
}
156
157
/// The GPU monolith drives on bare metal, the seat's primary or else its
158
/// first.
159
pub fn primary() -> Result<PathBuf, Box<dyn std::error::Error>> {
160
    let seat = std::env::var("XDG_SEAT").unwrap_or_else(|_| "seat0".into());
161
    Ok(match primary_gpu(&seat)? {
162
        Some(p) => p,
163
        None => all_gpus(&seat)?.into_iter().next().ok_or("no GPU found")?,
164
    })
165
}
166
167
pub fn init(
168
    event_loop: &mut EventLoop<'static, State>,
169
    state: &mut State,
170
    path: &Path,
171
) -> Result<(), Box<dyn std::error::Error>> {
172
    let (mut session, notifier) = LibSeatSession::new()?;
173
    let seat = session.seat();
174
    let node = DrmNode::from_path(path)?;
175
    tracing::info!(path = %path.display(), "primary GPU");
176
    let fd = session.open(
177
        path,
178
        OFlags::RDWR | OFlags::CLOEXEC | OFlags::NOCTTY | OFlags::NONBLOCK,
179
    )?;
180
    let fd = DrmDeviceFd::new(DeviceFd::from(fd));
181
    let (device, drm_events) = DrmDevice::new(fd.clone(), true)?;
182
    let nvidia = device
183
        .get_driver()
184
        .map(|d| d.name().to_string_lossy().to_lowercase().contains("nvidia"))
185
        .unwrap_or(false);
186
    if supports_syncobj_eventfd(&fd) {
187
        state.syncobj = Some(DrmSyncobjState::new::<State>(&state.display, fd.clone()));
188
    } else {
189
        tracing::info!("no syncobj eventfd support, clients stay on implicit sync");
190
    }
191
    let gbm = GbmDevice::new(fd)?;
192
    let render_node = node
193
        .node_with_type(NodeType::Render)
194
        .and_then(Result::ok)
195
        .unwrap_or(node);
196
    let allocator = GbmAllocator::new(
197
        gbm.clone(),
198
        GbmBufferFlags::RENDERING | GbmBufferFlags::SCANOUT,
199
    );
200
    let exporter = GbmFramebufferExporter::new(gbm.clone(), render_node.into());
201
    let render_formats: Vec<_> = state.renderer.render_formats().iter().copied().collect();
202
    let manager = DrmOutputManager::new(
203
        device,
204
        allocator,
205
        exporter,
206
        Some(gbm),
207
        [Fourcc::Xrgb8888, Fourcc::Argb8888],
208
        render_formats,
209
    );
210
211
    let mut libinput =
212
        Libinput::new_with_udev::<LibinputSessionInterface<LibSeatSession>>(session.clone().into());
213
    libinput
214
        .udev_assign_seat(&seat)
215
        .map_err(|()| "libinput could not take the seat")?;
216
    let handle = event_loop.handle();
217
    handle.insert_source(
218
        LibinputInputBackend::new(libinput.clone()),
219
        |event, _, state| {
220
            on_input(state, event);
221
        },
222
    )?;
223
    handle.insert_source(notifier, |event, _, state| match event {
224
        SessionEvent::PauseSession => {
225
            if let Some(drm) = state.drm.as_mut() {
226
                drm.libinput.suspend();
227
                drm.manager.pause();
228
            }
229
        }
230
        SessionEvent::ActivateSession => {
231
            if let Some(drm) = state.drm.as_mut() {
232
                if drm.libinput.resume().is_err() {
233
                    tracing::warn!("libinput did not resume");
234
                }
235
                if let Err(e) = drm.manager.lock().activate(false) {
236
                    tracing::warn!("could not reactivate the GPU: {e}");
237
                }
238
                for s in drm.surfaces.values_mut() {
239
                    s.queued = false;
240
                    s.redraw = true;
241
                }
242
            }
243
            state.needs_redraw = true;
244
        }
245
    })?;
246
    handle.insert_source(drm_events, |event, meta, state| match event {
247
        DrmEvent::VBlank(crtc) => on_vblank(state, crtc, meta),
248
        DrmEvent::Error(e) => tracing::warn!("drm: {e}"),
249
    })?;
250
    let udev = UdevBackend::new(&seat)?;
251
    handle.insert_source(udev, move |event, _, state| {
252
        if let UdevEvent::Changed { device_id } = event
253
            && state
254
                .drm
255
                .as_ref()
256
                .is_some_and(|d| d.node.dev_id() == device_id)
257
        {
258
            scan(state);
259
        }
260
    })?;
261
262
    state.drm = Some(Drm {
263
        session,
264
        libinput,
265
        handle,
266
        node,
267
        manager,
268
        scanner: DrmScanner::new(),
269
        nvidia,
270
        frame_flags: if std::env::var_os("MONOLITH_NO_SCANOUT").is_some() {
271
            FrameFlags::empty()
272
        } else {
273
            FrameFlags::DEFAULT
274
        },
275
        surfaces: HashMap::new(),
276
    });
277
    scan(state);
278
    Ok(())
279
}
280
281
/// Brings outputs in line with what is plugged in.
282
fn scan(state: &mut State) {
283
    let Some(drm) = state.drm.as_mut() else {
284
        return;
285
    };
286
    let events: Vec<DrmScanEvent> = match drm.scanner.scan_connectors(drm.manager.device()) {
287
        Ok(result) => result.into_iter().collect(),
288
        Err(e) => {
289
            tracing::warn!("scanning connectors failed: {e}");
290
            return;
291
        }
292
    };
293
    for event in events {
294
        match event {
295
            DrmScanEvent::Connected {
296
                connector,
297
                crtc: Some(crtc),
298
            } => connect(state, connector, crtc),
299
            DrmScanEvent::Disconnected {
300
                crtc: Some(crtc), ..
301
            } => disconnect(state, crtc),
302
            _ => {}
303
        }
304
    }
305
    state.apply_layout();
306
}
307
308
fn connect(state: &mut State, connector: connector::Info, crtc: crtc::Handle) {
309
    let name = format!(
310
        "{}-{}",
311
        connector.interface().as_str(),
312
        connector.interface_id()
313
    );
314
    let Some(&mode) = connector
315
        .modes()
316
        .iter()
317
        .find(|m| m.mode_type().contains(ModeTypeFlags::PREFERRED))
318
        .or_else(|| connector.modes().first())
319
    else {
320
        tracing::warn!(name, "connector has no modes");
321
        return;
322
    };
323
    let State {
324
        drm: Some(drm),
325
        renderer,
326
        layout,
327
        space,
328
        display,
329
        ..
330
    } = state
331
    else {
332
        return;
333
    };
334
    let (mm_w, mm_h) = connector.size().unwrap_or((0, 0));
335
    let output = Output::new(
336
        name.clone(),
337
        PhysicalProperties {
338
            size: (mm_w as i32, mm_h as i32).into(),
339
            subpixel: Subpixel::Unknown,
340
            make: "Unknown".into(),
341
            model: name.clone(),
342
            serial_number: String::new(),
343
        },
344
    );
345
    let wl_mode = Mode::from(mode);
346
    let x = layout
347
        .outputs()
348
        .map(|(_, o)| o.area.right())
349
        .fold(0.0, f64::max);
350
    let position = Point::from((x as i32, 0));
351
    output.change_current_state(
352
        Some(wl_mode),
353
        Some(Transform::Normal),
354
        Some(Scale::Integer(1)),
355
        Some(position),
356
    );
357
    output.set_preferred(wl_mode);
358
    output.create_global::<State>(display);
359
    let mut planes = drm.manager.device().planes(&crtc).ok();
360
    if drm.nvidia
361
        && let Some(p) = planes.as_mut()
362
    {
363
        // NVIDIA's overlay planes break composition, as anvil also found.
364
        p.overlay.clear();
365
    }
366
    let out = match drm.manager.lock().initialize_output::<_, Draw>(
367
        crtc,
368
        mode,
369
        &[connector.handle()],
370
        &output,
371
        planes,
372
        renderer,
373
        &DrmOutputRenderElements::default(),
374
    ) {
375
        Ok(out) => out,
376
        Err(e) => {
377
            tracing::warn!(name, "could not light up the output: {e}");
378
            return;
379
        }
380
    };
381
    space.map_output(&output, position);
382
    let (w, h) = mode.size();
383
    let area = Rect::new(x, 0.0, f64::from(w), f64::from(h));
384
    let id = layout.add_output(&name, area);
385
    tracing::info!(name, w, h, refresh = mode.vrefresh(), "output connected");
386
    drm.surfaces.insert(
387
        crtc,
388
        Surface {
389
            output,
390
            id,
391
            drm: out,
392
            queued: false,
393
            redraw: true,
394
            last_vblank_ns: None,
395
            scheduled: None,
396
            target_ns: None,
397
            budget: Budget::new(),
398
            presented: 0,
399
            stats_since: 0,
400
            refresh_ns: 1_000_000_000_000 / u64::from(wl_mode.refresh.max(1) as u32),
401
        },
402
    );
403
}
404
405
fn disconnect(state: &mut State, crtc: crtc::Handle) {
406
    let Some(drm) = state.drm.as_mut() else {
407
        return;
408
    };
409
    let Some(surface) = drm.surfaces.remove(&crtc) else {
410
        return;
411
    };
412
    if let Some(token) = surface.scheduled {
413
        drm.handle.remove(token);
414
    }
415
    tracing::info!(name = surface.output.name(), "output disconnected");
416
    state.space.unmap_output(&surface.output);
417
    state.layout.remove_output(surface.id);
418
}
419
420
/// The smithay output for a layout output, for protocols that need one.
421
pub fn output_for(drm: &Drm, id: OutputId) -> Option<Output> {
422
    drm.surfaces
423
        .values()
424
        .find(|s| s.id == id)
425
        .map(|s| s.output.clone())
426
}
427
428
fn on_vblank(state: &mut State, crtc: crtc::Handle, meta: &mut Option<DrmEventMetadata>) {
429
    let Some(surface) = state.drm.as_mut().and_then(|d| d.surfaces.get_mut(&crtc)) else {
430
        return;
431
    };
432
    if let Err(e) = surface.drm.frame_submitted() {
433
        tracing::warn!("frame was not submitted: {e}");
434
    }
435
    surface.queued = false;
436
    if let Some(DrmEventTime::Monotonic(t)) = meta.as_ref().map(|m| m.time) {
437
        let t = t.as_nanos() as u64;
438
        if let Some(target) = surface.target_ns.take() {
439
            if t > target + surface.refresh_ns / 2 {
440
                surface.budget.missed(surface.refresh_ns);
441
                tracing::debug!(
442
                    output = surface.output.name(),
443
                    late_ns = t - target,
444
                    budget_ns = surface.budget.ns(),
445
                    "frame missed its vblank"
446
                );
447
            } else {
448
                surface.budget.landed();
449
            }
450
        }
451
        surface.last_vblank_ns = Some(t);
452
        surface.presented += 1;
453
        let window = t.saturating_sub(surface.stats_since);
454
        if window >= 1_000_000_000 {
455
            let vblanks = window / surface.refresh_ns.max(1);
456
            tracing::debug!(
457
                output = surface.output.name(),
458
                presented = surface.presented,
459
                vblanks,
460
                budget_us = surface.budget.ns() / 1000,
461
                "frame rate"
462
            );
463
            surface.presented = 0;
464
            surface.stats_since = t;
465
        }
466
    }
467
    if surface.redraw {
468
        schedule(state, crtc);
469
    }
470
}
471
472
/// Starts a render at the last moment that still makes the next reachable
473
/// vblank, so commits arriving until then make it into the frame.
474
fn schedule(state: &mut State, crtc: crtc::Handle) {
475
    let Some(drm) = state.drm.as_mut() else {
476
        return;
477
    };
478
    let Some(surface) = drm.surfaces.get_mut(&crtc) else {
479
        return;
480
    };
481
    if surface.queued || surface.scheduled.is_some() {
482
        return;
483
    }
484
    let now = now_ns();
485
    let Some((vblank, start)) = surface.next_deadline(now) else {
486
        draw(state, crtc, now);
487
        return;
488
    };
489
    surface.target_ns = Some(vblank);
490
    let wait = Timer::from_duration(Duration::from_nanos(start.saturating_sub(now)));
491
    let token = drm.handle.insert_source(wait, move |_, _, state| {
492
        let target = state.drm.as_mut().and_then(|d| {
493
            let s = d.surfaces.get_mut(&crtc)?;
494
            s.scheduled = None;
495
            s.target_ns
496
        });
497
        if let Some(target) = target {
498
            draw(state, crtc, target);
499
        }
500
        TimeoutAction::Drop
501
    });
502
    surface.scheduled = token.ok();
503
}
504
505
fn draw(state: &mut State, crtc: crtc::Handle, target: u64) {
506
    if render_surface(state, crtc, target) {
507
        schedule(state, crtc);
508
    }
509
}
510
511
/// Renders every output that has something new and no frame in flight.
512
pub fn render(state: &mut State) {
513
    let Some(drm) = state.drm.as_mut() else {
514
        return;
515
    };
516
    if std::mem::take(&mut state.needs_redraw) {
517
        for s in drm.surfaces.values_mut() {
518
            s.redraw = true;
519
        }
520
    }
521
    let ready: Vec<crtc::Handle> = drm
522
        .surfaces
523
        .iter()
524
        .filter(|(_, s)| s.redraw && !s.queued)
525
        .map(|(c, _)| *c)
526
        .collect();
527
    for crtc in ready {
528
        schedule(state, crtc);
529
    }
530
}
531
532
/// Draws one output for the vblank at `target`. Returns whether it still
533
/// animates without a frame in flight, and so needs another render.
534
#[profiling::function]
535
fn render_surface(state: &mut State, crtc: crtc::Handle, target: u64) -> bool {
536
    let State {
537
        drm: Some(drm),
538
        renderer,
539
        layout,
540
        windows,
541
        anim,
542
        borders,
543
        config,
544
        space,
545
        start,
546
        cursor,
547
        cursor_status,
548
        pointer_pos,
549
        ..
550
    } = state
551
    else {
552
        return false;
553
    };
554
    if !drm.session.is_active() {
555
        return false;
556
    }
557
    let Some(surface) = drm.surfaces.get_mut(&crtc) else {
558
        return false;
559
    };
560
    if surface.queued {
561
        return false;
562
    }
563
    let began = std::time::Instant::now();
564
    let now = target;
565
    let Some(area) = layout.output(surface.id).map(|o| o.area) else {
566
        return false;
567
    };
568
    let mut elements = Vec::new();
569
    let local = *pointer_pos - Point::from((area.x, area.y));
570
    if local.x >= 0.0 && local.y >= 0.0 && local.x < area.w && local.y < area.h {
571
        elements.extend(cursor.elements(renderer, cursor_status, local));
572
    }
573
    let scene_began = std::time::Instant::now();
574
    elements.extend(crate::scene::build(
575
        layout,
576
        surface.id,
577
        windows,
578
        anim,
579
        renderer,
580
        now,
581
        &config.decoration,
582
        borders,
583
    ));
584
    let scene_ns = scene_began.elapsed().as_nanos() as u64;
585
    let animating = anim.active(now);
586
    anim.prune(now);
587
    let render_began = std::time::Instant::now();
588
    let cpu_began = thread_cpu_ns();
589
    let mut queue_ns = 0;
590
    match surface
591
        .drm
592
        .render_frame(renderer, &elements, CLEAR, drm.frame_flags)
593
    {
594
        Ok(result) if !result.is_empty => {
595
            let queue_began = std::time::Instant::now();
596
            match surface.drm.queue_frame(()) {
597
                Ok(()) => surface.queued = true,
598
                Err(e) => tracing::warn!("queueing a frame failed: {e}"),
599
            }
600
            queue_ns = queue_began.elapsed().as_nanos() as u64;
601
        }
602
        Ok(_) => {}
603
        Err(e) => tracing::warn!("rendering failed: {e}"),
604
    }
605
    let render_cpu_ns = thread_cpu_ns() - cpu_began;
606
    let render_ns = render_began.elapsed().as_nanos() as u64 - queue_ns;
607
    let took = began.elapsed().as_nanos() as u64;
608
    surface.budget.record(took);
609
    if took > surface.refresh_ns / 2 {
610
        tracing::debug!(
611
            output = surface.output.name(),
612
            took_ns = took,
613
            scene_ns,
614
            render_ns,
615
            render_cpu_ns,
616
            queue_ns,
617
            "slow frame"
618
        );
619
    }
620
    if !surface.queued {
621
        surface.target_ns = None;
622
    }
623
    surface.redraw = animating;
624
    let elapsed = start.elapsed();
625
    let output = surface.output.clone();
626
    for window in space.elements_for_output(&output) {
627
        window.send_frame(&output, elapsed, Some(Duration::ZERO), |_, _| {
628
            Some(output.clone())
629
        });
630
    }
631
    animating && !surface.queued
632
}
633
634
/// CPU time this thread has used, to tell a render that works from one that
635
/// waits or is descheduled.
636
fn thread_cpu_ns() -> u64 {
637
    let t = smithay::reexports::rustix::time::clock_gettime(
638
        smithay::reexports::rustix::time::ClockId::ThreadCPUTime,
639
    );
640
    t.tv_sec as u64 * 1_000_000_000 + t.tv_nsec as u64
641
}
642
643
fn on_input(state: &mut State, event: InputEvent<LibinputInputBackend>) {
644
    match event {
645
        InputEvent::Keyboard { event } => {
646
            let time = event.time();
647
            state.key(event.key_code(), event.state(), time);
648
        }
649
        InputEvent::PointerMotion { event } => {
650
            let pos = clamp(state, state.pointer_pos + event.delta());
651
            state.pointer_to(pos, event.time());
652
            state.pointer_frame();
653
            state.needs_redraw = true;
654
        }
655
        InputEvent::PointerMotionAbsolute { event } => {
656
            let Some(area) = state.layout.outputs().next().map(|(_, o)| o.area) else {
657
                return;
658
            };
659
            let p = event.position_transformed((area.w as i32, area.h as i32).into());
660
            let pos = Point::from((area.x + p.x, area.y + p.y));
661
            state.pointer_to(pos, event.time());
662
            state.pointer_frame();
663
            state.needs_redraw = true;
664
        }
665
        InputEvent::PointerButton { event } => {
666
            let time = event.time();
667
            state.pointer_button(event.button_code(), event.state(), time);
668
            state.pointer_frame();
669
        }
670
        InputEvent::PointerAxis { event } => {
671
            let source = event.source();
672
            let mut frame = AxisFrame::new(event.time()).source(source);
673
            for axis in [Axis::Horizontal, Axis::Vertical] {
674
                if let Some(v120) = event.amount_v120(axis) {
675
                    frame = frame.v120(axis, v120 as i32);
676
                }
677
                let amount = event
678
                    .amount(axis)
679
                    .or_else(|| event.amount_v120(axis).map(|v| v * 15.0 / 120.0));
680
                match amount {
681
                    Some(a) if a != 0.0 => frame = frame.value(axis, a),
682
                    Some(_) if source == AxisSource::Finger => frame = frame.stop(axis),
683
                    _ => {}
684
                }
685
            }
686
            state.pointer_axis(frame);
687
            state.pointer_frame();
688
        }
689
        _ => {}
690
    }
691
}
692
693
/// Keeps the pointer on some output, sliding along the edge it hit.
694
fn clamp(
695
    state: &State,
696
    pos: Point<f64, smithay::utils::Logical>,
697
) -> Point<f64, smithay::utils::Logical> {
698
    let inside = |a: &Rect, p: Point<f64, smithay::utils::Logical>| {
699
        p.x >= a.x && p.y >= a.y && p.x < a.right() && p.y < a.bottom()
700
    };
701
    let areas: Vec<Rect> = state.layout.outputs().map(|(_, o)| o.area).collect();
702
    if areas.iter().any(|a| inside(a, pos)) {
703
        return pos;
704
    }
705
    let Some(a) = areas
706
        .iter()
707
        .find(|a| inside(a, state.pointer_pos))
708
        .or(areas.first())
709
    else {
710
        return pos;
711
    };
712
    Point::from((
713
        pos.x.clamp(a.x, a.right() - 1.0),
714
        pos.y.clamp(a.y, a.bottom() - 1.0),
715
    ))
716
}