Open psychon opened 5 years ago
So, I did this and noticed that the resulting program doesn't work properly. I came up with a hack to make it do things (ignore_override_redirect
), but was worried that I might be doing something wrong in my port of findArgbVisual
. Only at the very end did I ever run xshowdamage.c
. Seems like this either doesn't work properly any more or doesn't really work under i3. Bummer.
Anyway, here is the Rust version and I do not feel like opening a PR for this:
// This is a (rough) port of xshowdamage app from xorg.
// The original file has Copyright (C) 2005 Novell, Inc., is authored by David Reveman
// <davidr@novell.com> and is licensed under a MIT license.
use std::process::exit;
use x11rb::connection::Connection;
use x11rb::errors::ReplyOrIdError;
use x11rb::properties::{WmHints, WmSizeHints, WmSizeHintsSpecification};
use x11rb::protocol::damage::{self, ConnectionExt as _};
use x11rb::protocol::render::{self, ConnectionExt as _};
use x11rb::protocol::shape::{self, ConnectionExt as _};
use x11rb::protocol::xproto::{self, ConnectionExt as _};
use x11rb::protocol::Event;
use x11rb::wrapper::ConnectionExt as _;
x11rb::atom_manager! {
pub Atoms: AtomsCookie {
_NET_WM_STATE,
_NET_WM_STATE_FULLSCREEN,
_NET_WM_STATE_ABOVE,
_NET_WM_STATE_STICKY,
_NET_WM_STATE_SKIP_TASKBAR,
_NET_WM_STATE_SKIP_PAGER,
}
}
#[derive(Debug, Default)]
struct DamageBox {
x: i16,
y: i16,
width: u16,
height: u16,
alpha: u16,
}
#[derive(Debug, Default)]
struct State {
boxes: Vec<DamageBox>,
window: xproto::Window,
width: u16,
height: u16,
pixmap: xproto::Pixmap,
picture: render::Picture,
gc: xproto::Gcontext,
redraw: bool,
}
impl State {
fn new(
conn: &impl Connection,
screen: &xproto::Screen,
window: xproto::Window,
pict_format: render::Pictformat,
) -> Result<Self, ReplyOrIdError> {
let (width, height) = (screen.width_in_pixels, screen.height_in_pixels);
let pixmap = conn.generate_id()?;
let picture = conn.generate_id()?;
let gc = conn.generate_id()?;
conn.create_pixmap(32, pixmap, window, width, height)?;
conn.render_create_picture(picture, pixmap, pict_format, &Default::default())?;
conn.create_gc(
gc,
window,
&xproto::CreateGCAux::new().graphics_exposures(0),
)?;
Ok(Self {
boxes: vec![],
window,
width,
height,
pixmap,
picture,
gc,
redraw: true,
})
}
fn watch_window(
&mut self,
conn: &impl Connection,
window: xproto::Window,
) -> Result<(), ReplyOrIdError> {
if window == self.window {
return Ok(());
}
// XXX: Was xshowdamage written for non-reparenting WMs?!? Nothing happens when this is active.
let ignore_override_redirect = true;
if ignore_override_redirect
|| !conn
.get_window_attributes(window)?
.reply()?
.override_redirect
{
let id = conn.generate_id()?;
conn.damage_create(id, window, damage::ReportLevel::RAW_RECTANGLES)?;
}
Ok(())
}
fn handle_event(&mut self, conn: &impl Connection, event: Event) -> Result<(), ReplyOrIdError> {
match event {
Event::Expose(_) => {
// XXX: This code is unreachable since we do not ask for expose events, but the
// original code also has this...?!
self.redraw = true;
Ok(())
}
Event::MapNotify(notify) => {
if notify.window != self.window {
self.watch_window(conn, notify.window)?;
}
Ok(())
}
Event::DamageNotify(notify) => {
if notify.drawable != self.window {
self.boxes.push(DamageBox {
x: notify.geometry.x + notify.area.x,
y: notify.geometry.y + notify.area.y,
width: notify.area.width,
height: notify.area.height,
alpha: 0xffff,
});
self.redraw = true;
}
Ok(())
}
Event::Error(err) => {
eprintln!("Got error {:?}", err);
Ok(())
}
_ => Ok(()),
}
}
fn redraw_boxes(&mut self, conn: &impl Connection) -> Result<(), ReplyOrIdError> {
if !self.redraw {
return Ok(());
}
self.redraw = false;
// Clear the pixmap
conn.render_fill_rectangles(
render::PictOp::SRC,
self.picture,
render::Color {
red: 0,
green: 0,
blue: 0,
alpha: 0,
},
&[xproto::Rectangle {
x: 0,
y: 0,
width: self.width,
height: self.height,
}],
)?;
// Draw boxes
for entry in &self.boxes {
conn.render_fill_rectangles(
render::PictOp::OVER,
self.picture,
render::Color {
red: entry.alpha,
green: 0,
blue: 0,
alpha: entry.alpha,
},
&[xproto::Rectangle {
x: entry.x,
y: entry.y,
width: entry.width,
height: entry.height,
}],
)?;
}
// Copy to the window
conn.copy_area(
self.pixmap,
self.window,
self.gc,
0,
0,
0,
0,
self.width,
self.height,
)?;
Ok(())
}
}
/// Check whether the X11 server supports all the X11 extensions that we need
fn check_server(
conn: &impl Connection,
screen: &xproto::Screen,
) -> Result<(xproto::Visualid, render::Pictformat), ReplyOrIdError> {
let extensions = [
damage::X11_EXTENSION_NAME,
render::X11_EXTENSION_NAME,
shape::X11_EXTENSION_NAME,
];
for ext in &extensions {
conn.prefetch_extension_information(ext)?;
}
for ext in &extensions {
if conn.extension_information(ext)?.is_none() {
eprintln!("{} extension is not supported", ext);
exit(1);
}
}
// We do not actually care about the version that the server supports, so ignore the reply
conn.damage_query_version(damage::X11_XML_VERSION.0, damage::X11_XML_VERSION.1)?;
let pict_formats = conn.render_query_pict_formats()?.reply()?;
// Find all 32 bit true color visuals that correspond to a RENDER direct format with alpha.
// The original code uses XGetVisualInfo() and XRenderFindVisualFormat() to simplify this.
let pict_format_for_visual = |visual: xproto::Visualid| -> Option<render::Pictformat> {
pict_formats
.screens
.iter()
.flat_map(|pict_screen| {
pict_screen
.depths
.iter()
.flat_map(|pict_depth| {
pict_depth
.visuals
.iter()
.filter(|pict_visual| pict_visual.visual == visual)
.map(|pict_visual| pict_visual.format)
.next()
})
.next()
})
.next()
};
let find_picture_format_info = |pict_format: render::Pictformat| {
pict_formats
.formats
.iter()
.filter(|format| format.id == pict_format)
.next()
};
let result = screen
.allowed_depths
.iter()
.filter(|depth| depth.depth == 32)
.filter_map(|depth| {
depth
.visuals
.iter()
.filter(|visual| visual.class == xproto::VisualClass::TRUE_COLOR)
.map(|visual| visual.visual_id)
.filter_map(|visual_id| {
pict_format_for_visual(visual_id)
.and_then(find_picture_format_info)
.filter(|info| {
info.type_ == render::PictType::DIRECT && info.direct.alpha_mask != 0
})
.map(|info| (visual_id, info.id))
})
.next()
})
.next();
match result {
Some(result) => Ok(result),
None => {
eprintln!("The X11 server does not support transparent windows");
exit(1);
}
}
}
fn create_window(
conn: &impl Connection,
screen: &xproto::Screen,
visual_id: xproto::Visualid,
atoms: &Atoms,
) -> Result<xproto::Window, ReplyOrIdError> {
// Create the window
let colormap = xproto::ColormapWrapper::create_colormap(
conn,
xproto::ColormapAlloc::NONE,
screen.root,
visual_id,
)?;
let window = conn.generate_id()?;
conn.create_window(
32,
window,
screen.root,
0,
0,
screen.width_in_pixels,
screen.height_in_pixels,
0,
xproto::WindowClass::INPUT_OUTPUT,
visual_id,
&xproto::CreateWindowAux::new()
.event_mask(xproto::EventMask::EXPOSURE)
.background_pixel(0)
.border_pixel(0)
.colormap(colormap.colormap()),
)?;
// Set hints on the window
let mut hints = WmHints::new();
hints.input = Some(false);
hints.set(conn, window)?;
let mut hints = WmSizeHints::new();
hints.position = Some((WmSizeHintsSpecification::ProgramSpecified, 0, 0));
hints.size = Some((
WmSizeHintsSpecification::ProgramSpecified,
screen.width_in_pixels.into(),
screen.height_in_pixels.into(),
));
hints.set(conn, window, xproto::AtomEnum::WM_NORMAL_HINTS)?;
conn.change_property32(
xproto::PropMode::REPLACE,
window,
atoms._NET_WM_STATE,
xproto::AtomEnum::ATOM,
&[
atoms._NET_WM_STATE_FULLSCREEN,
atoms._NET_WM_STATE_ABOVE,
atoms._NET_WM_STATE_STICKY,
atoms._NET_WM_STATE_SKIP_TASKBAR,
atoms._NET_WM_STATE_SKIP_PAGER,
],
)?;
// Make the window click-through by setting an empty input shape
conn.shape_rectangles(
shape::SO::SET,
shape::SK::INPUT,
xproto::ClipOrdering::YX_BANDED,
window,
0,
0,
&[],
)?;
conn.map_window(window)?;
Ok(window)
}
fn select_input_on_all_children(
conn: &impl Connection,
window: xproto::Window,
) -> Result<(), ReplyOrIdError> {
conn.change_window_attributes(
window,
&xproto::ChangeWindowAttributesAux::new()
.event_mask(xproto::EventMask::SUBSTRUCTURE_NOTIFY),
)?;
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// The original program supported an optional window-id argument. That was not ported.
let (conn, screen_num) = x11rb::connect(None)?;
let screen = &conn.setup().roots[screen_num];
let atoms = Atoms::new(&conn)?;
let (visual_id, picture_format) = check_server(&conn, screen)?;
let atoms = atoms.reply()?;
let window = create_window(&conn, screen, visual_id, &atoms)?;
// The original code has an "if" here. Since we do not implement the watch window option, we always do this
select_input_on_all_children(&conn, screen.root)?;
let mut state = State::new(&conn, screen, window, picture_format)?;
// Watch all existing windows
for child in conn.query_tree(screen.root)?.reply()?.children {
state.watch_window(&conn, child)?;
}
loop {
while let Some(event) = conn.poll_for_event()? {
state.handle_event(&conn, event)?;
}
state.redraw_boxes(&conn)?;
if state.boxes.is_empty() {
conn.flush()?;
state.handle_event(&conn, conn.wait_for_event()?)?;
} else {
// Make boxes more transparent
state
.boxes
.iter_mut()
.for_each(|element| element.alpha /= 2);
// Remove invisible ones
state.boxes.retain(|element| element.alpha > 0x00ff);
state.redraw = true;
conn.flush()?;
std::thread::sleep(std::time::Duration::from_millis(40));
}
}
}
(Oh and: Instead of poll()
ing for events, my Rust version just sleeps for 40 ms and does not do any kind of earlier wakeup. Seems to work about equally well.)
Edit: Ideas for improvements.
Similar to #25, but with less code to worry about: https://gitlab.freedesktop.org/xorg/app/xshowdamage/blob/master/xshowdamage.c
This also needs something like
poll
orselect
(which the libc crate provides).