Files
another-boids-in-rust/vendor/x11rb/examples/xclock_utc.rs

283 lines
8.1 KiB
Rust

use std::num::NonZeroUsize;
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use polling::{Event as PollingEvent, Poller};
use x11rb::atom_manager;
use x11rb::connection::Connection;
use x11rb::errors::{ConnectionError, ReplyOrIdError};
use x11rb::protocol::xproto::*;
use x11rb::protocol::Event;
use x11rb::rust_connection::RustConnection;
use x11rb::wrapper::ConnectionExt as _;
atom_manager! {
pub Atoms: AtomsCookie {
UTF8_STRING,
WM_DELETE_WINDOW,
WM_PROTOCOLS,
_NET_WM_NAME,
}
}
fn create_window(
conn: &impl Connection,
screen: &Screen,
atoms: &Atoms,
(width, height): (u16, u16),
) -> Result<Window, ReplyOrIdError> {
let win_id = conn.generate_id()?;
let win_aux =
CreateWindowAux::new().event_mask(EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY);
conn.create_window(
screen.root_depth,
win_id,
screen.root,
0,
0,
width,
height,
0,
WindowClass::INPUT_OUTPUT,
0,
&win_aux,
)?;
let title = "xclock";
conn.change_property8(
PropMode::REPLACE,
win_id,
AtomEnum::WM_NAME,
AtomEnum::STRING,
title.as_bytes(),
)?;
conn.change_property8(
PropMode::REPLACE,
win_id,
atoms._NET_WM_NAME,
atoms.UTF8_STRING,
title.as_bytes(),
)?;
conn.change_property32(
PropMode::REPLACE,
win_id,
atoms.WM_PROTOCOLS,
AtomEnum::ATOM,
&[atoms.WM_DELETE_WINDOW],
)?;
conn.map_window(win_id)?;
Ok(win_id)
}
fn redraw(
conn: &impl Connection,
screen: &Screen,
win_id: Window,
gc_id: Gcontext,
(width, height): (u16, u16),
) -> Result<(), ConnectionError> {
let (hour, minute, second) = get_time();
let center = ((width as f32) / 2.0, (height as f32) / 2.0);
let size = (width.min(height) as f32) / 2.0;
// Transform a value between 0 and 60 to a position on the clock (relative to the center)
let minute_to_outer_position = |minute: f32| {
let angle = (30.0 - minute) * 2.0 * std::f32::consts::PI / 60.0;
let (sin, cos) = angle.sin_cos();
(size * sin, size * cos)
};
// Create a line segment
fn create_line(center: (f32, f32), from: (f32, f32), to: (f32, f32)) -> Segment {
Segment {
x1: (center.0 + from.0).round() as _,
y1: (center.1 + from.1).round() as _,
x2: (center.0 + to.0).round() as _,
y2: (center.1 + to.1).round() as _,
}
}
// Draw the background
conn.change_gc(gc_id, &ChangeGCAux::new().foreground(screen.white_pixel))?;
conn.poly_fill_rectangle(
win_id,
gc_id,
&[Rectangle {
x: 0,
y: 0,
width,
height,
}],
)?;
conn.change_gc(gc_id, &ChangeGCAux::new().foreground(screen.black_pixel))?;
// Get a list of lines for the clock's face
let mut lines = (0..60)
.map(|minute| {
let outer = minute_to_outer_position(minute as _);
let length_factor = if minute % 5 == 0 { 0.8 } else { 0.9 };
create_line(
center,
outer,
(outer.0 * length_factor, outer.1 * length_factor),
)
})
.collect::<Vec<_>>();
// ... and also the hand for seconds
lines.push(create_line(
center,
(0.0, 0.0),
minute_to_outer_position(second as _),
));
// Draw everything
conn.poly_segment(win_id, gc_id, &lines)?;
// Now draw the hands
let point = |pos: (f32, f32), factor: f32| Point {
x: (center.0 + pos.0 * factor).round() as _,
y: (center.1 + pos.1 * factor).round() as _,
};
let hour_to_60 = (hour % 12) as f32 * 60.0 / 12.0;
for &(position, hand_length, hand_width) in
&[(hour_to_60, 0.6, 0.08), (minute as f32, 0.8, 0.05)]
{
let outer = minute_to_outer_position(position);
let ortho1 = (outer.1, -outer.0);
let ortho2 = (-outer.1, outer.0);
let opposite = (-outer.0, -outer.1);
let polygon = [
point(ortho1, hand_width),
point(opposite, hand_width),
point(ortho2, hand_width),
point(outer, hand_length),
];
conn.fill_poly(
win_id,
gc_id,
PolyShape::COMPLEX,
CoordMode::ORIGIN,
&polygon,
)?;
}
Ok(())
}
fn poll_with_timeout(
poller: &Poller,
conn: &RustConnection,
timeout: Duration,
) -> Result<(), Box<dyn std::error::Error>> {
// Add interest in the connection's stream.
unsafe {
// SAFETY: The guard bellow guarantees that the source will be removed
// from the poller.
poller.add(conn.stream(), PollingEvent::readable(1))?;
}
// Remove it if we time out.
let _guard = CallOnDrop(|| {
poller.delete(conn.stream()).ok();
});
// Wait for events.
let mut event = polling::Events::with_capacity(NonZeroUsize::new(1).unwrap());
let target = Instant::now() + timeout;
loop {
let remaining = target.saturating_duration_since(Instant::now());
poller.wait(&mut event, Some(remaining))?;
// If we received an event, we're done.
if event.iter().any(|event| event.readable) {
return Ok(());
}
// If our timeout expired, we're done.
if Instant::now() >= target {
// We do not really care about the result of poll. Either there was a timeout, in which case we
// try to handle events (there are none) and then redraw. Or there was an event, in which case
// we handle it and then still redraw.
return Ok(());
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, screen_num) = RustConnection::connect(None)?;
// The following is only needed for start_timeout_thread(), which is used for 'tests'
let conn1 = std::sync::Arc::new(conn);
let conn = &*conn1;
let poller = Poller::new()?;
let screen = &conn.setup().roots[screen_num];
let atoms = Atoms::new(conn)?.reply()?;
let (mut width, mut height) = (100, 100);
let win_id = create_window(conn, screen, &atoms, (width, height))?;
let gc_id = conn.generate_id().unwrap();
conn.create_gc(gc_id, win_id, &CreateGCAux::default())?;
util::start_timeout_thread(conn1.clone(), win_id);
conn.flush()?;
loop {
poll_with_timeout(&poller, conn, Duration::from_millis(1_000))?;
while let Some(event) = conn.poll_for_event()? {
println!("{event:?})");
match event {
Event::ConfigureNotify(event) => {
width = event.width;
height = event.height;
}
Event::ClientMessage(event) => {
let data = event.data.as_data32();
if event.format == 32
&& event.window == win_id
&& data[0] == atoms.WM_DELETE_WINDOW
{
println!("Window was asked to close");
return Ok(());
}
}
Event::Error(_) => println!("Got an unexpected error"),
_ => println!("Got an unknown event"),
}
}
redraw(conn, screen, win_id, gc_id, (width, height))?;
conn.flush()?;
}
}
/// Get the current time as (hour, minute, second)
fn get_time() -> (u8, u8, u8) {
let total_secs = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
let (second, total_minutes) = (total_secs % 60, total_secs / 60);
let (minute, total_hours) = (total_minutes % 60, total_minutes / 60);
let hour = total_hours % 24;
// This is in UTC. Getting local time is complicated and not important for us.
(hour as _, minute as _, second as _)
}
include!("integration_test_util/util.rs");
struct CallOnDrop<F: FnMut()>(F);
impl<F: FnMut()> Drop for CallOnDrop<F> {
fn drop(&mut self) {
(self.0)();
}
}