Files
squeekboard/src/outputs.rs
2020-03-12 11:26:49 +00:00

479 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Copyright (C) 2019-2020 Purism SPC
* SPDX-License-Identifier: GPL-3.0+
*/
/*! Managing Wayland outputs */
use std::cell::RefCell;
use std::vec::Vec;
use ::logging;
// traits
use ::logging::Warn;
/// Gathers stuff defined in C or called by C
pub mod c {
use super::*;
use std::os::raw::{ c_char, c_void };
use ::util::c::COpaquePtr;
// Defined in C
#[repr(transparent)]
#[derive(Clone, PartialEq, Copy, Hash)]
pub struct WlOutput(*const c_void);
#[repr(C)]
struct WlOutputListener<T: COpaquePtr> {
geometry: extern fn(
T, // data
WlOutput,
i32, // x
i32, // y
i32, // physical_width
i32, // physical_height
i32, // subpixel
*const c_char, // make
*const c_char, // model
i32, // transform
),
mode: extern fn(
T, // data
WlOutput,
u32, // flags
i32, // width
i32, // height
i32, // refresh
),
done: extern fn(
T, // data
WlOutput,
),
scale: extern fn(
T, // data
WlOutput,
i32, // factor
),
}
bitflags!{
/// Map to `wl_output.mode` values
pub struct Mode: u32 {
const NONE = 0x0;
const CURRENT = 0x1;
const PREFERRED = 0x2;
}
}
/// Map to `wl_output.transform` values
#[derive(Clone, PartialEq)]
pub enum Transform {
Normal = 0,
Rotated90 = 1,
Rotated180 = 2,
Rotated270 = 3,
Flipped = 4,
FlippedRotated90 = 5,
FlippedRotated180 = 6,
FlippedRotated270 = 7,
}
impl Transform {
fn from_u32(v: u32) -> Option<Transform> {
use self::Transform::*;
match v {
0 => Some(Normal),
1 => Some(Rotated90),
2 => Some(Rotated180),
3 => Some(Rotated270),
4 => Some(Flipped),
5 => Some(FlippedRotated90),
6 => Some(FlippedRotated180),
7 => Some(FlippedRotated270),
_ => None,
}
}
}
extern "C" {
// Rustc wrongly assumes
// that COutputs allows C direct access to the underlying RefCell
#[allow(improper_ctypes)]
fn squeek_output_add_listener(
wl_output: WlOutput,
listener: *const WlOutputListener<COutputs>,
data: COutputs,
) -> i32;
}
pub type COutputs = ::util::c::Wrapped<Outputs>;
/// A stable reference to an output.
#[derive(Clone)]
#[repr(C)]
pub struct OutputHandle {
wl_output: WlOutput,
outputs: COutputs,
}
impl OutputHandle {
// Cannot return an Output reference
// because COutputs is too deeply wrapped
pub fn get_state(&self) -> Option<OutputState> {
let outputs = self.outputs.clone_ref();
let outputs = outputs.borrow();
find_output(&outputs, self.wl_output.clone()).map(|o| o.current.clone())
}
pub fn get_id(&self) -> OutputId {
OutputId { wl_output: self.wl_output }
}
}
// Defined in Rust
extern fn outputs_handle_geometry(
outputs: COutputs,
wl_output: WlOutput,
_x: i32, _y: i32,
phys_width: i32, phys_height: i32,
_subpixel: i32,
_make: *const c_char, _model: *const c_char,
transform: i32,
) {
let transform = Transform::from_u32(transform as u32)
.or_print(
logging::Problem::Warning,
"Received invalid wl_output.transform value",
).unwrap_or(Transform::Normal);
let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut();
let output_state: Option<&mut OutputState>
= find_output_mut(&mut collection, wl_output)
.map(|o| &mut o.pending);
match output_state {
Some(state) => {
state.transform = Some(transform);
state.phys_size = {
if (phys_width > 0) & (phys_height > 0) {
Some(SizeMM { width: phys_width, height: phys_height })
} else {
log_print!(
logging::Level::Surprise,
"Impossible physical dimensions: {}mm × {}mm",
phys_width, phys_height,
);
None
}
}
},
None => log_print!(
logging::Level::Warning,
"Got geometry on unknown output",
),
};
}
extern fn outputs_handle_mode(
outputs: COutputs,
wl_output: WlOutput,
flags: u32,
width: i32,
height: i32,
_refresh: i32,
) {
let flags = Mode::from_bits(flags)
.or_print(
logging::Problem::Warning,
"Received invalid wl_output.mode flags",
).unwrap_or(Mode::NONE);
let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut();
let output_state: Option<&mut OutputState>
= find_output_mut(&mut collection, wl_output)
.map(|o| &mut o.pending);
match output_state {
Some(state) => {
if flags.contains(Mode::CURRENT) {
state.current_mode = Some(super::Mode { width, height});
}
},
None => log_print!(
logging::Level::Warning,
"Got mode on unknown output",
),
};
}
extern fn outputs_handle_done(
outputs_raw: COutputs,
wl_output: WlOutput,
) {
let outputs = outputs_raw.clone_ref();
{
let mut collection = RefCell::borrow_mut(&outputs);
let output = find_output_mut(&mut collection, wl_output);
match output {
Some(output) => { output.current = output.pending.clone(); }
None => log_print!(
logging::Level::Warning,
"Got done on unknown output",
),
};
}
let collection = RefCell::borrow(&outputs);
if let Some(ref cb) = &collection.update_cb {
let mut cb = RefCell::borrow_mut(cb);
let cb = Box::as_mut(&mut cb);
cb(OutputHandle { wl_output, outputs: outputs_raw });
}
}
extern fn outputs_handle_scale(
outputs: COutputs,
wl_output: WlOutput,
factor: i32,
) {
let outputs = outputs.clone_ref();
let mut collection = outputs.borrow_mut();
let output_state: Option<&mut OutputState>
= find_output_mut(&mut collection, wl_output)
.map(|o| &mut o.pending);
match output_state {
Some(state) => { state.scale = factor; }
None => log_print!(
logging::Level::Warning,
"Got scale on unknown output",
),
};
}
#[no_mangle]
pub extern "C"
fn squeek_outputs_new() -> COutputs {
COutputs::new(Outputs {
outputs: Vec::new(),
update_cb: None,
})
}
#[no_mangle]
pub extern "C"
fn squeek_outputs_free(outputs: COutputs) {
unsafe { outputs.unwrap() }; // gets dropped
}
#[no_mangle]
pub extern "C"
fn squeek_outputs_register(raw_collection: COutputs, output: WlOutput) {
let collection = raw_collection.clone_ref();
let mut collection = collection.borrow_mut();
collection.outputs.push(Output {
output: output.clone(),
pending: OutputState::uninitialized(),
current: OutputState::uninitialized(),
});
unsafe { squeek_output_add_listener(
output,
&WlOutputListener {
geometry: outputs_handle_geometry,
mode: outputs_handle_mode,
done: outputs_handle_done,
scale: outputs_handle_scale,
} as *const WlOutputListener<COutputs>,
raw_collection,
)};
}
#[no_mangle]
pub extern "C"
fn squeek_outputs_get_current(raw_collection: COutputs) -> OutputHandle {
let collection = raw_collection.clone_ref();
let collection = collection.borrow();
OutputHandle {
wl_output: collection.outputs[0].output.clone(),
outputs: raw_collection.clone(),
}
}
// TODO: handle unregistration
fn find_output(
collection: &Outputs,
wl_output: WlOutput,
) -> Option<&Output> {
collection.outputs
.iter()
.find_map(|o|
if o.output == wl_output { Some(o) } else { None }
)
}
fn find_output_mut(
collection: &mut Outputs,
wl_output: WlOutput,
) -> Option<&mut Output> {
collection.outputs
.iter_mut()
.find_map(|o|
if o.output == wl_output { Some(o) } else { None }
)
}
}
/// Generic size
#[derive(Clone, Debug)]
pub struct Size {
pub width: u32,
pub height: u32,
}
#[derive(Clone, PartialEq)]
pub struct SizeMM {
pub width: i32,
pub height: i32,
}
/// wl_output mode
#[derive(Clone, PartialEq)]
struct Mode {
width: i32,
height: i32,
}
#[derive(Clone, PartialEq)]
pub struct OutputState {
current_mode: Option<Mode>,
phys_size: Option<SizeMM>,
transform: Option<c::Transform>,
pub scale: i32,
}
impl OutputState {
// More properly, this would have been a builder kind of struct,
// with wl_output gradually adding properties to it
// before it reached a fully initialized state,
// when it would transform into a struct without all (some?) of the Options.
// However, it's not clear which state is fully initialized,
// and whether it would make things easier at all anyway.
fn uninitialized() -> OutputState {
OutputState {
current_mode: None,
phys_size: None,
transform: None,
scale: 1,
}
}
pub fn get_pixel_size(&self) -> Option<Size> {
use self::c::Transform;
match self {
OutputState {
current_mode: Some(Mode { width, height } ),
transform: Some(transform),
phys_size: _,
scale: _,
} => Some(
match transform {
Transform::Normal
| Transform::Rotated180
| Transform::Flipped
| Transform::FlippedRotated180 => Size {
width: *width as u32,
height: *height as u32,
},
_ => Size {
width: *height as u32,
height: *width as u32,
},
}
),
_ => None,
}
}
/// Returns transformed dimensions
pub fn get_phys_size(&self) -> Option<Size> {
use self::c::Transform;
match self {
OutputState {
current_mode: _,
transform: Some(transform),
phys_size: Some(SizeMM { width, height }),
scale: _,
} => Some(
match transform {
Transform::Normal
| Transform::Rotated180
| Transform::Flipped
| Transform::FlippedRotated180 => Size {
width: *width as u32,
height: *height as u32,
},
_ => Size {
width: *height as u32,
height: *width as u32,
},
}
),
_ => None,
}
}
}
/// A comparable ID of an output
#[derive(Clone, PartialEq, Hash)]
pub struct OutputId {
// WlOutput is a unique pointer, so it will not repeat
// even if there are multiple output managers.
wl_output: c::WlOutput,
}
pub struct Output {
output: c::WlOutput,
pending: OutputState,
current: OutputState,
}
/// The manager of all outputs.
// This is the target of several callbacks,
// so it should only be used with a stable place in memory, like `Rc<RefCell>`.
// It should not be instantiated externally or copied,
// or it will not receive those callbacks and be somewhat of an empty shell.
// It should be safe to use as long as the fields are not `pub`,
// and there's no `Clone`, and this module's API only ever gives out
// references wrapped in `Rc<RefCell>`.
// For perfectness, it would only ever give out immutable opaque references,
// but that's not practical at the moment.
// `mem::swap` could replace the value inside,
// but as long as the swap is atomic,
// that should not cause an inconsistent state.
pub struct Outputs {
outputs: Vec<Output>,
// The RefCell is here to let the function be called
// while holding only a read-reference to `Outputs`.
// Otherwise anything trying to get useful data from OutputHandle
// will fail to acquire reference to Outputs.
// TODO: Maybe pass only current state along with Outputs and Output hash.
// The only reason a full OutputHandle is here
// is to be able to track the right Output.
update_cb: Option<RefCell<Box<dyn FnMut(c::OutputHandle)>>>,
}
impl Outputs {
/// The function will get called whenever
/// any output changes or is removed or created.
/// If output handle doesn't return state, the output just went down.
/// It cannot modify anything in Outputs.
// FIXME: handle output destruction
pub fn set_update_cb(&mut self, callback: Box<dyn FnMut(c::OutputHandle)>) {
self.update_cb = Some(RefCell::new(callback));
}
}