-
Notifications
You must be signed in to change notification settings - Fork 38
/
tutorial.rs
2861 lines (2631 loc) · 105 KB
/
tutorial.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// This file contains a tutorial of both this crate and the X11 protocol. It was created by porting
// an existing libxcb tutorial that is available here:
//
// https://www.x.org/releases/X11R7.7/doc/libxcb/tutorial/index.html
//
// References to libxcb in the text were retained. Only the code examples were adapted, where
// possible. Some detailed explanations of libxcb required heavier editing.
//
// Since the libxcb tutorial is part of the libxcb source code, I assume that its MIT LICENSE
// applies. The exact situation is a bit unclear (libxcb's COPYING file has "Copyright (C)
// 2001-2006 Bart Massey, Jamey Sharp, and Josh Triplett.", but the git history says that the
// tutorial was only started in 2006 and was last touched in 2014).
//
// This tutorial is in a Rust file to ensure that the contained code actually compiles and
// everything works as intended. Nothing is worse than bitrotted tutorial. Here are all the
// imports:
extern crate x11rb;
use std::error::Error;
use x11rb::connection::Connection;
use x11rb::errors::{ConnectionError, ReplyError, ReplyOrIdError};
use x11rb::protocol::xproto::*;
use x11rb::protocol::Event;
use x11rb::wrapper::ConnectionExt as _;
use x11rb::COPY_DEPTH_FROM_PARENT;
use x11rb_protocol::SequenceNumber;
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Introduction
// ============
//
// This tutorial is based on the [Xlib Tutorial] written by Guy Keren. The author allowed me to
// take some parts of his text, mainly the text which deals with the X Windows generality.
//
// [Xlib Tutorial](http://users.actcom.co.il/~choo/lupg/tutorials/xlib-programming/xlib-programming.html)
//
// This tutorial is intended for people who want to start to program with the [XCB] library. keep
// in mind that XCB, like the [Xlib] library, isn't what most programmers wanting to write X
// applications are looking for. They should use a much higher level GUI toolkit like Motif,
// [LessTiff], [GTK], [QT], [EWL], [ETK], or use [Cairo]. However, we need to start somewhere. More
// than this, knowing how things work down below is never a bad idea.
//
// [XCB](http://xcb.freedesktop.org)
// [Xlib](http://tronche.com/gui/x/xlib/introduction)
// [LessTiff](http://www.lesstif.org)
// [GTK](http://www.gtk.org)
// [QT](http://www.trolltech.com)
// [EWL](http://www.enlightenment.org)
// [ETK](http://www.enlightenment.org)
// [Cairo](http://cairographics.org)
//
// After reading this tutorial, one should be able to write very simple graphical programs, but not
// programs with decent user interfaces. For such programs, one of the previously mentioned
// libraries should be used.
//
// But what is XCB? Xlib has been the standard C binding for the [X Window System] protocol for
// many years now. It is an excellent piece of work, but there are applications for which it is not
// ideal, for example:
//
// [X Window System](http://www.x.org)
//
// * **Small platforms**: Xlib is a large piece of code, and it's difficult to make it smaller
// * **Latency hiding**: Xlib requests requiring a reply are effectively synchronous: they block
// until the reply appears, whether the result is needed immediately or
// not.
// * **Direct access to the protocol**: Xlib does quite a bit of caching, layering, and similar
// optimizations. While this is normally a feature, it makes it difficult
// to simply emit specified X protocol requests and process specific
// responses.
// * **Threaded applications**: While Xlib does attempt to support multithreading, the API makes
// this difficult and error-prone.
// * **New extensions**: The Xlib infrastructure provides limited support for the new creation of
// X extension client side code.
//
// For these reasons, among others, XCB, an X C binding, has been designed to solve the above
// problems and thus provide a base for
//
// * Toolkit implementation.
// * Direct protocol-level programming.
// * Lightweight emulation of commonly used portions of the Xlib API.
//
//
// The client and server model of the X window system
// ==================================================
//
// The X Window System was developed with one major goal: flexibility. The idea was that the way
// things look is one thing, but the way things work is another matter. Thus, the lower levels
// provide the tools required to draw windows, handle user input, allow drawing graphics using
// colors (or black and white screens), etc. To this point, a decision was made to separate the
// system into two parts. A client that decides what to do, and a server that actually draws on the
// screen and reads user input in order to send it to the client for processing.
//
// This model is the complete opposite of what is used to when dealing with clients and servers. In
// our case, the user sits near the machine controlled by the server, while the client might be
// running on a remote machine. The server controls the screens, mouse and keyboard. A client may
// connect to the server, request that it draws a window (or several windows), and ask the server
// to send it any input the user sends to these windows. Thus, several clients may connect to a
// single X server (one might be running mail software, one running a WWW browser, etc). When input
// is sent by the user to some window, the server sends a message to the client controlling this
// window for processing. The client decides what to do with this input, and sends the server
// requests for drawing in the window.
//
// The whole session is carried out using the X message protocol. This protocol was originally
// carried over the TCP/IP protocol suite, allowing the client to run on any machine connected to
// the same network that the server is. Later on, the X servers were extended to allow clients
// running on the local machine with more optimized access to the server (note that an X protocol
// message may be several hundreds of KB in size), such as using shared memory, or using Unix
// domain sockets (a method for creating a logical channel on a Unix system between two processes).
//
//
// GUI programming: the asynchronous model
// =======================================
//
// Unlike conventional computer programs, that carry some serial nature, a GUI program usually uses
// an asynchronous programming model, also known as "event-driven programming". This means that
// that program mostly sits idle, waiting for events sent by the X server, and then acts upon these
// events. An event may say "The user pressed the 1st button mouse in spot (x,y)", or "The window
// you control needs to be redrawn". In order for the program to be responsive to the user input,
// as well as to refresh requests, it needs to handle each event in a rather short period of time
// (e.g. less that 200 milliseconds, as a rule of thumb).
//
// This also implies that the program may not perform operations that might take a long time while
// handling an event (such as opening a network connection to some remote server, or connecting to
// a database server, or even performing a long file copy operation). Instead, it needs to perform
// all these operations in an asynchronous manner. This may be done by using various asynchronous
// models to perform the longish operations, or by performing them in a different process or
// thread.
//
// So the way a GUI program looks is something like that:
// 1. Perform initialization routines.
// 2. Connect to the X server.
// 3. Perform X-related initialization.
// 4. While not finished:
// 1. Receive the next event from the X server.
// 2. Handle the event, possibly sending various drawing requests to the X server.
// 3. If the event was a quit message, exit the loop.
// 5. Close down the connection to the X server.
// 6. Perform cleanup operations.
//
//
// Basic XCB notions
// =================
//
// XCB has been created to eliminate the need for programs to actually implement the X protocol
// layer. This library gives a program a very low-level access to any X server. Since the protocol
// is standardized, a client using any implementation of XCB may talk with any X server (the same
// occurs for Xlib, of course). We now give a brief description of the basic XCB notions. They will
// be detailed later.
//
//
// The X Connection
// ----------------
//
// The major notion of using XCB is the X Connection. This is a structure representing the
// connection we have open with a given X server. It hides a queue of messages coming from the
// server, and a queue of pending requests that our client intends to send to the server. In XCB,
// this structure is named 'xcb_connection_t'. It is analogous to the Xlib Display. When we open a
// connection to an X server, the library returns a pointer to such a structure. Later, we supply
// this pointer to any XCB function that should send messages to the X server or receive messages
// from this server.
//
//
// Requests and replies: the Xlib killers
// --------------------------------------
//
// To ask for information from the X server, we have to make a request and ask for a reply. With
// Xlib, these two tasks are automatically done: Xlib locks the system, sends a request, waits for
// a reply from the X server and unlocks. This is annoying, especially if one makes a lot of
// requests to the X server. Indeed, Xlib has to wait for the end of a reply before asking for the
// next request (because of the locks that Xlib sends). For example, here is a time-line of N=4
// requests/replies with Xlib, with a round-trip latency **T_round_trip** that is 5 times long as
// the time required to write or read a request/reply (**T_write/T_read**):
//
// W-----RW-----RW-----RW-----R
//
// * W: Writing request
// * -: Stalled, waiting for data
// * R: Reading reply
//
// The total time is N * (T_write + T_round_trip + T_read).
//
// With XCB, we can suppress most of the round-trips as the requests and the replies are not
// locked. We usually send a request, then XCB returns to us a **cookie**, which is an identifier.
// Then, later, we ask for a reply using this **cookie** and XCB returns a pointer to that reply.
// Hence, with XCB, we can send a lot of requests, and later in the program, ask for all the
// replies when we need them. Here is the time-line for 4 requests/replies when we use this
// property of XCB:
//
// WWWW--RRRR
//
// The total time is N * T_write + max (0, T_round_trip - (N-1) * T_write) + N * T_read.
// Which can be considerably faster than all those Xlib round-trips.
//
// Here is a program that computes the time to create 500 atoms with Xlib and XCB. It shows the Xlib way, the bad XCB way (which is similar to Xlib) and the good XCB way. On my computer, XCB is 25 times faster than Xlib.
//
#[allow(clippy::needless_collect)]
fn example1() -> Result<(), Box<dyn Error>> {
use std::time::Instant;
let (conn, _) = x11rb::connect(None)?;
const COUNT: usize = 500;
let mut atoms = [Into::<u32>::into(AtomEnum::NONE); COUNT];
// Init names
let names = (0..COUNT).map(|i| format!("NAME{}", i)).collect::<Vec<_>>();
// Bad use
let start = Instant::now();
for i in 0..COUNT {
atoms[i] = conn.intern_atom(false, names[i].as_bytes())?.reply()?.atom;
}
let diff = start.elapsed();
println!("bad use time: {:?}", diff);
// Good use
let start = Instant::now();
// The `collect` is needed to make sure that the closure passed to
// `map` is called for every iteration before executing the for loop.
let cookies = names
.iter()
.map(|name| conn.intern_atom(false, name.as_bytes()))
.collect::<Vec<_>>();
for (i, atom) in cookies.into_iter().enumerate() {
atoms[i] = atom?.reply()?.atom;
}
let diff2 = start.elapsed();
println!("good use time: {:?}", diff2);
println!(
"ratio: {:?}",
diff.as_nanos() as f64 / diff2.as_nanos() as f64
);
Ok(())
}
//
// The Graphic Context
// -------------------
//
// When we perform various drawing operations (graphics, text, etc), we may specify various options
// for controlling how the data will be drawn (what foreground and background colors to use, how
// line edges will be connected, what font to use when drawing some text, etc). In order to avoid
// the need to supply hundreds of parameters to each drawing function, a graphical context
// structure is used. We set the various drawing options in this structure, and then we pass a
// pointer to this structure to any drawing routines. This is rather handy, as we often need to
// perform several drawing requests with the same options. Thus, we would initialize a graphical
// context, set the desired options, and pass this structure to all drawing functions.
//
// Note that graphic contexts have no client-side structure in XCB, they're just XIDs. Xlib has a
// client-side structure because it caches the GC contents so it can avoid making redundant
// requests, but of course XCB doesn't do that.
//
//
// Events
// ------
//
// A structure is used to pass events received from the X server. XCB supports exactly the events
// specified in the protocol (33 events). This structure contains the type of event received
// (including a bit for whether it came from the server or another client), as well as the data
// associated with the event (e.g. position on the screen where the event was generated, mouse
// button associated with the event, region of the screen associated with a "redraw" event, etc).
// The way to read the event's data depends on the event type.
//
//
// Using XCB-based programs
// ========================
//
// [This part of the tutorial was not translated. Go read https://www.x.org/releases/X11R7.7/doc/libxcb/tutorial/index.html#use if you want.
//
//
// Opening and closing the connection to an X server
// =================================================
//
// An X program first needs to open the connection to the X server. There is a function that opens
// a connection. It requires the display name, or None. In the latter case, the display name will
// be the one in the environment variable DISPLAY.
//
// pub fn connect(dpy_name: Option<&str>) -> Result<([...], usize), [...]>;
//
// The second tuple value provides the screen number used for the connection. The returned
// structure describes an X11 connection and is opaque. Here is how the connection can be opened:
fn example2() -> Result<(), Box<dyn Error>> {
let (conn, _screen) = x11rb::connect(None)?;
// To close a connection, it suffices to drop the connection object
drop(conn);
Ok(())
}
// Checking basic information about a connection
// ---------------------------------------------
//
// Once we have opened a connection to an X server, we should check some basic information about
// it: what screens it has, what is the size (width and height) of the screen, how many colors it
// supports (black and white ? grey scale ?, 256 colors ? more ?), and so on. We get such
// information from the x11rb::xproto::Screen structure:
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct RenamedScreen {
pub root: u32,
pub default_colormap: u32,
pub white_pixel: u32,
pub black_pixel: u32,
pub current_input_masks: u32,
pub width_in_pixels: u16,
pub height_in_pixels: u16,
pub width_in_millimeters: u16,
pub height_in_millimeters: u16,
pub min_installed_maps: u16,
pub max_installed_maps: u16,
pub root_visual: u32,
pub backing_stores: u8,
pub save_unders: u8,
pub root_depth: u8,
pub allowed_depths: Vec<Depth>,
}
// Here is a small program that shows how to use get this struct:
fn example3() -> Result<(), Box<dyn Error>> {
// Open the connection to the X server. Use the DISPLAY environment variable.
let (conn, screen_num) = x11rb::connect(None)?;
// Get the screen #screen_num
let screen = &conn.setup().roots[screen_num];
println!();
println!("Informations of screen {}:", screen.root);
println!(" width.........: {}", screen.width_in_pixels);
println!(" height........: {}", screen.height_in_pixels);
println!(" white pixel...: {}", screen.white_pixel);
println!(" black pixel...: {}", screen.black_pixel);
println!();
Ok(())
}
// Creating a basic window - the "hello world" program
// ===================================================
//
// After we got some basic information about our screen, we can create our first window. In the X
// Window System, a window is characterized by an Id. So, in XCB, a window is of type:
//
#[allow(unused)]
pub type Window = u32;
// We first ask for a new Id for our window, with this function:
//
// trait Connection {
// fn generate_id(&self) -> u32;
// }
//
// Then, XCB supplies the following function to create new windows:
#[allow(unused, clippy::too_many_arguments)]
fn own_create_window<A: Connection, B>(
c: &A, // The connection to use
depth: u8, // Depth of the screen
wid: u32, // Id of the window
parent: u32, // Id of an existing window that should be the parent of the new window
x: i16, // X position of the top-left corner of the window (in pixels)
y: i16, // Y position of the top-left corner of the window (in pixels)
width: u16, // Width of the window (in pixels)
height: u16, // Height of the window (in pixels)
border_width: u16, // Width of the window's border (in pixels)
class: B,
visual: u32,
value_list: &CreateWindowAux,
) -> Result<SequenceNumber, ConnectionError>
where
B: Into<u16>,
{
unimplemented!();
}
// The fact that we created the window does not mean that it will be drawn on screen. By default,
// newly created windows are not mapped on the screen (they are invisible). In order to make our
// window visible, we use the function `map_window()`, whose prototype is
//
// fn map_window(&self, window: u32) -> Result<SequenceNumber, ConnectionError>;
//
// Finally, here is a small program to create a window of size 150x150 pixels, positioned at the top-left corner of the screen:
fn example4() -> Result<(), Box<dyn Error>> {
// Open the connection to the X server. Use the DISPLAY environment variable.
let (conn, screen_num) = x11rb::connect(None)?;
// Get the screen #screen_num
let screen = &conn.setup().roots[screen_num];
// Ask for our window's Id
let win = conn.generate_id()?;
// Create the window
conn.create_window(
COPY_DEPTH_FROM_PARENT, // depth (same as root)
win, // window Id
screen.root, // parent window
0, // x
0, // y
150, // width
150, // height
10, // border width
WindowClass::INPUT_OUTPUT, // class
screen.root_visual, // visual
&Default::default(),
)?; // masks, not used yet
// Map the window on the screen
conn.map_window(win)?;
// Make sure commands are sent before the sleep, so window is shown
conn.flush()?;
std::thread::sleep(std::time::Duration::from_secs(10));
Ok(())
}
// In this code, you see one more function - flush(), not explained yet. It is used to flush
// all the pending requests. More precisely, there are 2 functions that do such things. The first
// one is flush():
//
// trait Connection {
// fn flush(&self) -> Result<(), ConnectionError>;
// }
//
// This function flushes all pending requests to the X server (much like the fflush() function is
// used to flush standard output). The second function is xcb_aux_sync() / sync():
//
// trait ConnectionExt {
// fn sync(&self) -> Result<(), ReplyError>;
// }
//
// This functions also flushes all pending requests to the X server, and then waits until the X
// server finishing processing these requests. In a normal program, this will not be necessary
// (we'll see why when we get to write a normal X program), but for now, we put it there.
//
// The window that is created by the above code has a non defined background. This one can be set
// to a specific color, thanks to the two last parameters of `xcb_create_window()`, which are not
// described yet. See the subsections [Configuring a window] or [Registering for event types using
// event masks] for examples on how to use these parameters. In addition, as no events are handled,
// you have to make a Ctrl-C to interrupt the program.
//
//
// Drawing in a window
// ===================
//
// Drawing in a window can be done using various graphical functions (drawing pixels, lines,
// rectangles, etc). In order to draw in a window, we first need to define various general drawing
// parameters (what line width to use, which color to draw with, etc). This is done using a
// graphical context.
//
//
// Allocating a Graphics Context
// -----------------------------
//
// As we said, a graphical context defines several attributes to be used with the various drawing
// functions. For this, we define a graphical context. We can use more than one graphical context
// with a single window, in order to draw in multiple styles (different colors, different line
// widths, etc). In XCB, a Graphics Context is, as a window, characterized by an Id:
//
// pub type Gcontext = u32;
//
// We first ask the X server to attribute an Id to our graphic context with this function:
//
// trait Connection {
// fn generate_id(&self) -> u32;
// }
//
// Then, we set the attributes of the graphic context with this function:
//
#[allow(unused)]
fn my_create_gc<A: Connection>(
c: &A,
cid: u32,
drawable: u32,
value_list: &CreateGCAux,
) -> Result<SequenceNumber, ConnectionError> {
unimplemented!();
}
// The CreateGCAux parameter of this function is specific to x11rb. It contains all the optional
// arguments of the request. It is defined like this:
#[allow(unused)]
#[derive(Debug, Clone, Copy, Default)]
pub struct RenamedCreateGcAux {
pub function: Option<u32>,
pub plane_mask: Option<u32>,
pub foreground: Option<u32>,
pub background: Option<u32>,
pub line_width: Option<u32>,
pub line_style: Option<u32>,
pub cap_style: Option<u32>,
pub join_style: Option<u32>,
pub fill_style: Option<u32>,
pub fill_rule: Option<u32>,
pub tile: Option<u32>,
pub stipple: Option<u32>,
pub tile_stipple_x_origin: Option<i32>,
pub tile_stipple_y_origin: Option<i32>,
pub font: Option<u32>,
pub subwindow_mode: Option<u32>,
pub graphics_exposures: Option<u32>,
pub clip_x_origin: Option<i32>,
pub clip_y_origin: Option<i32>,
pub clip_mask: Option<u32>,
pub dash_offset: Option<u32>,
pub dashes: Option<u32>,
pub arc_mode: Option<u32>,
}
// We give now an example on how to allocate a graphic context that specifies that each drawing
// function that uses it will draw in foreground with a black color.
fn example5() -> Result<(), Box<dyn Error>> {
// Open the connection to the X server. Use the DISPLAY environment variable.
let (conn, screen_num) = x11rb::connect(None)?;
// Get the screen #screen_num
let screen = &conn.setup().roots[screen_num];
// Create a black graphic context for drawing in the foreground.
let win = screen.root;
let black = conn.generate_id()?;
let values = CreateGCAux::default().foreground(screen.black_pixel);
conn.create_gc(black, win, &values)?;
Ok(())
}
// [The following is a bit XCB-specific, because CreateGCAux does not exist in libxcb.]
//
// Note should be taken regarding the role of "value_mask" and "value_list" in the prototype of
// `xcb_create_gc()`. Since a graphic context has many attributes, and since we often just want to
// define a few of them, we need to be able to tell the `xcb_create_gc()` which attributes we want
// to set. This is what the "value_mask" parameter is for. We then use the "value_list" parameter
// to specify actual values for the attribute we defined in "value_mask". Thus, for each constant
// used in "value_list", we will use the matching constant in "value_mask". In this case, we define
// a graphic context with one attribute: when drawing (a point, a line, etc), the foreground color
// will be black. The rest of the attributes of this graphic context will be set to their default
// values.
//
// See the next Subsection for more details.
//
// Changing the attributes of a Graphics Context
// ---------------------------------------------
//
// Once we have allocated a Graphic Context, we may need to change its attributes (for example,
// changing the foreground color we use to draw a line, or changing the attributes of the font we
// use to display strings. See Subsections Drawing with a color and [Assigning a Font to a Graphic
// Context]. This is done by using this function:
//
// fn change_gc(&self, gc: u32, value_list: &ChangeGCAux) -> Result<SequenceNumber, ConnectionError>;
//
// [Some more XCB-specific explanations skipped]
//
// Drawing primitives: point, line, box, circle,...
// ------------------------------------------------
//
// After we have created a Graphic Context, we can draw on a window using this Graphic Context,
// with a set of XCB functions, collectively called "drawing primitives". Let see how they are
// used.
//
// To draw a point, or several points, we use
//
// fn poly_point<B>(&self, coordinate_mode: B, drawable: u32, gc: u32,
// points: &[Point]) -> Result<SequenceNumber, ConnectionError>
// where B: Into<u8>;
//
// The `coordinate_mode` parameter specifies the coordinate mode. Available values are
//
// pub enum CoordMode {
// Origin,
// Previous,
// }
//
// If XCB_COORD_MODE_PREVIOUS is used, then all points but the first one are relative to the
// immediately previous point.
//
// The `Point` type is just a structure with two fields (the coordinates of the point):
//
// #[derive(Debug, Clone, Copy)]
// pub struct Point {
// pub x: i16,
// pub y: i16,
// }
//
// You could see an example in xpoints.c. **TODO** Set the link.
//
// To draw a line, or a polygonal line, we use
//
// fn poly_line<A>(&self, coordinate_mode: A, drawable: u32, gc: u32, points: &[Point])
// -> Result<SequenceNumber, ConnectionError>
// where A: Into<u8>
//
// This function will draw the line between the first and the second points, then the line between
// the second and the third points, and so on.
//
// To draw a segment, or several segments, we use
//
// fn poly_segment(&self, drawable: u32, gc: u32, segments: &[Segment]) -> Result<SequenceNumber, ConnectionError>;
//
// The `xcb_segment_t` type is just a structure with four fields (the coordinates of the two points that define the segment):
//
// pub struct Segment {
// pub x1: i16,
// pub y1: i16,
// pub x2: i16,
// pub y2: i16,
// }
//
// To draw a rectangle, or several rectangles, we use
//
// fn poly_rectangle(&self, drawable: u32, gc: u32, rectangles: &[Rectangle]) -> Result<SequenceNumber, ConnectionError>;
//
// The `xcb_rectangle_t` type is just a structure with four fields (the coordinates of the top-left
// corner of the rectangle, and its width and height):
//
// pub struct Rectangle {
// pub x: i16,
// pub y: i16,
// pub width: u16,
// pub height: u16,
// }
//
// To draw an elliptical arc, or several elliptical arcs, we use
//
// fn poly_arc(&self, drawable: u32, gc: u32, arcs: &[Arc]) -> Result<SequenceNumber, ConnectionError>;
//
// The `xcb_arc_t` type is a structure with six fields:
//
// pub struct Arc {
// pub x: i16,
// pub y: i16,
// pub width: u16,
// pub height: u16,
// pub angle1: i16,
// pub angle2: i16,
// }
//
// Note: the angles are expressed in units of 1/64 of a degree, so to have an angle of 90 degrees,
// starting at 0,`angle1 = 0` and `angle2 = 90 << 6`. Positive angles indicate counterclockwise
// motion, while negative angles indicate clockwise motion.
//
// The corresponding function which fill inside the geometrical object are listed below, without
// further explanation, as they are used as the above functions.
//
// To Fill a polygon defined by the points given as arguments , we use
//
// fn fill_poly<A, B>(&self, drawable: u32, gc: u32, shape: A, coordinate_mode: B, points: &[Point]) -> Result<SequenceNumber, ConnectionError>
// where A: Into<u8>, B: Into<u8>
//
// The `shape` parameter specifies a shape that helps the server to improve performance. Available values are
//
// pub enum PolyShape {
// Complex,
// Nonconvex,
// Convex,
// }
//
// To fill one or several rectangles, we use
//
// fn poly_fill_rectangle(&self, drawable: u32, gc: u32, rectangles: &[Rectangle]) -> Result<SequenceNumber, ConnectionError>;
//
// To fill one or several arcs, we use
//
// fn poly_fill_arc(&self, drawable: u32, gc: u32, arcs: &[Arc]) -> Result<SequenceNumber, ConnectionError>;
//
// To illustrate these functions, here is an example that draws four points, a polygonal line, two
// segments, two rectangles and two arcs. Remark that we use events for the first time, as an
// introduction to the next section.
fn example6() -> Result<(), Box<dyn Error>> {
// geometric objects
let points = [
Point { x: 10, y: 10 },
Point { x: 10, y: 20 },
Point { x: 20, y: 10 },
Point { x: 20, y: 20 },
];
let polyline = [
Point { x: 50, y: 10 },
Point { x: 5, y: 20 }, // Rest of points are relative
Point { x: 25, y: -20 },
Point { x: 10, y: 10 },
];
let segments = [
Segment {
x1: 100,
y1: 10,
x2: 140,
y2: 30,
},
Segment {
x1: 110,
y1: 25,
x2: 130,
y2: 60,
},
];
let rectangles = [
Rectangle {
x: 10,
y: 50,
width: 40,
height: 20,
},
Rectangle {
x: 80,
y: 50,
width: 10,
height: 40,
},
];
let arcs = [
Arc {
x: 10,
y: 100,
width: 60,
height: 40,
angle1: 0,
angle2: 90 << 6,
},
Arc {
x: 90,
y: 100,
width: 55,
height: 40,
angle1: 0,
angle2: 270 << 6,
},
];
// Open the connection to the X server. Use the DISPLAY environment variable.
let (conn, screen_num) = x11rb::connect(None)?;
// Get the screen #screen_num
let screen = &conn.setup().roots[screen_num];
// Create black (foreground) graphic context
let win = screen.root;
let foreground = conn.generate_id()?;
let values = CreateGCAux::default()
.foreground(screen.black_pixel)
.graphics_exposures(0);
conn.create_gc(foreground, win, &values)?;
// Ask for our window's Id
let win = conn.generate_id()?;
// Create the window
let values = CreateWindowAux::default()
.background_pixel(screen.white_pixel)
.event_mask(EventMask::EXPOSURE);
conn.create_window(
COPY_DEPTH_FROM_PARENT, // depth
win, // window Id
screen.root, // parent window
0, // x
0, // y
150, // width
150, // height
10, // border_width
WindowClass::INPUT_OUTPUT, // class
screen.root_visual, // visual
&values,
)?;
// Map the window on the screen
conn.map_window(win)?;
// We flush the request
conn.flush()?;
loop {
let event = conn.wait_for_event()?;
if let Event::Expose(_) = event {
// We draw the points
conn.poly_point(CoordMode::ORIGIN, win, foreground, &points)?;
// We draw the polygonal line
conn.poly_line(CoordMode::PREVIOUS, win, foreground, &polyline)?;
// We draw the segments
conn.poly_segment(win, foreground, &segments)?;
// We draw the rectangles
conn.poly_rectangle(win, foreground, &rectangles)?;
// We draw the arcs
conn.poly_arc(win, foreground, &arcs)?;
// We flush the request
conn.flush()?;
} else {
// Unknown event type, ignore it
}
}
}
// X Events
// ========
//
// In an X program, everything is driven by events. Event painting on the screen is sometimes done
// as a response to an event (an `Expose` event). If part of a program's window that was hidden,
// gets exposed (e.g. the window was raised above other widows), the X server will send an "expose"
// event to let the program know it should repaint that part of the window. User input (key
// presses, mouse movement, etc) is also received as a set of events.
//
//
// Registering for event types using event masks
// ---------------------------------------------
//
// During the creation of a window, you should give it what kind of events it wishes to receive.
// Thus, you may register for various mouse (also called pointer) events, keyboard events, expose
// events, and so on. This is done for optimizing the server-to-client connection (i.e. why send a
// program (that might even be running at the other side of the globe) an event it is not
// interested in ?)
//
// In XCB, you use the "value_mask" and "value_list" data in the `xcb_create_window()` function to
// register for events. Here is how we register for `Expose` event when creating a window:
//
#[allow(unused)]
fn example_expose<C: Connection>(
conn: &C,
depth: u8,
screen: &Screen,
) -> Result<(), Box<dyn Error>> {
let values = CreateWindowAux::default().event_mask(EventMask::EXPOSURE);
let win = conn.generate_id()?;
conn.create_window(
depth,
win,
screen.root,
0,
0,
150,
150,
10,
WindowClass::INPUT_OUTPUT,
screen.root_visual,
&values,
)?;
Ok(())
}
// `XCB_EVENT_MASK_EXPOSURE` is a constant defined in the xcb_event_mask_t enumeration in the
// "xproto.h" header file.
//
// If we wanted to register for several event types, we can logically "or" them, as follows:
#[allow(unused)]
fn example_or<C: Connection>(conn: &C, depth: u8, screen: &Screen) -> Result<(), Box<dyn Error>> {
let values =
CreateWindowAux::default().event_mask(EventMask::EXPOSURE | EventMask::BUTTON_PRESS);
let win = conn.generate_id()?;
conn.create_window(
depth,
win,
screen.root,
0,
0,
150,
150,
10,
WindowClass::INPUT_OUTPUT,
screen.root_visual,
&values,
)?;
Ok(())
}
// This registers for `Expose` events as well as for mouse button presses inside the created
// window. You should note that a mask may represent several event sub-types.
//
// The values that a mask could take are given by the `xcb_cw_t` enumeration:
//
// pub enum CW {
// BackPixmap,
// BackPixel,
// BorderPixmap,
// BorderPixel,
// BitGravity,
// WinGravity,
// BackingStore,
// BackingPlanes,
// BackingPixel,
// OverrideRedirect,
// SaveUnder,
// EventMask,
// DontPropagate,
// Colormap,
// Cursor,
// }
//
//
// [This note only applies to xcb, not x11rb]
//
// Note: we must be careful when setting the values of the valwin parameter, as they have to follow
// the order the `xcb_cw_t` enumeration. Here is an example:
//
// [example removed since x11rb does not have this problem]
//
// If the window has already been created, we can use the `xcb_change_window_attributes()` function
// to set the events that the window will receive. The subsection Configuring a window shows its
// prototype. As an example, here is a piece of code that configures the window to receive the
// `Expose` and `ButtonPress` events:
#[allow(unused)]
fn example_change_event_mask<C: Connection>(conn: &C, win: Window) -> Result<(), Box<dyn Error>> {
let values = ChangeWindowAttributesAux::default()
.event_mask(EventMask::EXPOSURE | EventMask::BUTTON_PRESS);
conn.change_window_attributes(win, &values)?;
Ok(())
}
// Note: A common bug programmers have is adding code to handle new event types in their program,
// while forgetting to add the masks for these events in the creation of the window. Such a
// programmer would then sit there for hours debugging their program, wondering "Why doesn't my
// program notice that I released the button?", only to find that they registered for button press
// events but not for button release events.
//
//
// Receiving events: writing the events loop
// -----------------------------------------
//
// After we have registered for the event types we are interested in, we need to enter a loop of
// receiving events and handling them. There are two ways to receive events: a blocking way and a
// non-blocking way:
//
// * `xcb_wait_for_event (xcb_connection_t *c)` is the blocking way. It waits (so blocks...)
// until an event is queued in the X server. Then it retrieves it into a newly allocated
// structure (it dequeues it from the queue) and returns it. This structure has to be freed. The
// function returns `NULL` if an error occurs.
//
// * `xcb_poll_for_event (xcb_connection_t *c, int *error)` is the non-blocking way. It looks at
// the event queue and returns (and dequeues too) an existing event into a newly allocated
// structure. This structure has to be freed. It returns `NULL` if there is no event. If an
// error occurs, the parameter `error` will be filled with the error status.
//
// There are various ways to write such a loop. We present two ways to write such a loop, with the
// two functions above. The first one uses `xcb_wait_for_event_t`, which is similar to an event
// Xlib loop using only `XNextEvent`:
#[allow(unused)]
fn example_wait_for_event<C: Connection>(conn: &C) -> Result<(), Box<dyn Error>> {
loop {
let event = conn.wait_for_event()?;
match event {
Event::Expose(_event) => {
// ....
}
Event::ButtonPress(_event) => {
// ....
}
_ => {
// Unknown event type, ignore it
}
}
}
Ok(())
}
// You will certainly want to use `xcb_poll_for_event(xcb_connection_t *c, int *error)` if, in
// Xlib, you use `XPending` or `XCheckMaskEvent`:
//
// while (XPending (display)) {
// XEvent ev;
//
// XNextEvent(d, &ev);
//
// /* Manage your event */
// }
//
// Such a loop in XCB looks like:
//
// xcb_generic_event_t *ev;
//
// while ((ev = xcb_poll_for_event (conn, 0))) {
// /* Manage your event */
// }
//
// The events are managed in the same way as with `xcb_wait_for_event_t`. Obviously, we will need