Files

278 lines
7.6 KiB
Rust

// This is a (rough) port of hypnomoire from the xcb-demo repository.
// The original file has Copyright (C) 2001-2002 Bart Massey and Jamey Sharp and is licensed under
// a MIT license.
extern crate x11rb;
use std::f64::consts::PI;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;
use x11rb::connection::Connection;
use x11rb::errors::{ReplyError, ReplyOrIdError};
use x11rb::protocol::xproto::*;
use x11rb::protocol::Event;
use x11rb::wrapper::ConnectionExt as _;
use x11rb::COPY_DEPTH_FROM_PARENT;
/// Lag angle for the follow line
const LAG: f64 = 0.3;
/// Frames per second
const FRAME_RATE: u64 = 10;
/// Number of windows to show
const WINS: usize = 3;
#[derive(Default)]
struct WindowState {
window: Window,
pixmap: Pixmap,
angle_velocity: f64,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let (conn, screen_num) = connect(None)?;
let conn = Arc::new(conn);
let screen = &conn.setup().roots[screen_num];
let white = conn.generate_id()?;
let black = conn.generate_id()?;
conn.create_gc(
white,
screen.root,
&CreateGCAux::new()
.graphics_exposures(0)
.foreground(screen.white_pixel),
)?;
conn.create_gc(
black,
screen.root,
&CreateGCAux::new()
.graphics_exposures(0)
.foreground(screen.black_pixel),
)?;
let windows: Vec<_> = (0..WINS)
.map(|_| Arc::new(Mutex::new(WindowState::default())))
.collect();
for win in windows.iter() {
let conn2 = Arc::clone(&conn);
let win = Arc::clone(win);
thread::spawn(move || run(conn2, win, screen_num, white, black));
}
event_thread(conn, windows, white)?;
Ok(())
}
fn run<C: Connection>(
conn: Arc<C>,
window_state: Arc<Mutex<WindowState>>,
screen_num: usize,
white: Gcontext,
black: Gcontext,
) -> Result<(), ReplyOrIdError> {
let wm_protocols = conn.intern_atom(false, b"WM_PROTOCOLS")?;
let wm_delete_window = conn.intern_atom(false, b"WM_DELETE_WINDOW")?;
let wm_protocols = wm_protocols.reply()?.atom;
let wm_delete_window = wm_delete_window.reply()?.atom;
let screen = &conn.setup().roots[screen_num];
let default_size = 300;
let pixmap = conn.generate_id()?;
let window = conn.generate_id()?;
{
let mut guard = window_state.lock().unwrap();
guard.pixmap = pixmap;
guard.window = window;
guard.angle_velocity = 0.05;
}
conn.create_window(
COPY_DEPTH_FROM_PARENT,
window,
screen.root,
0, // x
0, // y
default_size, // width
default_size, // height
0,
WindowClass::INPUT_OUTPUT,
screen.root_visual,
&CreateWindowAux::new()
.background_pixel(screen.white_pixel)
.event_mask(
EventMask::BUTTON_RELEASE | EventMask::EXPOSURE | EventMask::STRUCTURE_NOTIFY,
)
.do_not_propogate_mask(EventMask::BUTTON_PRESS),
)?;
conn.change_property32(
PropMode::REPLACE,
window,
wm_protocols,
AtomEnum::ATOM,
&[wm_delete_window],
)?;
conn.map_window(window)?;
conn.create_pixmap(
screen.root_depth,
pixmap,
window,
default_size,
default_size,
)?;
let rect = Rectangle {
x: 0,
y: 0,
width: default_size,
height: default_size,
};
conn.poly_fill_rectangle(pixmap, white, &[rect])?;
let mut theta: f64 = 0.0;
let radius = f64::from(default_size) + 1.0;
loop {
let guard = window_state.lock().unwrap();
let (center_x, center_y) = (default_size as i16 / 2, default_size as i16 / 2);
let (sin, cos) = theta.sin_cos();
let (x, y) = ((radius * cos) as i16, (radius * sin) as i16);
let lines = [
Point {
x: center_x,
y: center_y,
},
Point { x, y },
];
conn.poly_line(CoordMode::PREVIOUS, pixmap, black, &lines)?;
let (sin, cos) = (theta + LAG).sin_cos();
let (x, y) = ((radius * cos) as i16, (radius * sin) as i16);
let lines = [
Point {
x: center_x,
y: center_y,
},
Point { x, y },
];
conn.poly_line(CoordMode::PREVIOUS, pixmap, white, &lines)?;
conn.copy_area(
pixmap,
window,
white,
0,
0,
0,
0,
default_size,
default_size,
)?;
conn.flush()?;
theta += guard.angle_velocity;
while theta > 2.0 * PI {
theta -= 2.0 * PI;
}
while theta < 0.0 {
theta += 2.0 * PI;
}
drop(guard);
thread::sleep(Duration::from_micros(1_000_000 / FRAME_RATE));
}
}
fn event_thread<C>(
conn_arc: Arc<C>,
windows: Vec<Arc<Mutex<WindowState>>>,
white: Gcontext,
) -> Result<(), ReplyError>
where
C: Connection + Send + Sync + 'static,
{
let mut first_window_mapped = false;
let conn = &*conn_arc;
loop {
let event = conn.wait_for_event()?;
match event {
Event::Expose(event) => {
if let Some(state) = find_window_by_id(&windows, event.window) {
let state = state.lock().unwrap();
conn.copy_area(
state.pixmap,
state.window,
white,
event.x as _,
event.y as _,
event.x as _,
event.y as _,
event.width,
event.height,
)?;
if event.count == 0 {
conn.flush()?;
}
} else {
eprintln!("Expose on unknown window!");
}
}
Event::ButtonRelease(event) => {
if let Some(state) = find_window_by_id(&windows, event.event) {
let mut state = state.lock().unwrap();
match event.detail {
// Button 1 is left mouse button
1 => state.angle_velocity *= -1.0,
// Buttons 4 and 5 are mouse wheel
4 => state.angle_velocity += 0.001,
5 => state.angle_velocity -= 0.001,
_ => {}
}
} else {
eprintln!("ButtonRelease on unknown window!");
}
}
Event::MapNotify(event) => {
if !first_window_mapped {
first_window_mapped = true;
util::start_timeout_thread(conn_arc.clone(), event.window);
}
}
Event::ClientMessage(_) => {
// We simply assume that this is a message to close. Since we are the main thread,
// everything closes when we exit.
return Ok(());
}
Event::Error(err) => {
eprintln!("Got an X11 error: {err:?}");
}
_ => {}
}
}
}
fn find_window_by_id(
windows: &[Arc<Mutex<WindowState>>],
window: Window,
) -> Option<&Arc<Mutex<WindowState>>> {
windows.iter().find(|state| {
state
.lock()
.map(|state| state.window == window)
.unwrap_or(false)
})
}
include!("integration_test_util/util.rs");
include!("integration_test_util/connect.rs");