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