Basic lock

This commit is contained in:
Leni Aniva 2023-09-10 20:39:49 -07:00
parent 105d70d2ca
commit d0192547df
Signed by: aniva
GPG Key ID: 4D9B1C8D10EA4C50
7 changed files with 1542 additions and 128 deletions

4
.gitignore vendored
View File

@ -1 +1,5 @@
.*
!.gitignore
*.log
/target /target

1050
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,21 @@
name = "udon" name = "udon"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
description = "A kanban-lockscreen"
[dependencies] [dependencies]
wayland-client = "0.30.2" # Logs and error reporting
log = "0.4.20"
log4rs = "1.2.0"
color-eyre = "0.6.2"
# Surface drawing
cairo = "0.0.4"
# the version here must match the one used by wayland-client
calloop = "0.10.0"
calloop-wayland-source = "0.1.0"
tempfile = "3.7.0"
wayland-client = { version = "0.30.2" }
# staging needed for ext session lock
wayland-protocols = { version = "0.30.1", features = ["staging", "client"] }

View File

@ -1,3 +1,9 @@
# Udon # Udon
A Wayland lockscreen supporting rotating display of informations A Wayland lockscreen supporting rotating display of informations
## Acknowledgements
The following code repositories were consulted to make Udon possible
- [wpaperd](https://github.com/danyspin97/wpaperd)

5
rustfmt.toml Normal file
View File

@ -0,0 +1,5 @@
edition = "2021"
brace_style = "AlwaysNextLine"
hard_tabs = true
tab_spaces = 3
imports_granularity = "Crate"

View File

@ -1,81 +1,69 @@
use wayland_client::{protocol::wl_registry, Connection, Dispatch, QueueHandle}; mod state;
// This struct represents the state of our app. This simple app does not
// need any state, by this type still supports the `Dispatch` implementations.
struct AppData;
// Implement `Dispatch<WlRegistry, ()> for out state. This provides the logic use crate::state::State;
// to be able to process events for the wl_registry interface.
// use calloop_wayland_source::WaylandSource;
// The second type parameter is the user-data of our implementation. It is a use color_eyre::eyre::{self, Context};
// mechanism that allows you to associate a value to each particular Wayland use wayland_client::{globals as WG, Connection};
// object, and allow different dispatching logic depending on the type of the
// associated value. fn setup() -> eyre::Result<()>
//
// In this example, we just use () as we don't have any value to associate. See
// the `Dispatch` documentation for more details about this.
impl Dispatch<wl_registry::WlRegistry, ()> for AppData
{ {
fn event( use log4rs::{append, config, encode::pattern::PatternEncoder};
_state: &mut Self, let stdout = append::console::ConsoleAppender::builder()
_: &wl_registry::WlRegistry, .encoder(Box::new(PatternEncoder::new(
event: wl_registry::Event, "{d(%Y-%m-%d %H:%M:%S)} | {h({l}):5.5} | [{T}] {f}:{L} — {m}{n}",
_: &(), )))
_: &Connection, .build();
_: &QueueHandle<AppData>,
) let file = append::file::FileAppender::builder()
{ .encoder(Box::new(PatternEncoder::new("{d} - {m}{n}")))
// When receiving events from the wl_registry, we are only interested in the .build("requests.log")?;
// `global` event, which signals a new available global.
// When receiving this event, we just print its characteristics in this example. let config = config::Config::builder()
if let wl_registry::Event::Global { .appender(config::Appender::builder().build("stdout", Box::new(stdout)))
name, .appender(config::Appender::builder().build("file", Box::new(file)))
interface, .build(
version, config::Root::builder()
} = event .appender("stdout")
{ .build(log::LevelFilter::Trace),
println!("[{}] {} (v{})", name, interface, version); )?;
let _handle = log4rs::init_config(config)?;
Ok(())
}
fn main() -> eyre::Result<()>
{
// Initialise on I/O modules
color_eyre::install()?;
setup().wrap_err("Could not initialize logging")?;
// Create WL connection and get the display
let connection = Connection::connect_to_env()?;
let display = connection.display();
let (globals, event_queue) = WG::registry_queue_init::<State>(&connection)?;
let q_handle = event_queue.handle();
let registry = display.get_registry(&q_handle, ());
let mut state = State::new(globals.contents().clone_list(), &registry, &q_handle)?;
// Main calloop event loop
let mut event_loop = calloop::EventLoop::<State>::try_new()?;
WaylandSource::new(event_queue)?.insert(event_loop.handle())?;
log::trace!("Main loop starting");
state.lock(&q_handle);
while let Ok(_) = event_loop.dispatch(None, &mut state) {
if state.stop {
break;
} }
} }
}
log::trace!("Teardown");
// The main function of our program
fn main() Ok(())
{
// Create a Wayland connection by connecting to the server through the
// environment-provided configuration.
let conn = Connection::connect_to_env().unwrap();
// Retrieve the WlDisplay Wayland object from the connection. This object is
// the starting point of any Wayland program, from which all other objects will
// be created.
let display = conn.display();
// Create an event queue for our event processing
let mut event_queue = conn.new_event_queue();
// An get its handle to associated new objects to it
let qh = event_queue.handle();
// Create a wl_registry object by sending the wl_display.get_registry request
// This method takes two arguments: a handle to the queue the newly created
// wl_registry will be assigned to, and the user-data that should be associated
// with this registry (here it is () as we don't need user-data).
let _registry = display.get_registry(&qh, ());
// At this point everything is ready, and we just need to wait to receive the events
// from the wl_registry, our callback will print the advertized globals.
println!("Advertized globals:");
// To actually receive the events, we invoke the `sync_roundtrip` method. This method
// is special and you will generally only invoke it during the setup of your program:
// it will block until the server has received and processed all the messages you've
// sent up to now.
//
// In our case, that means it'll block until the server has received our
// wl_display.get_registry request, and as a reaction has sent us a batch of
// wl_registry.global events.
//
// `sync_roundtrip` will then empty the internal buffer of the queue it has been invoked
// on, and thus invoke our `Dispatch` implementation that prints the list of advertized
// globals.
event_queue.roundtrip(&mut AppData).unwrap();
} }

448
src/state.rs Normal file
View File

@ -0,0 +1,448 @@
use color_eyre::eyre::{self, eyre};
use wayland_client::{globals as WG, protocol as WP, Connection, Dispatch, QueueHandle, WEnum};
use wayland_protocols::ext::session_lock::v1::client as WpSl;
pub struct State
{
pub stop: bool,
// wayland globals
pub compositor: WP::wl_compositor::WlCompositor,
pub subcompositor: WP::wl_subcompositor::WlSubcompositor,
pub shm: WP::wl_shm::WlShm,
pub seat: WP::wl_seat::WlSeat,
pub output: WP::wl_output::WlOutput,
pub ext_session_lock_manager: WpSl::ext_session_lock_manager_v1::ExtSessionLockManagerV1,
// wayland objects
pub surface: Option<WP::wl_surface::WlSurface>,
pub ext_session_lock: Option<WpSl::ext_session_lock_v1::ExtSessionLockV1>,
pub ext_session_lock_surface: Option<WpSl::ext_session_lock_surface_v1::ExtSessionLockSurfaceV1>,
buffer: Option<WP::wl_buffer::WlBuffer>,
//cairo_surface: Option<cairo::ImageSurface>,
}
impl State
{
pub fn new(
globals: Vec<WG::Global>,
registry: &WP::wl_registry::WlRegistry,
q_handle: &wayland_client::QueueHandle<State>,
) -> eyre::Result<State, eyre::Report>
{
let globals: std::collections::BTreeMap<_, _> = globals
.into_iter()
.map(|global| (global.interface, global.name))
.collect();
let compositor_name = globals
.get("wl_compositor")
.ok_or_else(|| eyre!("compositor global not found"))?;
let subcompositor_name = globals
.get("wl_subcompositor")
.ok_or_else(|| eyre!("subcompositor global not found"))?;
let shm_name = globals
.get("wl_shm")
.ok_or_else(|| eyre!("shm global not found"))?;
let seat_name = globals
.get("wl_seat")
.ok_or_else(|| eyre!("seat global not found"))?;
let output_name = globals
.get("wl_output")
.ok_or_else(|| eyre!("output global not found"))?;
let eslm_name = globals
.get("ext_session_lock_manager_v1")
.ok_or_else(|| eyre!("session lock global not found"))?;
Ok(State {
stop: false,
compositor: registry.bind(*compositor_name, 4, q_handle, ()),
subcompositor: registry.bind(*subcompositor_name, 1, q_handle, ()),
shm: registry.bind(*shm_name, 1, q_handle, ()),
seat: registry.bind(*seat_name, 4, q_handle, ()),
output: registry.bind(*output_name, 4, q_handle, ()),
ext_session_lock_manager: registry.bind(*eslm_name, 1, q_handle, ()),
surface: None,
ext_session_lock: None,
ext_session_lock_surface: None,
buffer: None,
})
}
pub fn lock(&mut self, q_handle: &wayland_client::QueueHandle<State>)
{
self.ext_session_lock = Some(self.ext_session_lock_manager.lock(&q_handle, ()));
/*
let surface = self.compositor.create_surface(&q_handle, ());
self.ext_session_lock_surface =
Some(registry.get_lock_surface(&surface, &self.output, &q_handle, ()));
self.surface = Some(surface);
*/
}
pub fn unlock(&mut self)
{
match &self.ext_session_lock {
Some(lock) => {
lock.unlock_and_destroy();
self.stop = true;
}
None => log::warn!("Attempting to unlock lock that does not exist"),
}
}
}
// This hook is required to read globals
impl Dispatch<WP::wl_registry::WlRegistry, WG::GlobalListContents> for State
{
fn event(
_state: &mut Self,
_proxy: &WP::wl_registry::WlRegistry,
_event: WP::wl_registry::Event,
_data: &WG::GlobalListContents,
_conn: &Connection,
_q_handle: &QueueHandle<State>,
)
{
}
}
impl Dispatch<WP::wl_registry::WlRegistry, ()> for State
{
fn event(
_state: &mut Self,
_proxy: &WP::wl_registry::WlRegistry,
_event: WP::wl_registry::Event,
_data: &(),
_conn: &Connection,
_q_handle: &QueueHandle<State>,
)
{
}
}
/*
impl Dispatch<WP::wl_registry::WlRegistry, ()> for State
{
fn event(
state: &mut Self,
proxy: &WP::wl_registry::WlRegistry,
event: WP::wl_registry::Event,
_data: &(),
_conn: &Connection,
q_handle: &QueueHandle<State>,
)
{
if let WP::wl_registry::Event::Global {
name,
interface,
version,
} = event
{
match interface.as_str() {
"wl_compositor" => {
let v = 4; // Version override
state.compositor = Some(proxy.bind(name, v, q_handle, ()));
log::trace!("Compositor [{}] {} (v{})", name, interface, version);
}
"wl_subcompositor" => {
let v = 1; // Version override
state.subcompositor = Some(proxy.bind(name, v, q_handle, ()));
log::trace!("Subcompositor [{}] {} (v{})", name, interface, version);
}
"wl_shm" => {
let v = 1; // Version override
state.shm = Some(proxy.bind(name, v, q_handle, ()));
log::trace!("Shared Memory [{}] {} (v{})", name, interface, version);
}
"wl_seat" => {
let v = 4; // Version override
state.seat = Some(proxy.bind(name, v, q_handle, ()));
log::trace!("Seat [{}] {} (v{})", name, interface, version);
}
"wl_output" => {
let v = 4; // Version override
state.output = Some(proxy.bind(name, v, q_handle, ()));
log::trace!("Output [{}] {} (v{})", name, interface, version);
}
"ext_session_lock_manager_v1" => {
let v = 1;
state.ext_session_lock_manager = Some(proxy.bind(name, v, q_handle, ()));
log::trace!(
"Ext Session Lock Manager [{}] {} (v{})",
name, interface, version
);
}
_ => log::trace!("Other Global [{}] {} (v{})", name, interface, version),
}
}
}
}
*/
impl Dispatch<WP::wl_compositor::WlCompositor, ()> for State
{
fn event(
_state: &mut Self,
_proxy: &WP::wl_compositor::WlCompositor,
_event: WP::wl_compositor::Event,
_data: &(),
_conn: &Connection,
_q_handle: &QueueHandle<State>,
)
{
log::trace!("Event from compositor!")
}
}
impl Dispatch<WP::wl_subcompositor::WlSubcompositor, ()> for State
{
fn event(
_state: &mut Self,
_proxy: &WP::wl_subcompositor::WlSubcompositor,
_event: WP::wl_subcompositor::Event,
_data: &(),
_conn: &Connection,
_q_handle: &QueueHandle<State>,
)
{
log::trace!("Event from subcompositor!")
}
}
impl Dispatch<WP::wl_shm::WlShm, ()> for State
{
fn event(
_state: &mut Self,
_proxy: &WP::wl_shm::WlShm,
_event: WP::wl_shm::Event,
_data: &(),
_conn: &Connection,
_q_handle: &QueueHandle<State>,
)
{
log::trace!("Event from shm!")
}
}
impl Dispatch<WP::wl_output::WlOutput, ()> for State
{
fn event(
_state: &mut Self,
_proxy: &WP::wl_output::WlOutput,
_event: WP::wl_output::Event,
_data: &(),
_conn: &Connection,
_q_handle: &QueueHandle<State>,
)
{
log::trace!("Event from output!")
}
}
impl Dispatch<WpSl::ext_session_lock_manager_v1::ExtSessionLockManagerV1, ()> for State
{
fn event(
_state: &mut Self,
_proxy: &WpSl::ext_session_lock_manager_v1::ExtSessionLockManagerV1,
_event: WpSl::ext_session_lock_manager_v1::Event,
_data: &(),
_conn: &Connection,
_q_handle: &QueueHandle<State>,
)
{
unreachable!("Session lock manager should not emit any event");
//state.lock(&q_handle);
}
}
impl Dispatch<WP::wl_surface::WlSurface, ()> for State
{
fn event(
_state: &mut Self,
_proxy: &WP::wl_surface::WlSurface,
_event: WP::wl_surface::Event,
_data: &(),
_conn: &Connection,
_q_handle: &QueueHandle<State>,
)
{
log::trace!("Event from surface!")
}
}
impl Dispatch<WpSl::ext_session_lock_v1::ExtSessionLockV1, ()> for State
{
fn event(
state: &mut Self,
proxy: &WpSl::ext_session_lock_v1::ExtSessionLockV1,
event: WpSl::ext_session_lock_v1::Event,
_data: &(),
_conn: &Connection,
q_handle: &QueueHandle<State>,
)
{
match event {
WpSl::ext_session_lock_v1::Event::Locked => {
if state.stop {
return;
}
let surface = state.compositor.create_surface(&q_handle, ());
state.ext_session_lock_surface =
Some(proxy.get_lock_surface(&surface, &state.output, &q_handle, ()));
state.surface = Some(surface);
log::info!("Locked.");
}
WpSl::ext_session_lock_v1::Event::Finished => {
state.ext_session_lock_surface = None;
log::trace!("Could not lock surface!");
}
_ => log::trace!("Unknown event"),
}
}
}
fn draw(tmp: &mut std::fs::File, (buf_x, buf_y): (u32, u32))
{
use std::{cmp::min, io::Write};
let mut buf = std::io::BufWriter::new(tmp);
for y in 0..buf_y {
for x in 0..buf_x {
let a = 0xFF;
let r = min(((buf_x - x) * 0xFF) / buf_x, ((buf_y - y) * 0xFF) / buf_y);
let g = min((x * 0xFF) / buf_x, ((buf_y - y) * 0xFF) / buf_y);
let b = min(((buf_x - x) * 0xFF) / buf_x, (y * 0xFF) / buf_y);
let color = (a << 24) + (r << 16) + (g << 8) + b;
buf.write_all(&color.to_ne_bytes()).unwrap();
}
}
buf.flush().unwrap();
}
impl Dispatch<WpSl::ext_session_lock_surface_v1::ExtSessionLockSurfaceV1, ()> for State
{
fn event(
state: &mut Self,
proxy: &WpSl::ext_session_lock_surface_v1::ExtSessionLockSurfaceV1,
event: WpSl::ext_session_lock_surface_v1::Event,
_data: &(),
_conn: &Connection,
q_handle: &QueueHandle<State>,
)
{
match event {
WpSl::ext_session_lock_surface_v1::Event::Configure {
serial,
width,
height,
} => {
use std::os::unix::prelude::AsRawFd;
log::info!(
"Lock surface configuration event: ({}), {} {}",
serial,
width,
height
);
// Ack should go before any buffer commitment.
proxy.ack_configure(serial);
let mut file = tempfile::tempfile().unwrap();
draw(&mut file, (width, height));
let pool =
state
.shm
.create_pool(file.as_raw_fd(), (width * height * 4) as i32, q_handle, ());
let buffer = pool.create_buffer(
0,
width as i32,
height as i32,
(width * 4) as i32,
WP::wl_shm::Format::Argb8888,
q_handle,
(),
);
let surface = state.surface.as_ref().unwrap();
surface.attach(Some(&buffer), 0, 0);
surface.commit();
state.buffer = Some(buffer);
}
_ => log::trace!("Unknown event"),
}
}
}
impl Dispatch<WP::wl_seat::WlSeat, ()> for State
{
fn event(
state: &mut Self,
proxy: &WP::wl_seat::WlSeat,
event: WP::wl_seat::Event,
_data: &(),
_conn: &Connection,
q_handle: &QueueHandle<State>,
)
{
log::trace!("Event from seat!");
if let WP::wl_seat::Event::Capabilities {
capabilities: WEnum::Value(capabilities),
} = event
{
if capabilities.contains(WP::wl_seat::Capability::Keyboard) {
proxy.get_keyboard(q_handle, ());
} else {
log::error!("Seat capability does not contain keyboard");
state.stop = true;
}
}
}
}
impl Dispatch<WP::wl_keyboard::WlKeyboard, ()> for State
{
fn event(
state: &mut Self,
_proxy: &WP::wl_keyboard::WlKeyboard,
event: WP::wl_keyboard::Event,
_data: &(),
_conn: &Connection,
_q_handle: &QueueHandle<State>,
)
{
if let WP::wl_keyboard::Event::Key { key, .. } = event {
if key == 1 {
// ESC key
log::trace!("Event from keyboard! Unlocking.");
state.stop = true;
state.unlock();
}
}
}
}
impl Dispatch<WP::wl_shm_pool::WlShmPool, ()> for State
{
fn event(
_state: &mut Self,
_proxy: &WP::wl_shm_pool::WlShmPool,
_event: WP::wl_shm_pool::Event,
_data: &(),
_conn: &Connection,
_q_handle: &QueueHandle<State>,
)
{
log::trace!("Event from shm pool!")
}
}
impl Dispatch<WP::wl_buffer::WlBuffer, ()> for State
{
fn event(
_state: &mut Self,
_proxy: &WP::wl_buffer::WlBuffer,
_event: WP::wl_buffer::Event,
_data: &(),
_conn: &Connection,
_q_handle: &QueueHandle<State>,
)
{
log::trace!("Event from shm pool!")
}
}