Expand description
Basic Usage of breadx
Let’s create a simple application using breadx
. I’ll explain each of
the pieces along the way.
Application Setup
First, let’s just create a simple application that depends on breadx
.
TODO: application setup once breadx is on crates.io
You should see a main.rs
file. This is the entry point for your
application. It’s a good place to put your main logic.
Connect to the Server
The first thing you do is connect to the X11 server. You can use the
DisplayConnection::connect()
method, which connects to the server.
use breadx::{display::DisplayConnection, Result};
fn main() -> Result<()> {
let mut connection = DisplayConnection::connect(None)?;
Ok(())
}
For those unfamiliar with Rust, I’ll go into each part of the program:
use breadx::{...}
imports theDisplayConnection
andResult
types from thebreadx
crate.fn main() -> Result<()>
defines the main function, which is the entry point for the application. It returnsResult
, which is a sum type consisting of the success state (Ok
) and the failure state (Err
).let mut connection = ...
defines theconnection
variable, makes it a mutable variable, and initializes it to the result of the following expression.DisplayConnection::connect(...)
creates a newDisplayConnection
that is connected to the currently running X11 server. It opens a TCP or Unix connection, preforms the initial setup handshake, and then returns a type that can be used to communicate with the X11 server.None
is a version of another sum type,Option
. In this case, it is a parameter passed toDisplayConnection::connect()
. The first parameter of the function indicates the display name to be used, which determines what X11 server to connect to. In this case,None
indicates to use the display name contained in theDISPLAY
environment variable.- The
?
at the end is syntactic sugar.connect()
returns aResult
, and if it isErr
, the?
operator will causemain()
to return the error early. Ok(())
at the end is used to tell the function to return the success status, since we made it through all right. The()
is the empty tuple, which is often used in Rust to express that we have no data.
To summarize, all this does is open the connection to the X11 server, and then immediately closes it. That won’t do. How about we do something with it?
Create a Window
Let’s send our first request. There’s pretty much nothing better we can do than create a window at this point.
There are a few things we have to take care of first. We need to determine the “parent” of the window, or the window that it’s inside of. We’ll go more in depth as to what exactly a window is earlier, but you should know that the actual screen can be used as a parent.
use breadx::{display::DisplayConnection, prelude::*, protocol::xproto, Result};
fn main() -> Result<()> {
let mut connection = DisplayConnection::connect(None)?;
let parent = connection.default_screen().root;
Ok(())
}
Some things to take note of:
- Note that we are now importing the prelude, which contains
certain traits to enhance the functionality of the
DisplayConnection
type. - We also import the
xproto
module, which contains some useful types that we’ll use later. - We set parent to the value of
root
ofconnection.default_screen()
, which returns the screen we’re assigned to use by default.
Now that everything is in place, we can create the window.
use breadx::{display::DisplayConnection, prelude::*, protocol::xproto, Result};
fn main() -> Result<()> {
// snip existing code
let wid = connection.generate_xid()?;
let window = connection.create_window(
0, // depth
wid,
parent,
0, // x
0, // y
600, // width
400, // height
0, // border width
xproto::WindowClass::COPY_FROM_PARENT,
0, // visual class
xproto::CreateWindowAux::new(),
)?;
Ok(())
}
First, using the generate_xid()
function, we generate a unique XID. This is
a value that the client and server will share to identify resources. XIDs
aren’t just unique to the process, they’re unique across the entire system. This
means that you can use XIDs that originate in other processes, and they will
still be unique.
Then, we call the create_window()
function. This function takes the following
parameters:
- The depth of the window. This is the number of bits per pixel. Setting it
to
0
means that the window will use the depth of its parent. - The window ID. This is the value that the client and server will share to identify the window.
- The parent window. This is the window that the new window will be inside of.
Remember that
parent
refers to the screen. - The x and y coordinates of the window.
- The width and height of the window.
- The border width. This is the width of the border around the window. However, you don’t actually have a border around a top-level window, so this value doesn’t really matter.
- The window class, which determines whether the window is an “input only” window or an “input-output” window. The distinction isn’t important for this example, so we just copy it from the parent.
- The visual class defines certain properties of the window, like the range of colors
that are available.
0
once again indicates that we need to inherit from the parent. CreateWindowAux
is used to contain properties that are not strictly necessary for creating a window, but are still useful. In this case, we don’t need to specify any properties, so we just use the default constructor.
If you run this code, you’ll notice that nothing happens. You see, while the window is created, it’s not actually visible. We need to tell the server to actually show the window.