1 // This example reads a .ppm file and displays the image on screen. It shows how to work
2 // with images.
3
4 use x11rb::connection::Connection;
5 use x11rb::errors::ReplyOrIdError;
6 use x11rb::image::{ColorComponent, Image, PixelLayout};
7 use x11rb::protocol::xproto::{
8 AtomEnum, ConnectionExt, CreateGCAux, CreateWindowAux, PropMode, Screen, VisualClass, Visualid,
9 Window, WindowClass,
10 };
11 use x11rb::protocol::Event;
12 use x11rb::wrapper::ConnectionExt as _;
13
14 x11rb::atom_manager! {
15 Atoms: AtomsCookie {
16 WM_PROTOCOLS,
17 WM_DELETE_WINDOW,
18 }
19 }
20
21 /// Create a window with the given image as background.
create_window( conn: &impl Connection, screen: &Screen, atoms: &Atoms, image: &Image, ) -> Result<Window, ReplyOrIdError>22 fn create_window(
23 conn: &impl Connection,
24 screen: &Screen,
25 atoms: &Atoms,
26 image: &Image,
27 ) -> Result<Window, ReplyOrIdError> {
28 let win_id = conn.generate_id()?;
29 let pixmap_id = conn.generate_id()?;
30 let gc_id = conn.generate_id()?;
31
32 conn.create_gc(
33 gc_id,
34 screen.root,
35 &CreateGCAux::default().graphics_exposures(0),
36 )?;
37 conn.create_pixmap(
38 screen.root_depth,
39 pixmap_id,
40 screen.root,
41 image.width(),
42 image.height(),
43 )?;
44 image.put(conn, pixmap_id, gc_id, 0, 0)?;
45 conn.free_gc(gc_id)?;
46
47 conn.create_window(
48 screen.root_depth,
49 win_id,
50 screen.root,
51 0,
52 0,
53 image.width(),
54 image.height(),
55 0,
56 WindowClass::INPUT_OUTPUT,
57 0,
58 &CreateWindowAux::default().background_pixmap(pixmap_id),
59 )?;
60 conn.free_pixmap(pixmap_id)?;
61
62 conn.change_property32(
63 PropMode::REPLACE,
64 win_id,
65 atoms.WM_PROTOCOLS,
66 AtomEnum::ATOM,
67 &[atoms.WM_DELETE_WINDOW],
68 )?;
69
70 Ok(win_id)
71 }
72
73 /// Check that the given visual is "as expected" (pixel values are 0xRRGGBB with RR/GG/BB being the
74 /// colors). Otherwise, this exits the process.
check_visual(screen: &Screen, id: Visualid) -> PixelLayout75 fn check_visual(screen: &Screen, id: Visualid) -> PixelLayout {
76 // Find the information about the visual and at the same time check its depth.
77 let visual_info = screen
78 .allowed_depths
79 .iter()
80 .filter_map(|depth| {
81 let info = depth.visuals.iter().find(|depth| depth.visual_id == id);
82 info.map(|info| (depth.depth, info))
83 })
84 .next();
85 let (depth, visual_type) = match visual_info {
86 Some(info) => info,
87 None => {
88 eprintln!("Did not find the root visual's description?!");
89 std::process::exit(1);
90 }
91 };
92 // Check that the pixels have red/green/blue components that we can set directly.
93 match visual_type.class {
94 VisualClass::TRUE_COLOR | VisualClass::DIRECT_COLOR => {}
95 _ => {
96 eprintln!(
97 "The root visual is not true / direct color, but {:?}",
98 visual_type,
99 );
100 std::process::exit(1);
101 }
102 }
103 let result = PixelLayout::from_visual_type(*visual_type)
104 .expect("The server sent a malformed visual type");
105 assert_eq!(result.depth(), depth);
106 result
107 }
108
main() -> Result<(), Box<dyn std::error::Error>>109 fn main() -> Result<(), Box<dyn std::error::Error>> {
110 // Load the image
111 let image = match std::env::args_os().nth(1) {
112 None => {
113 eprintln!(
114 "Expected a file name of a PPM as argument, using a built-in default image instead"
115 );
116 ppm_parser::parse_ppm_bytes(&BUILTIN_IMAGE)?
117 }
118 Some(arg) => ppm_parser::parse_ppm_file(&arg)?,
119 };
120
121 let (conn, screen_num) = x11rb::connect(None)?;
122
123 // The following is only needed for start_timeout_thread(), which is used for 'tests'
124 let conn1 = std::sync::Arc::new(conn);
125 let conn = &*conn1;
126
127 let screen = &conn.setup().roots[screen_num];
128 let pixel_layout = check_visual(screen, screen.root_visual);
129
130 // Convert the image from the PPM format into the server's native format.
131 let ppm_layout = PixelLayout::new(
132 ColorComponent::new(8, 16)?,
133 ColorComponent::new(8, 8)?,
134 ColorComponent::new(8, 0)?,
135 );
136 let image = image.reencode(ppm_layout, pixel_layout, conn.setup())?;
137
138 let atoms = Atoms::new(conn)?.reply()?;
139 let win_id = create_window(conn, screen, &atoms, &image)?;
140 conn.map_window(win_id)?;
141
142 util::start_timeout_thread(conn1.clone(), win_id);
143
144 conn.flush()?;
145
146 loop {
147 let event = conn.wait_for_event().unwrap();
148 match event {
149 Event::ClientMessage(event) => {
150 let data = event.data.as_data32();
151 if event.format == 32 && event.window == win_id && data[0] == atoms.WM_DELETE_WINDOW
152 {
153 println!("Window was asked to close");
154 return Ok(());
155 }
156 }
157 Event::Error(err) => println!("Got an unexpected error: {:?}", err),
158 ev => println!("Got an unknown event: {:?}", ev),
159 }
160 }
161 }
162
163 mod ppm_parser {
164 use std::ffi::OsStr;
165 use std::io::{Error as IOError, ErrorKind, Read, Result as IOResult};
166
167 use x11rb::image::{BitsPerPixel, Image, ImageOrder, ScanlinePad};
168
make_io_error(text: &'static str) -> IOError169 fn make_io_error(text: &'static str) -> IOError {
170 IOError::new(ErrorKind::Other, text)
171 }
172
173 /// Read until the next b'\n'.
read_to_end_of_line(input: &mut impl Read) -> IOResult<()>174 fn read_to_end_of_line(input: &mut impl Read) -> IOResult<()> {
175 let mut byte = [0; 1];
176 loop {
177 input.read_exact(&mut byte)?;
178 if byte[0] == b'\n' {
179 return Ok(());
180 }
181 }
182 }
183
184 /// Read a decimal number from the input.
read_decimal(input: &mut impl Read) -> IOResult<u16>185 fn read_decimal(input: &mut impl Read) -> IOResult<u16> {
186 let mut byte = [0; 1];
187
188 // Skip leading whitespace and comments
189 loop {
190 input.read_exact(&mut byte)?;
191 match byte[0] {
192 b' ' | b'\t' | b'\r' => {}
193 // Comment, skip a whole line
194 b'#' => read_to_end_of_line(input)?,
195 _ => break,
196 }
197 }
198
199 // Now comes a number
200 if !byte[0].is_ascii_digit() {
201 return Err(make_io_error("Failed parsing a number"));
202 }
203
204 let mut result: u16 = 0;
205 while byte[0].is_ascii_digit() {
206 let value = u16::from(byte[0] - b'0');
207 result = result
208 .checked_mul(10)
209 .map(|result| result + value)
210 .ok_or_else(|| make_io_error("Overflow while parsing number"))?;
211
212 input.read_exact(&mut byte)?;
213 }
214
215 // After the number, there should be some whitespace.
216 if byte[0].is_ascii_whitespace() {
217 Ok(result)
218 } else {
219 Err(make_io_error("Unexpected character in header"))
220 }
221 }
222
parse_ppm(input: &mut impl Read) -> IOResult<Image<'static>>223 fn parse_ppm(input: &mut impl Read) -> IOResult<Image<'static>> {
224 let mut header = [0; 2];
225 input.read_exact(&mut header)?;
226 if header != *b"P6" {
227 return Err(make_io_error("Incorrect file header"));
228 }
229 read_to_end_of_line(input)?;
230 let width = read_decimal(input)?;
231 let height = read_decimal(input)?;
232 let max = read_decimal(input)?;
233
234 if max != 255 {
235 eprintln!(
236 "Image declares a max pixel value of {}, but I expected 255.",
237 max,
238 );
239 eprintln!("Something will happen...?");
240 }
241
242 let mut image = Image::allocate(
243 width,
244 height,
245 ScanlinePad::Pad8,
246 24,
247 BitsPerPixel::B24,
248 ImageOrder::MSBFirst,
249 );
250 input.read_exact(image.data_mut())?;
251
252 Ok(image)
253 }
254
parse_ppm_bytes(bytes: &[u8]) -> IOResult<Image<'static>>255 pub fn parse_ppm_bytes(bytes: &[u8]) -> IOResult<Image<'static>> {
256 use std::io::Cursor;
257
258 parse_ppm(&mut Cursor::new(bytes))
259 }
260
parse_ppm_file(file_name: &OsStr) -> IOResult<Image<'static>>261 pub fn parse_ppm_file(file_name: &OsStr) -> IOResult<Image<'static>> {
262 use std::fs::File;
263 use std::io::BufReader;
264
265 parse_ppm(&mut BufReader::new(File::open(file_name)?))
266 }
267 }
268
269 // Simple builtin PPM that is used if none is provided on the command line
270 #[rustfmt::skip]
271 const BUILTIN_IMAGE: [u8; 35] = [
272 b'P', b'6', b'\n',
273 // width and height
274 b'4', b' ', b'2', b'\n',
275 b'2', b'5', b'5', b'\n',
276 // Black pixel
277 0x00, 0x00, 0x00,
278 // red pixel
279 0xff, 0x00, 0x00,
280 // green pixel
281 0x00, 0xff, 0x00,
282 // blue pixel
283 0x00, 0x00, 0xff,
284 // white pixel
285 0xff, 0xff, 0xff,
286 // cyan pixel
287 0x00, 0xff, 0xff,
288 // magenta pixel
289 0xff, 0x00, 0xff,
290 // yellow pixel
291 0xff, 0xff, 0x00,
292 ];
293
294 include!("integration_test_util/util.rs");
295