├── .github └── workflows │ └── CI.yml ├── .gitignore ├── .gitmodules ├── ARCHITECTURE.md ├── Cargo.toml ├── Justfile ├── LICENSE ├── README.md ├── breadx-generator ├── Cargo.toml ├── README.md └── src │ ├── items.rs │ └── main.rs ├── breadx ├── Cargo.toml ├── examples │ ├── async_std_async.rs │ ├── basic.rs │ ├── just_emit_setup.rs │ ├── tokio_async.rs │ ├── util │ │ └── cancel.rs │ └── xclock.rs └── src │ ├── automatically_generated.rs │ ├── connection │ ├── buffered.rs │ ├── mod.rs │ ├── sendmsg.rs │ ├── std_wrapper.rs │ └── test.rs │ ├── display │ ├── basic.rs │ ├── cell.rs │ ├── cookie.rs │ ├── ext.rs │ ├── extension_map.rs │ ├── mod.rs │ ├── poison.rs │ ├── prefetch.rs │ ├── raw_request.rs │ ├── sans_io.rs │ ├── sync.rs │ └── tests │ │ ├── mod.rs │ │ ├── sequencing.rs │ │ └── setup.rs │ ├── error.rs │ ├── futures │ ├── check_for_error.rs │ ├── checked.rs │ ├── flush.rs │ ├── generate_xid.rs │ ├── mod.rs │ ├── send_request.rs │ ├── send_request_raw.rs │ ├── synchronize.rs │ ├── try_with.rs │ ├── wait_for_event.rs │ ├── wait_for_reply.rs │ └── wait_for_reply_raw.rs │ ├── gates.rs │ ├── lib.rs │ ├── mutex.rs │ ├── mutlireply.rs │ ├── name │ ├── mod.rs │ └── nb_connect.rs │ ├── rt_support │ ├── async_std_support.rs │ ├── mod.rs │ └── tokio_support.rs │ ├── tutorials │ ├── basics.rs │ ├── introduction.rs │ └── mod.rs │ ├── utils │ ├── mod.rs │ ├── test.rs │ └── unblock.rs │ └── void.rs ├── deny.toml └── xcb_parser ├── Cargo.toml ├── README.md ├── src ├── docs.rs ├── eventstruct.rs ├── expression.rs ├── fields.rs ├── lib.rs ├── toplevel.rs ├── xenum.rs └── xstruct.rs └── tests └── parse_xproto_test.rs /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | # Copyright John Nunley, 2022. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE or copy at 4 | # https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | name: CI 7 | 8 | on: 9 | push: 10 | branches: 11 | - master 12 | pull_request: 13 | 14 | env: 15 | BREADX_EXAMPLE_TIMEOUT: 1 16 | RUSTFLAGS: -Dwarnings 17 | clippy_version: 1.60.0 18 | 19 | jobs: 20 | rustfmt: 21 | name: rustfmt 22 | runs-on: ubuntu-latest 23 | strategy: 24 | matrix: 25 | include: 26 | - rust: stable 27 | 28 | steps: 29 | - uses: actions/checkout@v2 30 | - name: Install Rust 31 | uses: actions-rs/toolchain@v1 32 | with: 33 | toolchain: ${{ matrix.rust }} 34 | profile: minimal 35 | override: true 36 | components: rustfmt 37 | - uses: Swatinem/rust-cache@v1 38 | - name: Run rustfmt 39 | run: | 40 | if ! rustfmt --check --edition 2021 $(git ls-files '*.rs'); then 41 | printf "Please fix rustfmt errors.\n" >&2 42 | exit 1 43 | fi 44 | clippy: 45 | name: clippy 46 | runs-on: ubuntu-latest 47 | 48 | steps: 49 | - uses: actions/checkout@v2 50 | - name: Install Rust 51 | uses: actions-rs/toolchain@v1 52 | with: 53 | toolchain: ${{ env.clippy_version }} 54 | override: true 55 | components: clippy 56 | - uses: Swatinem/rust-cache@v1 57 | - name: Run Clippy 58 | uses: actions-rs/cargo@v1 59 | with: 60 | command: clippy 61 | args: --all --tests --all-features 62 | 63 | # copy-pasted from async-io 64 | cross: 65 | runs-on: ${{ matrix.os }} 66 | strategy: 67 | fail-fast: false 68 | matrix: 69 | os: [ubuntu-latest, macos-latest] 70 | steps: 71 | - uses: actions/checkout@v3 72 | with: 73 | submodules: true 74 | - name: Install Rust 75 | run: rustup update stable 76 | - name: Install cross 77 | uses: taiki-e/install-action@cross 78 | - name: Android 79 | if: startsWith(matrix.os, 'ubuntu') 80 | run: cross test --target arm-linux-androideabi 81 | - name: NetBSD 82 | if: startsWith(matrix.os, 'ubuntu') 83 | run: cross build --target x86_64-unknown-netbsd 84 | - name: iOS 85 | if: startsWith(matrix.os, 'macos') 86 | run: cross build --target aarch64-apple-ios 87 | 88 | test: 89 | name: run tests 90 | runs-on: ${{ matrix.os }} 91 | strategy: 92 | matrix: 93 | os: 94 | - windows-latest 95 | - ubuntu-latest 96 | - macos-latest 97 | toolchain: 98 | - stable 99 | include: 100 | - os: ubuntu-latest 101 | toolchain: beta 102 | - os: ubuntu-latest 103 | toolchain: nightly 104 | - os: ubuntu-latest 105 | toolchain: 1.49.0 # msrv 106 | 107 | steps: 108 | - uses: actions/checkout@v2 109 | with: 110 | submodules: true 111 | - name: Install Rust 112 | uses: actions-rs/toolchain@v1 113 | with: 114 | toolchain: ${{ matrix.toolchain }} 115 | override: true 116 | - uses: Swatinem/rust-cache@v1 117 | 118 | # run tests for all crates 119 | - name: Default Features Test 120 | run: cargo test 121 | 122 | # run tests with no default features 123 | - name: No Default Features Test 124 | run: cargo test --no-default-features --tests 125 | working-directory: breadx 126 | 127 | - name: No Default Features Test (All Extensions) 128 | run: cargo test --no-default-features --features all-extensions --tests 129 | working-directory: breadx 130 | 131 | # run tests with all features 132 | - name: All Features Test 133 | run: cargo test --all-features 134 | working-directory: breadx 135 | 136 | # run tests for just async 137 | - name: Async Test 138 | run: cargo test --features async 139 | working-directory: breadx 140 | 141 | - name: Async Test (All Extensions) 142 | run: cargo test --features async all-extensions 143 | working-directory: breadx 144 | 145 | # run pl tests 146 | - name: Parking Lot Test 147 | run: cargo test --features pl all-extensions 148 | working-directory: breadx 149 | 150 | # run all examples 151 | # took this from x11rb's ci 152 | - name: Run Examples (Linux) 153 | if: startsWith(matrix.os, 'ubuntu') 154 | run: | 155 | cd breadx 156 | for example in examples/*.rs; do 157 | example=${example/examples\//} 158 | example=${example/.rs/} 159 | xvfb-run -a cargo run --example "$example" --all-features 160 | done 161 | 162 | # run examples on windows 163 | - uses: cygwin/cygwin-install-action@master 164 | if: startsWith(matrix.os, 'windows') 165 | with: 166 | packages: xorg-server-extra 167 | 168 | - name: Run Examples (Windows) 169 | if: startsWith(matrix.os, 'windows') 170 | shell: powershell 171 | env: 172 | DISPLAY: 127.0.0.1:0 173 | run: | 174 | $xvfb = Start-Process -PassThru -FilePath C:\cygwin\bin\Xvfb.exe -ArgumentList "-listen tcp :0" 175 | Get-ChildItem breadx/examples | 176 | Where {$_.extension -eq ".rs"} | 177 | Foreach-Object { 178 | 179 | cargo run --example $($_.BaseName) --all-features 180 | 181 | if ($LASTEXITCODE -ne 0) { 182 | throw "Example $($_.BaseName) failed" 183 | } 184 | } 185 | Stop-Process -Id $xvfb.Id 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | vistest 4 | vistest.c 5 | 6 | 7 | # Added by cargo 8 | # 9 | # already existing elements were commented out 10 | 11 | #/target 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "xcbproto"] 2 | path = xcbproto 3 | url = https://gitlab.freedesktop.org/xorg/proto/xcbproto.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright John Nunley, 2022. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE or copy at 4 | # https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | [workspace] 7 | members = ["breadx", "xcb_parser", "breadx-generator"] -------------------------------------------------------------------------------- /Justfile: -------------------------------------------------------------------------------- 1 | # Copyright John Nunley, 2022. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE or copy at 4 | # https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | protocol: 7 | cargo run --manifest-path breadx-generator/Cargo.toml \ 8 | xcbproto/src \ 9 | breadx/src/automatically_generated.rs -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Passively Maintained 2 | 3 | The original author will spend their effort helping to maintain [`x11rb`](https://crates.io/crates/x11rb) instead of this crate. Bugs will still be fixed; however new features will not be added. 4 | 5 | # breadx 6 | 7 | [![crates.io][crates-badge]][crates-url] 8 | [![Documentation][docs-badge]][docs-url] 9 | [![Build Status][build-badge]][build-url] 10 | 11 | [crates-badge]: https://img.shields.io/crates/v/breadx 12 | [crates-url]: https://crates.io/crates/breadx 13 | [docs-badge]: https://img.shields.io/docsrs/breadx 14 | [docs-url]: https://docs.rs/breadx 15 | [build-badge]: https://img.shields.io/github/workflow/status/bread-graphics/breadx/CI 16 | [build-url]: https://github.com/bread-graphics/breadx/actions?query=workflow%3ACI+branch%3Amaster 17 | 18 | An implementation of the X Window System Protocol in Rust, with an emphasis on comprehensability and usability. 19 | 20 | ## Advantages 21 | 22 | * `breadx` is simple and direct. There is very little between you and the protocol. 23 | * Without the `sync_display` feature, `breadx` uses no synchronization primitives, eliminating one of the primary causes of deadlocking. 24 | * `breadx` is written in 100% safe code. 25 | * `breadx` is able to be `no_std` and be used without the standard library. 26 | * Runtime-independent `async` support may be enabled using the `async` feature. 27 | * API is designed to be able to be used everywhere. 28 | 29 | ## Disadvantages 30 | 31 | * On its own, `breadx` is not compatible with libraries that use `lixcb` or Xlib. Consider using [`whitebreadx`](https://github.com/bread-graphics/whitebreadx) if this is important. 32 | * `breadx` provides no utility or helper functions beyond the requests that go on the wire. 33 | 34 | ## Tutorials/Examples 35 | 36 | For tutorials and examples of breadx's usage, check out [the docs](https://docs.rs/breadx). 37 | 38 | ## MSRV 39 | 40 | The current MSRV for `breadx` with all features enabled is 1.49.0. This is largely tied to `tokio`'s MSRV, and with fewer features enabled `breadx` 41 | is likely to work on Rust versions as low as 1.46.0 (although this is never guaranteed). The MSRV will not be changed without either a 42 | major or minor version bump. 43 | 44 | ## License 45 | 46 | This package is distributed under the Boost Software License Version 1.0. 47 | Consult the [LICENSE](./LICENSE) file or consult the [web mirror] for 48 | more information. 49 | 50 | [web mirror]: https://www.boost.org/LICENSE_1_0.txt 51 | -------------------------------------------------------------------------------- /breadx-generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright John Nunley, 2022. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE or copy at 4 | # https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | [package] 7 | name = "breadx-generator" 8 | version = "0.1.0" 9 | edition = "2018" 10 | license = "BSL-1.0" 11 | 12 | [dependencies] 13 | anyhow = "1.0.57" 14 | heck = "0.4.0" 15 | tracing = "0.1.34" 16 | tracing-subscriber = "0.3.11" 17 | xcb_parser = { path = "../xcb_parser" } -------------------------------------------------------------------------------- /breadx-generator/README.md: -------------------------------------------------------------------------------- 1 | # `breadx-generator` 2 | 3 | This crate maps the primitives generated by `xcb-parser` into helper 4 | functions used for calling requests. 5 | 6 | ## License 7 | 8 | This package is distributed under the Boost Software License Version 1.0. 9 | Consult the [LICENSE](../LICENSE) file or consult the [web mirror] for 10 | more information. 11 | 12 | [web mirror]: https://www.boost.org/LICENSE_1_0.txt -------------------------------------------------------------------------------- /breadx-generator/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use anyhow::Result; 7 | use std::{ 8 | env, 9 | fmt::Write as _, 10 | fs, 11 | io::{BufWriter, Write}, 12 | path::Path, 13 | }; 14 | use xcb_parser::Header; 15 | 16 | mod items; 17 | 18 | fn main() -> Result<()> { 19 | // setup tracing 20 | tracing_subscriber::fmt::init(); 21 | 22 | // read in files 23 | let mut args = env::args_os(); 24 | let root_path = args.by_ref().nth(1).map_or_else( 25 | || Path::new(env!("CARGO_MANIFEST_DIR")).join("../xcbproto/src"), 26 | |arg| arg.into(), 27 | ); 28 | 29 | tracing::info!("Reading files from {:?}", root_path); 30 | 31 | // read all filenames into a Vec 32 | let mut filepaths = fs::read_dir(root_path)? 33 | .map(|entry| entry.map(|entry| entry.path())) 34 | .inspect(|entry| { 35 | if let Ok(entry) = entry { 36 | tracing::debug!("Parsing file {:?}", entry); 37 | } 38 | }) 39 | .collect::, _>>()?; 40 | 41 | // sort filenames deterministically 42 | filepaths.sort(); 43 | 44 | // begin writing to the output file 45 | let output_file = args.next().map_or_else( 46 | || Path::new(env!("CARGO_MANIFEST_DIR")).join("../breadx/src/automatically_generated.rs"), 47 | |arg| arg.into(), 48 | ); 49 | 50 | tracing::info!("Writing to {:?}", output_file); 51 | 52 | let mut output = BufWriter::new(fs::File::create(output_file)?); 53 | 54 | // write the header 55 | output.write_all( 56 | r#" 57 | // This file is automatically generated by the `breadx-generator` crate. 58 | // Do not edit this file directly. 59 | 60 | // Copyright John Nunley, 2022. 61 | // Distributed under the Boost Software License, Version 1.0. 62 | // (See accompanying file LICENSE or copy at 63 | // https://www.boost.org/LICENSE_1_0.txt) 64 | 65 | //! Contains automatically generated items. 66 | 67 | #![rustfmt::skip] 68 | 69 | use crate::{Result, display::{Cookie, Display, DisplayExt}}; 70 | use alloc::borrow::Cow; 71 | #[allow(unused_imports)] 72 | use alloc::vec::Vec; 73 | use core::borrow::Borrow; 74 | use __private::Sealed; 75 | 76 | cfg_async! { 77 | use crate::{display::{AsyncDisplay, AsyncDisplayExt}, futures}; 78 | use __private::Sealed2; 79 | } 80 | "# 81 | .as_bytes(), 82 | )?; 83 | 84 | // generate list to contain header info 85 | let mut headers = Vec::new(); 86 | 87 | // generate items for each request 88 | let (sync_items, async_items) = filepaths 89 | .into_iter() 90 | .map(|path| process_file(&path, &mut headers)) 91 | .filter_map(|res| match res { 92 | Ok(res) => Some(res), 93 | Err(err) => { 94 | tracing::error!("{}", err); 95 | None 96 | } 97 | }) 98 | .unzip::<_, _, Vec, Vec>(); 99 | 100 | // begin writing the impl blocks 101 | output.write_all( 102 | r#" 103 | pub trait DisplayFunctionsExt : Display + Sealed {"# 104 | .as_bytes(), 105 | )?; 106 | 107 | for sync_item in sync_items { 108 | // indent it by 4 spaces 109 | let sync_item = sync_item.replace('\n', "\n "); 110 | write!(output, "\n {}", sync_item)?; 111 | } 112 | 113 | output.write_all( 114 | r#" 115 | } 116 | 117 | #[cfg(feature = "async")] 118 | pub trait AsyncDisplayFunctionsExt : AsyncDisplay + Sealed2 {"# 119 | .as_bytes(), 120 | )?; 121 | 122 | for async_item in async_items { 123 | // indent it by 4 spaces 124 | let async_item = async_item.replace('\n', "\n "); 125 | write!(output, "\n {}", async_item)?; 126 | } 127 | 128 | // finish it up 129 | output.write_all( 130 | r#"} 131 | 132 | impl DisplayFunctionsExt for D {} 133 | 134 | #[cfg(feature = "async")] 135 | impl AsyncDisplayFunctionsExt for D {} 136 | 137 | mod __private { 138 | use crate::display::Display; 139 | 140 | pub trait Sealed { 141 | fn __sealed_trait_marker() {} 142 | } 143 | 144 | impl Sealed for D {} 145 | 146 | cfg_async! { 147 | use crate::display::AsyncDisplay; 148 | 149 | pub trait Sealed2 { 150 | fn __sealed_trait_marker() {} 151 | } 152 | 153 | impl Sealed2 for D {} 154 | } 155 | } 156 | "# 157 | .as_bytes(), 158 | )?; 159 | 160 | // add types module 161 | output.write_all(generate_types(&headers).as_bytes())?; 162 | 163 | Ok(()) 164 | } 165 | 166 | fn process_file(filepath: &Path, headers: &mut Vec
) -> Result<(String, String)> { 167 | // open the file using xcb_parser 168 | let parser = xcb_parser::read_xcb_from_file(filepath)?; 169 | let header = parser.header().clone(); 170 | headers.push(header.clone()); 171 | 172 | // calculate all of the items 173 | let item_pairs = parser 174 | .filter_map(|item| { 175 | item.map(|item| { 176 | // create a tuple of the sync item and async item 177 | let sync_item = items::generate_item(&header, &item, items::Mode::Sync); 178 | let async_item = items::generate_item(&header, &item, items::Mode::Async); 179 | 180 | sync_item.map(move |sync_item| (sync_item, async_item.unwrap_or_else(|| "".into()))) 181 | }) 182 | .transpose() 183 | }) 184 | .collect::, _>>()?; 185 | 186 | Ok(item_pairs.into_iter().unzip()) 187 | } 188 | 189 | fn generate_types(headers: &[Header]) -> String { 190 | // list of header imports 191 | let mut base = r#" 192 | 193 | #[allow(dead_code, unused_imports)] 194 | mod types { 195 | pub(crate) type Card8 = u8; 196 | pub(crate) type Card16 = u16; 197 | pub(crate) type Card32 = u32; 198 | pub(crate) type Card64 = u64; 199 | pub(crate) type Bool = bool; 200 | pub(crate) type Char = u8; 201 | pub(crate) type Byte = u8; 202 | pub(crate) type Int8 = i8; 203 | pub(crate) type Int16 = i16; 204 | pub(crate) type Int32 = i32; 205 | pub(crate) type Float = f32; 206 | pub(crate) type Double = f64; 207 | pub(crate) type Void = u8; 208 | 209 | pub(crate) use crate::Fd; 210 | "# 211 | .to_string(); 212 | 213 | // append header imports to this mess 214 | for header in headers { 215 | if !always_available(&header.header) { 216 | writeln!(base, r#" #[cfg(feature = "{}")]"#, header.header).unwrap(); 217 | } 218 | writeln!( 219 | base, 220 | " pub(crate) use crate::protocol::{}::{{self, *}};", 221 | header.header 222 | ) 223 | .unwrap(); 224 | } 225 | 226 | // finish it up 227 | base.push('}'); 228 | base 229 | } 230 | 231 | pub fn always_available(name: &str) -> bool { 232 | matches!(name, "xproto" | "bigreq" | "xc_misc" | "ge") 233 | } 234 | -------------------------------------------------------------------------------- /breadx/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright John Nunley, 2022. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE or copy at 4 | # https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | [package] 7 | name = "breadx" 8 | version = "3.1.0" 9 | authors = ["notgull "] 10 | edition = "2018" 11 | description = "Pure-Rust X11 connection implementation with a focus on adaptability" 12 | license = "BSL-1.0" 13 | homepage = "https://github.com/bread-graphics/breadx#readme" 14 | repository = "https://github.com/bread-graphics/breadx" 15 | documentation = "https://docs.rs/breadx" 16 | 17 | [dependencies] 18 | advance = { version = "0.1.0", optional = true } 19 | ahash = { version = "0.7.6", default-features = false, features = ["compile-time-rng"] } 20 | async-io = { version = "1", optional = true } 21 | blocking = { version = "1", optional = true } 22 | bytemuck = { version = "1.9.1" } 23 | cfg-if = "1" 24 | concurrent-queue = { version = "1.2.2", optional = true } 25 | fionread = { version = "0.1.1", optional = true } 26 | futures-util = { version = "0.3.21", default-features = false, features = ["alloc"], optional = true } 27 | gethostname = { version = "0.2.3", optional = true } 28 | hashbrown = { version = "0.11.2", default-features = false } 29 | nix = { version = "0.24.1", optional = true } 30 | parking_lot = { version = "0.12.0", optional = true } 31 | socket2 = { version = "0.4", features = ["all"], optional = true } 32 | tokio = { version = "1.19", features = ["net", "rt"], optional = true } 33 | tracing = { version = "0.1.34", default-features = false } 34 | x11rb-protocol = { version = "~0.10", default-features = false } 35 | 36 | [dev-dependencies] 37 | async-std = { version = "1.11.0", features = ["attributes"] } 38 | chrono = "0.4.20" 39 | euclid = "0.22.7" 40 | ordered-float = "3.0.0" 41 | spin_on = "0.1.1" 42 | tokio = { version = "1.19", features = ["macros", "io-std", "io-util", "sync", "time", "tracing"] } 43 | tokio-stream = "0.1.9" 44 | tracing-subscriber = "0.3.15" 45 | uds_windows = "1.0.2" 46 | 47 | [features] 48 | default = ["std"] 49 | std = ["advance", "ahash/std", "fionread", "gethostname", "nix", "socket2", "x11rb-protocol/std", "x11rb-protocol/resource_manager"] 50 | async = ["futures-util", "std"] 51 | async-std-support = ["async", "async-io", "blocking"] 52 | tokio-support = ["async", "tokio"] 53 | 54 | sync_display = ["concurrent-queue", "std"] 55 | pl = ["sync_display", "parking_lot"] 56 | 57 | composite = ["xfixes", "x11rb-protocol/composite"] 58 | damage = ["xfixes", "x11rb-protocol/damage"] 59 | dpms = ["x11rb-protocol/dpms"] 60 | dri2 = ["x11rb-protocol/dri2"] 61 | dri3 = ["x11rb-protocol/dri3"] 62 | glx = ["x11rb-protocol/glx"] 63 | present = ["randr", "xfixes", "sync", "x11rb-protocol/present"] 64 | randr = ["render", "x11rb-protocol/randr"] 65 | record = ["x11rb-protocol/record"] 66 | render = ["x11rb-protocol/render"] 67 | res = ["x11rb-protocol/res"] 68 | screensaver = ["x11rb-protocol/screensaver"] 69 | shape = ["x11rb-protocol/shape"] 70 | shm = ["x11rb-protocol/shm"] 71 | sync = ["x11rb-protocol/sync"] 72 | xevie = ["x11rb-protocol/xevie"] 73 | xf86dri = ["x11rb-protocol/xf86dri"] 74 | xf86vidmode = ["x11rb-protocol/xf86vidmode"] 75 | xfixes = ["render", "shape", "x11rb-protocol/xfixes"] 76 | xinerama = ["x11rb-protocol/xinerama"] 77 | xinput = ["xfixes", "x11rb-protocol/xinput"] 78 | xkb = ["x11rb-protocol/xkb"] 79 | xprint = ["x11rb-protocol/xprint"] 80 | xselinux = ["x11rb-protocol/xselinux"] 81 | xtest = ["x11rb-protocol/xtest"] 82 | xv = ["shm", "x11rb-protocol/xv"] 83 | xvmc = ["xv", "x11rb-protocol/xvmc"] 84 | 85 | all-extensions = [ 86 | "composite", 87 | "damage", 88 | "dpms", 89 | "dri2", 90 | "dri3", 91 | "glx", 92 | "present", 93 | "randr", 94 | "record", 95 | "render", 96 | "res", 97 | "screensaver", 98 | "shape", 99 | "shm", 100 | "sync", 101 | "xevie", 102 | "xf86dri", 103 | "xf86vidmode", 104 | "xfixes", 105 | "xinerama", 106 | "xinput", 107 | "xkb", 108 | "xprint", 109 | "xselinux", 110 | "xtest", 111 | "xv", 112 | "xvmc", 113 | ] 114 | 115 | [package.metadata.docs.rs] 116 | all-features = true 117 | -------------------------------------------------------------------------------- /breadx/examples/async_std_async.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #[cfg(feature = "async-std-support")] 7 | #[path = "util/cancel.rs"] 8 | mod cancel; 9 | 10 | #[cfg(feature = "async-std-support")] 11 | use breadx::Result; 12 | 13 | #[cfg(feature = "async-std-support")] 14 | mod inner { 15 | use async_std::io::prelude::*; 16 | use breadx::{ 17 | prelude::*, 18 | protocol::{xproto, Event}, 19 | rt_support::async_std_support, 20 | Result, 21 | }; 22 | 23 | pub async fn real_main() -> Result<()> { 24 | super::cancel::spawn_kill_thread(); 25 | 26 | // almost identical to the tokio example, see that for comments 27 | let mut connection = async_std_support::connect(None).await?; 28 | 29 | // the events our windows receives. 30 | let events = xproto::EventMask::EXPOSURE | xproto::EventMask::BUTTON_PRESS; 31 | 32 | // the background color 33 | let background = connection.default_screen().white_pixel; 34 | 35 | // create the new window 36 | let parent = connection.default_screen().root; 37 | let wid = connection.generate_xid().await?; 38 | 39 | connection 40 | .create_window_checked( 41 | 0, 42 | wid, 43 | parent, 44 | 0, 45 | 0, 46 | 600, 47 | 400, 48 | 0, 49 | xproto::WindowClass::COPY_FROM_PARENT, 50 | 0, 51 | xproto::CreateWindowAux::new() 52 | .event_mask(events) 53 | .background_pixel(background), 54 | ) 55 | .await?; 56 | 57 | // map to screen and set title 58 | connection.map_window_checked(wid).await?; 59 | let title = "Hello from async-std!"; 60 | connection 61 | .change_property_checked( 62 | xproto::PropMode::REPLACE, 63 | wid, 64 | xproto::AtomEnum::WM_NAME.into(), 65 | xproto::AtomEnum::STRING.into(), 66 | 8, 67 | title.len() as u32, 68 | title, 69 | ) 70 | .await?; 71 | 72 | // set up a GC for drawing 73 | let gc = connection.generate_xid().await?; 74 | connection 75 | .create_gc_checked( 76 | gc, 77 | wid, 78 | xproto::CreateGCAux::new() 79 | .foreground(connection.default_screen().black_pixel) 80 | .graphics_exposures(0) 81 | .line_width(10), 82 | ) 83 | .await?; 84 | 85 | // create some colors 86 | let cmap = connection.default_screen().default_colormap; 87 | let red_color = connection.alloc_color(cmap, 0xffff, 0, 0).await?; 88 | let green_color = connection.alloc_color(cmap, 0, 0xffff, 0).await?; 89 | let blue_color = connection.alloc_color(cmap, 0, 0, 0xffff).await?; 90 | 91 | connection.flush().await?; 92 | 93 | // resolve the colors 94 | let red_pixel = connection.wait_for_reply(red_color).await?.pixel; 95 | let green_pixel = connection.wait_for_reply(green_color).await?.pixel; 96 | let blue_pixel = connection.wait_for_reply(blue_color).await?.pixel; 97 | 98 | // setup exit strategy 99 | let wm_protocols = connection.intern_atom(false, "WM_PROTOCOLS").await?; 100 | let wm_delete_window = connection.intern_atom(false, "WM_DELETE_WINDOW").await?; 101 | connection.flush().await?; 102 | let wm_protocols = connection.wait_for_reply(wm_protocols).await?.atom; 103 | let wm_delete_window = connection.wait_for_reply(wm_delete_window).await?.atom; 104 | 105 | connection 106 | .change_property_checked( 107 | xproto::PropMode::REPLACE, 108 | wid, 109 | wm_protocols, 110 | xproto::AtomEnum::ATOM.into(), 111 | 32, 112 | 1, 113 | &wm_delete_window, 114 | ) 115 | .await?; 116 | 117 | super::cancel::spawn_close_thread(wid); 118 | 119 | // primary event loop 120 | loop { 121 | let event = connection.wait_for_event().await?; 122 | 123 | match event { 124 | Event::Expose(_) => { 125 | // begin the repaint 126 | 127 | // draw a red "X" 128 | connection 129 | .change_gc(gc, xproto::ChangeGCAux::new().foreground(red_pixel)) 130 | .await?; 131 | connection 132 | .poly_segment( 133 | wid, 134 | gc, 135 | &[ 136 | xproto::Segment { 137 | x1: 10, 138 | y1: 10, 139 | x2: 150, 140 | y2: 150, 141 | }, 142 | xproto::Segment { 143 | x1: 150, 144 | y1: 10, 145 | x2: 10, 146 | y2: 150, 147 | }, 148 | ], 149 | ) 150 | .await?; 151 | 152 | // draw a green circle 153 | connection 154 | .change_gc(gc, xproto::ChangeGCAux::new().foreground(green_pixel)) 155 | .await?; 156 | connection 157 | .poly_fill_arc( 158 | wid, 159 | gc, 160 | &[xproto::Arc { 161 | x: 200, 162 | y: 10, 163 | width: 150, 164 | height: 150, 165 | angle1: 0, 166 | angle2: 360 * 64, 167 | }], 168 | ) 169 | .await?; 170 | 171 | // draw a blue semicircle 172 | connection 173 | .change_gc(gc, xproto::ChangeGCAux::new().foreground(blue_pixel)) 174 | .await?; 175 | connection 176 | .poly_fill_arc( 177 | wid, 178 | gc, 179 | &[xproto::Arc { 180 | x: 200, 181 | y: 10, 182 | width: 150, 183 | height: 150, 184 | angle1: 0, 185 | angle2: 270 * 64, 186 | }], 187 | ) 188 | .await?; 189 | 190 | // draw the black outline of a circle 191 | connection 192 | .change_gc( 193 | gc, 194 | xproto::ChangeGCAux::new() 195 | .foreground(connection.default_screen().black_pixel), 196 | ) 197 | .await?; 198 | connection 199 | .poly_arc( 200 | wid, 201 | gc, 202 | &[xproto::Arc { 203 | x: 200, 204 | y: 10, 205 | width: 150, 206 | height: 150, 207 | angle1: 0, 208 | angle2: 360 * 64, 209 | }], 210 | ) 211 | .await?; 212 | 213 | // end the repaint 214 | connection.flush().await?; 215 | } 216 | Event::ButtonPress(bp) => { 217 | // indicate the button press 218 | let mut stdout = async_std::io::stdout(); 219 | writeln!(stdout, "Detected click at ({}, {})", bp.event_x, bp.event_y) 220 | .await 221 | .unwrap(); 222 | } 223 | Event::ClientMessage(cme) => { 224 | // check if exit msg 225 | if cme.data.as_data32()[0] == wm_delete_window { 226 | break; 227 | } 228 | } 229 | _ => {} 230 | } 231 | } 232 | 233 | Ok(()) 234 | } 235 | } 236 | 237 | #[async_std::main] 238 | #[cfg(feature = "async-std-support")] 239 | async fn main() -> Result<()> { 240 | tracing_subscriber::fmt::init(); 241 | inner::real_main().await 242 | } 243 | 244 | #[cfg(not(feature = "async-std-support"))] 245 | fn main() { 246 | println!("This example requires the `async-std-support` feature to be enabled."); 247 | } 248 | -------------------------------------------------------------------------------- /breadx/examples/basic.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! Demonstration of the basic capabilities of `breadx`. 7 | 8 | #[path = "util/cancel.rs"] 9 | mod cancel; 10 | 11 | #[cfg(feature = "std")] 12 | use breadx::{ 13 | display::DisplayConnection, 14 | prelude::*, 15 | protocol::{xproto, Event}, 16 | }; 17 | 18 | #[cfg(feature = "std")] 19 | fn main() -> breadx::Result<()> { 20 | tracing_subscriber::fmt::init(); 21 | cancel::spawn_kill_thread(); 22 | 23 | // Create a new display connection. 24 | let mut connection = DisplayConnection::connect(None)?; 25 | 26 | // Events that our window receives. 27 | let events = xproto::EventMask::EXPOSURE | xproto::EventMask::BUTTON_PRESS; 28 | 29 | // Background color. 30 | let background = connection.default_screen().white_pixel; 31 | 32 | // Create a new window. 33 | let parent = connection.default_screen().root; 34 | let wid = connection.generate_xid()?; 35 | 36 | connection.create_window( 37 | 0, // 0 indicates inherit from parent 38 | wid, 39 | parent, 40 | 0, // x 41 | 0, // y 42 | 600, // width 43 | 400, // height 44 | 0, // border width 45 | xproto::WindowClass::COPY_FROM_PARENT, 46 | 0, // borrow from parent 47 | xproto::CreateWindowAux::new() 48 | .event_mask(events) 49 | .background_pixel(background), 50 | )?; 51 | 52 | // Map it to the screen and set the title. 53 | connection.map_window_checked(wid)?; 54 | let title = "Hello, world!"; 55 | connection.change_property_checked( 56 | xproto::PropMode::REPLACE, 57 | wid, 58 | xproto::AtomEnum::WM_NAME.into(), 59 | xproto::AtomEnum::STRING.into(), 60 | 8, 61 | title.len() as u32, 62 | title, 63 | )?; 64 | 65 | // set up a GC for our window 66 | let gc = connection.generate_xid()?; 67 | connection.create_gc_checked( 68 | gc, 69 | wid, 70 | xproto::CreateGCAux::new() 71 | .foreground(connection.default_screen().black_pixel) 72 | .graphics_exposures(0) 73 | .line_width(10), 74 | )?; 75 | 76 | // create some colors 77 | let cmap = connection.default_screen().default_colormap; 78 | let red_color = connection.alloc_color(cmap, u16::MAX, 0, 0)?; 79 | let green_color = connection.alloc_color(cmap, 0, u16::MAX, 0)?; 80 | let blue_color = connection.alloc_color(cmap, 0, 0, u16::MAX)?; 81 | 82 | connection.flush()?; 83 | 84 | // resolve the colors 85 | let red_pixel = connection.wait_for_reply(red_color)?.pixel; 86 | let green_pixel = connection.wait_for_reply(green_color)?.pixel; 87 | let blue_pixel = connection.wait_for_reply(blue_color)?.pixel; 88 | 89 | // set up an exit strategy 90 | let wm_protocols = connection.intern_atom(false, "WM_PROTOCOLS")?; 91 | let wm_delete_window = connection.intern_atom(false, "WM_DELETE_WINDOW")?; 92 | connection.flush()?; 93 | let wm_protocols = connection.wait_for_reply(wm_protocols)?.atom; 94 | let wm_delete_window = connection.wait_for_reply(wm_delete_window)?.atom; 95 | 96 | connection.change_property( 97 | xproto::PropMode::REPLACE, 98 | wid, 99 | wm_protocols, 100 | xproto::AtomEnum::ATOM.into(), 101 | 32, 102 | 1, 103 | &wm_delete_window, 104 | )?; 105 | 106 | cancel::spawn_close_thread(wid); 107 | 108 | // primary event loop 109 | loop { 110 | let event = connection.wait_for_event()?; 111 | 112 | match event { 113 | Event::Expose(_) => { 114 | // we need to repaint 115 | // note that we've stopped using _checked() requests 116 | // here for the sake of speed 117 | 118 | // draw two red lines for an "x" 119 | connection.change_gc(gc, xproto::ChangeGCAux::new().foreground(red_pixel))?; 120 | connection.poly_segment( 121 | wid, 122 | gc, 123 | &[ 124 | xproto::Segment { 125 | x1: 10, 126 | y1: 10, 127 | x2: 150, 128 | y2: 150, 129 | }, 130 | xproto::Segment { 131 | x1: 150, 132 | y1: 10, 133 | x2: 10, 134 | y2: 150, 135 | }, 136 | ], 137 | )?; 138 | 139 | // draw a green circle 140 | // note that angles are in units of 16ths of a degree 141 | connection.change_gc(gc, xproto::ChangeGCAux::new().foreground(green_pixel))?; 142 | connection.poly_fill_arc( 143 | wid, 144 | gc, 145 | &[xproto::Arc { 146 | x: 200, 147 | y: 10, 148 | width: 150, 149 | height: 150, 150 | angle1: 0, 151 | angle2: 360 * 64, 152 | }], 153 | )?; 154 | 155 | // draw a blue semicircle 156 | connection.change_gc(gc, xproto::ChangeGCAux::new().foreground(blue_pixel))?; 157 | connection.poly_fill_arc( 158 | wid, 159 | gc, 160 | &[xproto::Arc { 161 | x: 200, 162 | y: 10, 163 | width: 150, 164 | height: 150, 165 | angle1: 0, 166 | angle2: 270 * 64, 167 | }], 168 | )?; 169 | 170 | // draw the black outline of a circle 171 | connection.change_gc( 172 | gc, 173 | xproto::ChangeGCAux::new().foreground(connection.default_screen().black_pixel), 174 | )?; 175 | connection.poly_arc( 176 | wid, 177 | gc, 178 | &[xproto::Arc { 179 | x: 200, 180 | y: 10, 181 | width: 150, 182 | height: 150, 183 | angle1: 0, 184 | angle2: 360 * 64, 185 | }], 186 | )?; 187 | 188 | connection.flush()?; 189 | } 190 | Event::ButtonPress(bp) => { 191 | // indicate that we have been clicked 192 | println!("Detected click at ({}, {})", bp.event_x, bp.event_y); 193 | } 194 | Event::ClientMessage(cme) => { 195 | // check if it's telling us to exit 196 | if cme.data.as_data32()[0] == wm_delete_window { 197 | break; 198 | } 199 | } 200 | _ => {} 201 | } 202 | } 203 | 204 | Ok(()) 205 | } 206 | 207 | #[cfg(not(feature = "std"))] 208 | fn main() { 209 | println!("Feature `std` is necessary to run this example."); 210 | } 211 | -------------------------------------------------------------------------------- /breadx/examples/just_emit_setup.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! Less of an example of what to do and more of just a diagnostic tool 7 | //! for what our `Setup` looks like. 8 | 9 | #[cfg(feature = "std")] 10 | use breadx::{display::DisplayConnection, prelude::*}; 11 | 12 | #[cfg(feature = "std")] 13 | fn main() { 14 | let conn = DisplayConnection::connect(None).unwrap(); 15 | println!("{:#?}", conn.setup()); 16 | } 17 | 18 | #[cfg(not(feature = "std"))] 19 | fn main() { 20 | println!("This example requires the `std` feature."); 21 | } 22 | -------------------------------------------------------------------------------- /breadx/examples/tokio_async.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #[cfg(all(feature = "tokio-support", unix))] 7 | #[path = "util/cancel.rs"] 8 | mod cancel; 9 | 10 | #[cfg(all(feature = "tokio-support", unix))] 11 | use breadx::Result; 12 | 13 | #[cfg(all(feature = "tokio-support", unix))] 14 | mod inner { 15 | use breadx::{ 16 | prelude::*, 17 | protocol::{xproto, Event}, 18 | rt_support::tokio_support, 19 | Result, 20 | }; 21 | use tokio::io::AsyncWriteExt; 22 | 23 | #[tokio::main(flavor = "current_thread")] 24 | pub async fn real_main() -> Result<()> { 25 | super::cancel::spawn_kill_thread(); 26 | 27 | // Connect to the server. 28 | let mut connection = tokio_support::connect(None).await?; 29 | 30 | // the events our windows receives. 31 | let events = xproto::EventMask::EXPOSURE | xproto::EventMask::BUTTON_PRESS; 32 | 33 | // the background color 34 | let background = connection.default_screen().white_pixel; 35 | 36 | // create the new window 37 | let parent = connection.default_screen().root; 38 | let wid = connection.generate_xid().await?; 39 | 40 | connection 41 | .create_window_checked( 42 | 0, 43 | wid, 44 | parent, 45 | 0, 46 | 0, 47 | 600, 48 | 400, 49 | 0, 50 | xproto::WindowClass::COPY_FROM_PARENT, 51 | 0, 52 | xproto::CreateWindowAux::new() 53 | .event_mask(events) 54 | .background_pixel(background), 55 | ) 56 | .await?; 57 | 58 | // map to screen and set title 59 | connection.map_window_checked(wid).await?; 60 | let title = "Hello from tokio!"; 61 | connection 62 | .change_property_checked( 63 | xproto::PropMode::REPLACE, 64 | wid, 65 | xproto::AtomEnum::WM_NAME.into(), 66 | xproto::AtomEnum::STRING.into(), 67 | 8, 68 | title.len() as u32, 69 | title, 70 | ) 71 | .await?; 72 | 73 | // set up a GC for drawing 74 | let gc = connection.generate_xid().await?; 75 | connection 76 | .create_gc_checked( 77 | gc, 78 | wid, 79 | xproto::CreateGCAux::new() 80 | .foreground(connection.default_screen().black_pixel) 81 | .graphics_exposures(0) 82 | .line_width(10), 83 | ) 84 | .await?; 85 | 86 | // create some colors 87 | let cmap = connection.default_screen().default_colormap; 88 | let red_color = connection.alloc_color(cmap, 0xffff, 0, 0).await?; 89 | let green_color = connection.alloc_color(cmap, 0, 0xffff, 0).await?; 90 | let blue_color = connection.alloc_color(cmap, 0, 0, 0xffff).await?; 91 | 92 | connection.flush().await?; 93 | 94 | // resolve the colors 95 | let red_pixel = connection.wait_for_reply(red_color).await?.pixel; 96 | let green_pixel = connection.wait_for_reply(green_color).await?.pixel; 97 | let blue_pixel = connection.wait_for_reply(blue_color).await?.pixel; 98 | 99 | // setup exit strategy 100 | let wm_protocols = connection.intern_atom(false, "WM_PROTOCOLS").await?; 101 | let wm_delete_window = connection.intern_atom(false, "WM_DELETE_WINDOW").await?; 102 | connection.flush().await?; 103 | let wm_protocols = connection.wait_for_reply(wm_protocols).await?.atom; 104 | let wm_delete_window = connection.wait_for_reply(wm_delete_window).await?.atom; 105 | 106 | connection 107 | .change_property_checked( 108 | xproto::PropMode::REPLACE, 109 | wid, 110 | wm_protocols, 111 | xproto::AtomEnum::ATOM.into(), 112 | 32, 113 | 1, 114 | &wm_delete_window, 115 | ) 116 | .await?; 117 | 118 | super::cancel::spawn_close_thread(wid); 119 | 120 | // primary event loop 121 | loop { 122 | let event = connection.wait_for_event().await?; 123 | 124 | match event { 125 | Event::Expose(_) => { 126 | // begin the repaint 127 | 128 | // draw a red "X" 129 | connection 130 | .change_gc(gc, xproto::ChangeGCAux::new().foreground(red_pixel)) 131 | .await?; 132 | connection 133 | .poly_segment( 134 | wid, 135 | gc, 136 | &[ 137 | xproto::Segment { 138 | x1: 10, 139 | y1: 10, 140 | x2: 150, 141 | y2: 150, 142 | }, 143 | xproto::Segment { 144 | x1: 150, 145 | y1: 10, 146 | x2: 10, 147 | y2: 150, 148 | }, 149 | ], 150 | ) 151 | .await?; 152 | 153 | // draw a green circle 154 | connection 155 | .change_gc(gc, xproto::ChangeGCAux::new().foreground(green_pixel)) 156 | .await?; 157 | connection 158 | .poly_fill_arc( 159 | wid, 160 | gc, 161 | &[xproto::Arc { 162 | x: 200, 163 | y: 10, 164 | width: 150, 165 | height: 150, 166 | angle1: 0, 167 | angle2: 360 * 64, 168 | }], 169 | ) 170 | .await?; 171 | 172 | // draw a blue semicircle 173 | connection 174 | .change_gc(gc, xproto::ChangeGCAux::new().foreground(blue_pixel)) 175 | .await?; 176 | connection 177 | .poly_fill_arc( 178 | wid, 179 | gc, 180 | &[xproto::Arc { 181 | x: 200, 182 | y: 10, 183 | width: 150, 184 | height: 150, 185 | angle1: 0, 186 | angle2: 270 * 64, 187 | }], 188 | ) 189 | .await?; 190 | 191 | // draw the black outline of a circle 192 | connection 193 | .change_gc( 194 | gc, 195 | xproto::ChangeGCAux::new() 196 | .foreground(connection.default_screen().black_pixel), 197 | ) 198 | .await?; 199 | connection 200 | .poly_arc( 201 | wid, 202 | gc, 203 | &[xproto::Arc { 204 | x: 200, 205 | y: 10, 206 | width: 150, 207 | height: 150, 208 | angle1: 0, 209 | angle2: 360 * 64, 210 | }], 211 | ) 212 | .await?; 213 | 214 | // end the repaint 215 | connection.flush().await?; 216 | } 217 | Event::ButtonPress(bp) => { 218 | // indicate the button press 219 | let mut stdout = tokio::io::stdout(); 220 | let f = format!("Detected click at ({}, {})\n", bp.event_x, bp.event_y); 221 | stdout.write_all(f.as_bytes()).await.unwrap(); 222 | } 223 | Event::ClientMessage(cme) => { 224 | // check if exit msg 225 | if cme.data.as_data32()[0] == wm_delete_window { 226 | break; 227 | } 228 | } 229 | _ => {} 230 | } 231 | } 232 | 233 | Ok(()) 234 | } 235 | } 236 | 237 | #[cfg(all(feature = "tokio-support", unix))] 238 | fn main() -> Result<()> { 239 | tracing_subscriber::fmt::init(); 240 | inner::real_main() 241 | } 242 | 243 | #[cfg(not(all(feature = "tokio-support", unix)))] 244 | fn main() { 245 | println!("`tokio-support` feature needs to be enabled for this example"); 246 | } 247 | -------------------------------------------------------------------------------- /breadx/examples/util/cancel.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! Utilities for canceling the programs. 7 | //! 8 | //! This is used in CI to be able to run these programs as a testing 9 | //! measure. The ideal case opens a new display and sends the kill 10 | //! command to the given root window. The less-ideal case, in case 11 | //! a deadlock happens, manually kills the program after one minute. 12 | //! 13 | //! These functions are only active if the "BREADX_EXAMPLE_TIMEOUT" 14 | //! environment variable is present at compile time. 15 | 16 | #![cfg(feature = "std")] 17 | 18 | use breadx::{display::DisplayConnection, prelude::*, protocol::xproto}; 19 | use std::{process, thread, time::Duration}; 20 | 21 | const SKIP_TIMEOUT: bool = option_env!("BREADX_EXAMPLE_TIMEOUT").is_none(); 22 | 23 | /// Spawns the thread that kills the entire program. 24 | pub fn spawn_kill_thread() { 25 | if SKIP_TIMEOUT { 26 | return; 27 | } 28 | 29 | thread::Builder::new() 30 | .name("kill-thread".to_string()) 31 | .spawn(|| { 32 | // wait one minute 33 | thread::sleep(Duration::from_secs(60)); 34 | 35 | // kill the program 36 | tracing::error!("{}", KILL_PROGRAM); 37 | 38 | process::exit(0); 39 | }) 40 | .expect("failed to spawn kill thread"); 41 | } 42 | 43 | /// Connect to the server and send a close signal to the main window 44 | /// after the given amount of time. 45 | pub fn spawn_close_thread(main_window: xproto::Window) { 46 | if SKIP_TIMEOUT { 47 | return; 48 | } 49 | 50 | thread::Builder::new() 51 | .name("close-thread".to_string()) 52 | .spawn(move || { 53 | // wait one second 54 | thread::sleep(Duration::from_secs(1)); 55 | 56 | tracing::warn!("{}", CLOSE_PROGRAM); 57 | 58 | // open up a new connection 59 | let mut display = DisplayConnection::connect(None).unwrap(); 60 | 61 | // intern the needed atoms 62 | let protocols = display.intern_atom(false, "WM_PROTOCOLS").unwrap(); 63 | let delete_window = display.intern_atom(false, "WM_DELETE_WINDOW").unwrap(); 64 | let protocols = display.wait_for_reply(protocols).unwrap().atom; 65 | let delete_window = display.wait_for_reply(delete_window).unwrap().atom; 66 | 67 | // create the event 68 | let event = xproto::ClientMessageEvent::new( 69 | 32, 70 | main_window, 71 | protocols, 72 | [delete_window, 0, 0, 0, 0], 73 | ); 74 | let mut send_event = xproto::SendEventRequest { 75 | propagate: false, 76 | destination: main_window, 77 | event_mask: xproto::EventMask::NO_EVENT.into(), 78 | event: std::borrow::Cow::Owned(event.into()), 79 | }; 80 | 81 | // send the events, fallibly 82 | display.send_void_request(send_event.clone(), true).ok(); 83 | send_event.event_mask = xproto::EventMask::SUBSTRUCTURE_REDIRECT.into(); 84 | display.send_void_request(send_event, true).ok(); 85 | display.flush().unwrap(); 86 | }) 87 | .expect("failed to spawn close thread"); 88 | } 89 | 90 | const KILL_PROGRAM: &str = " 91 | The program is not responding to the close request being sent 92 | to the window. The program will now exit with an error status, 93 | in order to prevent the CI from being deadlocked. 94 | "; 95 | 96 | const CLOSE_PROGRAM: &str = " 97 | The program will now send a close request to the main window, 98 | in order to stop the event loop. This will shut down the program 99 | safely. 100 | 101 | If this is not desired behavior, disable the `BREADX_EXAMPLE_TIMEOUT` 102 | environment variable. 103 | "; 104 | -------------------------------------------------------------------------------- /breadx/src/connection/test.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #![cfg(test)] 7 | 8 | use alloc::vec::Vec; 9 | use core::{cmp, mem}; 10 | 11 | use super::{Connection, IoSlice, IoSliceMut}; 12 | use crate::Fd; 13 | 14 | /// It is useful, for testing, to just have a `Connection` that reads and writes from 15 | /// internal buffers. 16 | pub(crate) struct TestConnection<'a> { 17 | /// Bytes that are passed to the reader. 18 | read_bytes: &'a [u8], 19 | /// File descriptors that are passed to the reader. 20 | read_fds: &'a mut Vec, 21 | 22 | /// Bytes that are passed from the writer. 23 | written_bytes: &'a mut Vec, 24 | /// File descriptors that are passed from the writer. 25 | written_fds: &'a mut Vec, 26 | } 27 | 28 | impl<'a> TestConnection<'a> { 29 | /// Create a new `TestConnection`. 30 | pub fn new( 31 | read_bytes: &'a [u8], 32 | read_fds: &'a mut Vec, 33 | written_bytes: &'a mut Vec, 34 | written_fds: &'a mut Vec, 35 | ) -> Self { 36 | Self { 37 | read_bytes, 38 | read_fds, 39 | written_bytes, 40 | written_fds, 41 | } 42 | } 43 | } 44 | 45 | impl<'a> Connection for TestConnection<'a> { 46 | fn send_slices_and_fds( 47 | &mut self, 48 | slices: &[IoSlice<'_>], 49 | fds: &mut Vec, 50 | ) -> crate::Result { 51 | let mut len = 0; 52 | 53 | // push every slice onto the written_bytes vector 54 | for slice in slices { 55 | len += slice.len(); 56 | self.written_bytes.extend_from_slice(&*slice); 57 | } 58 | 59 | self.written_fds 60 | .extend(mem::take(fds).into_iter().map(|fd| { 61 | cfg_if::cfg_if! { 62 | if #[cfg(all(feature = "std", unix))] { 63 | Fd::into_raw_fd(fd) 64 | } else { 65 | let _ = fd; 66 | 0 67 | } 68 | } 69 | })); 70 | Ok(len) 71 | } 72 | 73 | fn recv_slices_and_fds( 74 | &mut self, 75 | buffer: &mut [IoSliceMut<'_>], 76 | fds: &mut Vec, 77 | ) -> crate::Result { 78 | // copy bytes from read_bytes for as long as we can 79 | let mut len = 0; 80 | for slice in buffer { 81 | let amt = cmp::min(self.read_bytes.len(), slice.len()); 82 | slice[..amt].copy_from_slice(&self.read_bytes[..amt]); 83 | len += amt; 84 | self.read_bytes = &self.read_bytes[amt..]; 85 | } 86 | 87 | fds.append(self.read_fds); 88 | 89 | Ok(len) 90 | } 91 | 92 | fn flush(&mut self) -> crate::Result<()> { 93 | Ok(()) 94 | } 95 | 96 | fn shutdown(&self) -> crate::Result<()> { 97 | Ok(()) 98 | } 99 | 100 | fn non_blocking_recv_slices_and_fds( 101 | &mut self, 102 | slices: &mut [IoSliceMut<'_>], 103 | fds: &mut Vec, 104 | ) -> crate::Result { 105 | self.recv_slices_and_fds(slices, fds) 106 | } 107 | } 108 | 109 | pub(crate) fn with_test_connection( 110 | read_bytes: &[u8], 111 | read_fds: Vec, 112 | test: impl FnOnce(TestConnection<'_>), 113 | asserts: impl FnOnce(Vec, Vec), 114 | ) { 115 | let mut written_bytes = Vec::new(); 116 | let mut written_fds = Vec::new(); 117 | let mut read_fds = read_fds 118 | .into_iter() 119 | .map(|fd| { 120 | cfg_if::cfg_if! { 121 | if #[cfg(all(feature = "std", unix))] { 122 | Fd::new(fd) 123 | } else { 124 | let _ = fd; 125 | panic!("can't parse fds on non-std unix") 126 | } 127 | } 128 | }) 129 | .collect::>(); 130 | 131 | let conn = TestConnection::new( 132 | read_bytes, 133 | &mut read_fds, 134 | &mut written_bytes, 135 | &mut written_fds, 136 | ); 137 | 138 | test(conn); 139 | asserts(written_bytes, written_fds); 140 | } 141 | 142 | #[cfg(all(unix, feature = "std"))] 143 | mod tests { 144 | use super::*; 145 | use crate::{ 146 | connection::{new_io_slice, new_io_slice_mut}, 147 | setup_tracing, 148 | }; 149 | use alloc::vec; 150 | 151 | #[test] 152 | fn test_send_slices_and_fds() { 153 | setup_tracing(); 154 | 155 | with_test_connection( 156 | &[], 157 | vec![], 158 | |mut conn| { 159 | let slices = vec![ 160 | new_io_slice(&[1, 2, 3]), 161 | new_io_slice(&[4, 5, 6]), 162 | new_io_slice(&[7, 8, 9]), 163 | ]; 164 | let mut fds = (&[1, 2, 3, 4, 5]) 165 | .iter() 166 | .copied() 167 | .map(Fd::new) 168 | .collect::>(); 169 | let mut total_len = 9; 170 | 171 | while total_len > 0 { 172 | total_len -= conn.send_slices_and_fds(&slices, &mut fds).unwrap(); 173 | } 174 | }, 175 | |written_bytes, written_fds| { 176 | assert_eq!(written_bytes, &[1, 2, 3, 4, 5, 6, 7, 8, 9]); 177 | assert_eq!(&written_fds, &[1, 2, 3, 4, 5]); 178 | }, 179 | ); 180 | } 181 | 182 | #[test] 183 | fn test_recv_slices_and_fds() { 184 | setup_tracing(); 185 | 186 | with_test_connection( 187 | &[1, 2, 3, 4, 5, 6, 7, 8, 9], 188 | vec![1, 2, 3, 4, 5], 189 | |mut conn| { 190 | let mut buffer = vec![0; 9]; 191 | 192 | // split buffer borrows up 193 | let bborrow = &mut buffer[..]; 194 | let (bborrow1, bborrow_r) = bborrow.split_at_mut(3); 195 | let (bborrow2, bborrow3) = bborrow_r.split_at_mut(3); 196 | 197 | let mut iov = [ 198 | new_io_slice_mut(bborrow1), 199 | new_io_slice_mut(bborrow2), 200 | new_io_slice_mut(bborrow3), 201 | ]; 202 | let mut fds = vec![]; 203 | let mut total_len = 9; 204 | 205 | while total_len > 0 { 206 | total_len -= conn.recv_slices_and_fds(&mut iov, &mut fds).unwrap(); 207 | } 208 | 209 | assert_eq!(buffer, [1, 2, 3, 4, 5, 6, 7, 8, 9]); 210 | let fds = fds.into_iter().map(Fd::into_raw_fd).collect::>(); 211 | assert_eq!(fds, vec![1, 2, 3, 4, 5]); 212 | }, 213 | |_, _| {}, 214 | ); 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /breadx/src/display/cell.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use crate::Result; 7 | 8 | use super::{Display, DisplayBase, RawReply, RawRequest}; 9 | use alloc::sync::Arc; 10 | use core::cell::RefCell; 11 | use x11rb_protocol::protocol::{xproto::Setup, Event}; 12 | 13 | cfg_async! { 14 | use super::{Interest, AsyncDisplay, CanBeAsyncDisplay}; 15 | use core::task::{Context, Poll}; 16 | } 17 | 18 | /// An implementation of `Display` that can be used immutably through 19 | /// thread-unsafe cells. 20 | pub struct CellDisplay { 21 | setup: Arc, 22 | default_screen_index: usize, 23 | inner: RefCell, 24 | } 25 | 26 | impl From for CellDisplay { 27 | fn from(inner: Dpy) -> Self { 28 | let setup = inner.setup().clone(); 29 | Self { 30 | default_screen_index: inner.default_screen_index(), 31 | inner: RefCell::new(inner), 32 | setup, 33 | } 34 | } 35 | } 36 | 37 | impl DisplayBase for CellDisplay { 38 | fn default_screen_index(&self) -> usize { 39 | self.default_screen_index 40 | } 41 | 42 | fn setup(&self) -> &Arc { 43 | &self.setup 44 | } 45 | 46 | fn poll_for_reply_raw(&mut self, seq: u64) -> Result> { 47 | self.inner.get_mut().poll_for_reply_raw(seq) 48 | } 49 | 50 | fn poll_for_event(&mut self) -> Result> { 51 | self.inner.get_mut().poll_for_event() 52 | } 53 | } 54 | 55 | impl Display for CellDisplay { 56 | fn send_request_raw(&mut self, req: RawRequest<'_, '_>) -> Result { 57 | self.inner.get_mut().send_request_raw(req) 58 | } 59 | 60 | fn wait_for_event(&mut self) -> Result { 61 | self.inner.get_mut().wait_for_event() 62 | } 63 | 64 | fn wait_for_reply_raw(&mut self, seq: u64) -> Result { 65 | self.inner.get_mut().wait_for_reply_raw(seq) 66 | } 67 | 68 | fn maximum_request_length(&mut self) -> Result { 69 | self.inner.get_mut().maximum_request_length() 70 | } 71 | 72 | fn generate_xid(&mut self) -> Result { 73 | self.inner.get_mut().generate_xid() 74 | } 75 | 76 | fn synchronize(&mut self) -> Result<()> { 77 | self.inner.get_mut().synchronize() 78 | } 79 | 80 | fn flush(&mut self) -> Result<()> { 81 | self.inner.get_mut().flush() 82 | } 83 | 84 | fn check_for_error(&mut self, seq: u64) -> Result<()> { 85 | self.inner.get_mut().check_for_error(seq) 86 | } 87 | } 88 | 89 | impl DisplayBase for &CellDisplay { 90 | fn default_screen_index(&self) -> usize { 91 | self.default_screen_index 92 | } 93 | 94 | fn setup(&self) -> &Arc { 95 | &self.setup 96 | } 97 | 98 | fn poll_for_event(&mut self) -> Result> { 99 | self.inner.borrow_mut().poll_for_event() 100 | } 101 | 102 | fn poll_for_reply_raw(&mut self, seq: u64) -> Result> { 103 | self.inner.borrow_mut().poll_for_reply_raw(seq) 104 | } 105 | } 106 | 107 | impl Display for &CellDisplay { 108 | fn send_request_raw(&mut self, req: RawRequest<'_, '_>) -> Result { 109 | self.inner.borrow_mut().send_request_raw(req) 110 | } 111 | 112 | fn wait_for_event(&mut self) -> Result { 113 | self.inner.borrow_mut().wait_for_event() 114 | } 115 | 116 | fn wait_for_reply_raw(&mut self, seq: u64) -> Result { 117 | self.inner.borrow_mut().wait_for_reply_raw(seq) 118 | } 119 | 120 | fn maximum_request_length(&mut self) -> Result { 121 | self.inner.borrow_mut().maximum_request_length() 122 | } 123 | 124 | fn generate_xid(&mut self) -> Result { 125 | self.inner.borrow_mut().generate_xid() 126 | } 127 | 128 | fn synchronize(&mut self) -> Result<()> { 129 | self.inner.borrow_mut().synchronize() 130 | } 131 | 132 | fn flush(&mut self) -> Result<()> { 133 | self.inner.borrow_mut().flush() 134 | } 135 | 136 | fn check_for_error(&mut self, seq: u64) -> Result<()> { 137 | self.inner.borrow_mut().check_for_error(seq) 138 | } 139 | } 140 | 141 | cfg_async! { 142 | impl CanBeAsyncDisplay for CellDisplay { 143 | fn try_send_request_raw( 144 | &mut self, 145 | req: &mut RawRequest<'_, '_>, 146 | ctx: &mut Context<'_>, 147 | ) -> Result> { 148 | self.inner.get_mut().try_send_request_raw(req, ctx) 149 | } 150 | 151 | fn format_request( 152 | &mut self, 153 | req: &mut RawRequest<'_, '_>, 154 | ctx: &mut Context<'_>, 155 | ) -> Result> { 156 | self.inner.get_mut().format_request(req, ctx) 157 | } 158 | 159 | fn try_wait_for_event(&mut self, ctx: &mut Context<'_>) -> Result> { 160 | self.inner.get_mut().try_wait_for_event(ctx) 161 | } 162 | 163 | fn try_wait_for_reply_raw( 164 | &mut self, 165 | seq: u64, 166 | ctx: &mut Context<'_>, 167 | ) -> Result> { 168 | self.inner.get_mut().try_wait_for_reply_raw(seq, ctx) 169 | } 170 | 171 | fn try_flush(&mut self, ctx: &mut Context<'_>) -> Result> { 172 | self.inner.get_mut().try_flush(ctx) 173 | } 174 | 175 | fn try_maximum_request_length( 176 | &mut self, 177 | ctx: &mut Context<'_>, 178 | ) -> Result> { 179 | self.inner.get_mut().try_maximum_request_length(ctx) 180 | } 181 | 182 | fn try_generate_xid(&mut self, ctx: &mut Context<'_>) -> Result> { 183 | self.inner.get_mut().try_generate_xid(ctx) 184 | } 185 | 186 | fn try_check_for_error( 187 | &mut self, 188 | seq: u64, 189 | ctx: &mut Context<'_>, 190 | ) -> Result> { 191 | self.inner.get_mut().try_check_for_error(seq, ctx) 192 | } 193 | } 194 | 195 | impl CanBeAsyncDisplay for &CellDisplay { 196 | fn try_send_request_raw( 197 | &mut self, 198 | req: &mut RawRequest<'_, '_>, 199 | ctx: &mut Context<'_>, 200 | ) -> Result> { 201 | self.inner.borrow_mut().try_send_request_raw(req, ctx) 202 | } 203 | 204 | fn format_request( 205 | &mut self, 206 | req: &mut RawRequest<'_, '_>, 207 | ctx: &mut Context<'_>, 208 | ) -> Result> { 209 | self.inner.borrow_mut().format_request(req, ctx) 210 | } 211 | 212 | fn try_wait_for_event(&mut self, ctx: &mut Context<'_>) -> Result> { 213 | self.inner.borrow_mut().try_wait_for_event(ctx) 214 | } 215 | 216 | fn try_wait_for_reply_raw( 217 | &mut self, 218 | seq: u64, 219 | ctx: &mut Context<'_>, 220 | ) -> Result> { 221 | self.inner.borrow_mut().try_wait_for_reply_raw(seq, ctx) 222 | } 223 | 224 | fn try_flush(&mut self, ctx: &mut Context<'_>) -> Result> { 225 | self.inner.borrow_mut().try_flush(ctx) 226 | } 227 | 228 | fn try_maximum_request_length( 229 | &mut self, 230 | ctx: &mut Context<'_>, 231 | ) -> Result> { 232 | self.inner.borrow_mut().try_maximum_request_length(ctx) 233 | } 234 | 235 | fn try_generate_xid(&mut self, ctx: &mut Context<'_>) -> Result> { 236 | self.inner.borrow_mut().try_generate_xid(ctx) 237 | } 238 | 239 | fn try_check_for_error( 240 | &mut self, 241 | seq: u64, 242 | ctx: &mut Context<'_>, 243 | ) -> Result> { 244 | self.inner.borrow_mut().try_check_for_error(seq, ctx) 245 | } 246 | } 247 | 248 | impl AsyncDisplay for CellDisplay { 249 | fn poll_for_interest( 250 | &mut self, 251 | interest: Interest, 252 | callback: &mut dyn FnMut(&mut dyn AsyncDisplay, &mut Context< '_>) -> Result<()>, 253 | ctx: &mut Context< '_> 254 | ) -> Poll> { 255 | self.inner.get_mut().poll_for_interest(interest, callback, ctx) 256 | } 257 | } 258 | 259 | impl AsyncDisplay for &CellDisplay { 260 | fn poll_for_interest( 261 | &mut self, 262 | interest: Interest, 263 | callback: &mut dyn FnMut(&mut dyn AsyncDisplay, &mut Context< '_>) -> Result<()>, 264 | ctx: &mut Context< '_> 265 | ) -> Poll> { 266 | self.inner.borrow_mut().poll_for_interest(interest, callback, ctx) 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /breadx/src/display/cookie.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! Annotated request cookie types. 7 | 8 | use core::marker::PhantomData; 9 | use core::{fmt, hash::Hash}; 10 | 11 | /// A sequence number indicating the state of a request. 12 | pub struct Cookie { 13 | sequence: u64, 14 | marker: PhantomData, 15 | } 16 | 17 | // because of T, we need to manually implement these traits instead 18 | // of deriving 19 | 20 | impl Clone for Cookie { 21 | fn clone(&self) -> Self { 22 | Self { 23 | sequence: self.sequence, 24 | marker: PhantomData, 25 | } 26 | } 27 | } 28 | 29 | impl Copy for Cookie {} 30 | 31 | impl PartialEq for Cookie { 32 | fn eq(&self, other: &Self) -> bool { 33 | self.sequence == other.sequence 34 | } 35 | } 36 | 37 | impl Eq for Cookie {} 38 | 39 | impl PartialOrd for Cookie { 40 | fn partial_cmp(&self, other: &Self) -> Option { 41 | self.sequence.partial_cmp(&other.sequence) 42 | } 43 | } 44 | 45 | impl Ord for Cookie { 46 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { 47 | self.sequence.cmp(&other.sequence) 48 | } 49 | } 50 | 51 | impl Hash for Cookie { 52 | fn hash(&self, state: &mut H) { 53 | self.sequence.hash(state); 54 | } 55 | } 56 | 57 | impl fmt::Debug for Cookie { 58 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 | fmt::Debug::fmt(&self.sequence, f) 60 | } 61 | } 62 | 63 | impl fmt::Display for Cookie { 64 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 65 | fmt::Display::fmt(&self.sequence, f) 66 | } 67 | } 68 | 69 | impl Cookie { 70 | /// Create a new `Cookie` from the raw sequence number. 71 | #[must_use] 72 | pub fn from_sequence(sequence: u64) -> Self { 73 | Self { 74 | sequence, 75 | marker: PhantomData, 76 | } 77 | } 78 | 79 | /// Get the raw sequence number. 80 | #[must_use] 81 | pub fn sequence(self) -> u64 { 82 | self.sequence 83 | } 84 | } 85 | 86 | impl From for Cookie { 87 | fn from(sequence: u64) -> Self { 88 | Self::from_sequence(sequence) 89 | } 90 | } 91 | 92 | impl From> for u64 { 93 | fn from(cookie: Cookie) -> Self { 94 | cookie.sequence 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /breadx/src/display/ext.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! Extension traits. 7 | 8 | use x11rb_protocol::x11_utils::{ReplyFDsRequest, ReplyRequest, TryParseFd, VoidRequest}; 9 | 10 | use super::{ 11 | from_reply_fds_request, from_reply_request, from_void_request, Cookie, Display, DisplayBase, 12 | }; 13 | use crate::Result; 14 | use alloc::vec::Vec; 15 | use core::mem; 16 | 17 | cfg_async! { 18 | use super::{AsyncStatus, AsyncDisplay, RawRequest}; 19 | use crate::futures; 20 | use core::task::Context; 21 | } 22 | 23 | pub trait DisplayBaseExt: DisplayBase { 24 | /// Poll for a reply matching the given sequence number. 25 | fn poll_for_reply(&mut self, cookie: Cookie) -> Result> { 26 | // TODO: zero sized reply 27 | if mem::size_of::() == 0 { 28 | tracing::warn!("zero sized reply"); 29 | return Ok(None); 30 | } 31 | 32 | match self.poll_for_reply_raw(cookie.sequence())? { 33 | Some(reply) => reply.into_reply().map(Some), 34 | None => Ok(None), 35 | } 36 | } 37 | } 38 | 39 | impl DisplayBaseExt for D {} 40 | 41 | /// Extension traits to allow for sending generic requests and replies. 42 | pub trait DisplayExt: Display { 43 | /// Send a request with no reply. 44 | fn send_void_request( 45 | &mut self, 46 | request: impl VoidRequest, 47 | discard_reply: bool, 48 | ) -> Result> { 49 | from_void_request(request, discard_reply, |request| { 50 | let seq = self.send_request_raw(request)?; 51 | 52 | Ok(Cookie::from(seq)) 53 | }) 54 | } 55 | 56 | /// Send a request with a reply. 57 | fn send_reply_request(&mut self, request: R) -> Result> { 58 | from_reply_request(request, |request| { 59 | let seq = self.send_request_raw(request)?; 60 | 61 | Ok(Cookie::from(seq)) 62 | }) 63 | } 64 | 65 | /// Send a request with a reply containing file descriptors. 66 | fn send_reply_fd_request( 67 | &mut self, 68 | request: R, 69 | ) -> Result> { 70 | from_reply_fds_request(request, |request| { 71 | let seq = self.send_request_raw(request)?; 72 | 73 | Ok(Cookie::from(seq)) 74 | }) 75 | } 76 | 77 | /// Receive a reply from the server. 78 | fn wait_for_reply(&mut self, cookie: Cookie) -> Result { 79 | let span = tracing::debug_span!( 80 | "wait_for_reply", 81 | cookie = %cookie.sequence(), 82 | ); 83 | let _enter = span.enter(); 84 | 85 | if mem::size_of::() == 0 { 86 | // zero sized reply indicates that this is a void request, 87 | // check if we need to 88 | tracing::debug!("void request, beginning synchronize"); 89 | 90 | // ensure it didn't error out 91 | // this implies a synchronize 92 | self.check_for_error(cookie.into())?; 93 | 94 | return Ok(R::try_parse_fd(&[], &mut Vec::new()) 95 | .unwrap_or_else(|_| unreachable!()) 96 | .0); 97 | } 98 | 99 | let reply = self.wait_for_reply_raw(cookie.into())?; 100 | reply.into_reply() 101 | } 102 | } 103 | 104 | impl DisplayExt for D {} 105 | 106 | cfg_async! { 107 | /// Extension trait to allow for sending generic requests and replies. 108 | pub trait AsyncDisplayExt : AsyncDisplay { 109 | /// Wrap a function that returns an `AsyncStatus` in the runtime 110 | /// that this is connected to. 111 | #[doc(hidden)] 112 | fn try_with) -> Result>>( 113 | &mut self, 114 | f: F 115 | ) -> futures::TryWith<'_, R, F, Self> { 116 | futures::TryWith::new(self, f) 117 | } 118 | 119 | /// Wait for the X11 server to return the given sequence number. 120 | fn wait_for_reply_raw( 121 | &mut self, 122 | seq: u64, 123 | ) -> futures::WaitForReplyRaw<'_, Self> { 124 | futures::WaitForReplyRaw::polling(self, seq) 125 | } 126 | 127 | /// Wait for the X11 server to send a reply. 128 | fn wait_for_event( 129 | &mut self, 130 | ) -> futures::WaitForEvent<'_, Self> { 131 | futures::WaitForEvent::polling(self) 132 | } 133 | 134 | /// Synchronize with the X11 server. 135 | fn synchronize(&mut self) -> futures::Synchronize<'_, Self> { 136 | futures::Synchronize::new(self) 137 | } 138 | 139 | /// Flush the display. 140 | fn flush(&mut self) -> futures::Flush<'_, Self> { 141 | futures::Flush::polling(self) 142 | } 143 | 144 | /// Send a raw request to the X11 server. 145 | fn send_request_raw( 146 | &mut self, 147 | request: RawRequest<'_, '_>, 148 | ) -> futures::SendRequestRaw<'_, Self>{ 149 | futures::SendRequestRaw::polling(self, request) 150 | } 151 | 152 | /// Check to see if a void request has returned. 153 | fn check_for_error( 154 | &mut self, 155 | seq: u64 156 | ) -> futures::CheckForError<'_, Self> { 157 | futures::CheckForError::polling(self, seq) 158 | } 159 | 160 | /// Generate an XID. 161 | fn generate_xid( 162 | &mut self 163 | ) -> futures::GenerateXid<'_, Self> { 164 | futures::GenerateXid::polling(self) 165 | } 166 | 167 | /// Send a void request to the X11 server. 168 | fn send_void_request( 169 | &mut self, 170 | request: impl VoidRequest, 171 | discard_reply: bool, 172 | ) -> futures::SendRequest<'_, Self, ()> { 173 | futures::SendRequest::for_void(self, discard_reply, request) 174 | } 175 | 176 | /// Send a request with a reply to the X11 server. 177 | fn send_reply_request( 178 | &mut self, 179 | request: R 180 | ) -> futures::SendRequest<'_, Self, R::Reply> { 181 | futures::SendRequest::for_reply(self, request) 182 | } 183 | 184 | /// Send a request with a reply containing file descriptors to the X11 server. 185 | fn send_reply_fd_request( 186 | &mut self, 187 | request: R, 188 | ) -> futures::SendRequest<'_, Self, R::Reply> { 189 | futures::SendRequest::for_reply_fds(self, request) 190 | } 191 | 192 | /// Wait for a reply from the X11 server. 193 | fn wait_for_reply( 194 | &mut self, 195 | cookie: Cookie, 196 | ) -> futures::WaitForReply<'_, Self, R> { 197 | futures::WaitForReply::new(self, cookie) 198 | } 199 | } 200 | 201 | impl AsyncDisplayExt for D {} 202 | } 203 | -------------------------------------------------------------------------------- /breadx/src/display/extension_map.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use ahash::RandomState; 7 | 8 | use super::Prefetch; 9 | use crate::HashMap; 10 | 11 | use x11rb_protocol::{ 12 | protocol::xproto::QueryExtensionRequest, 13 | x11_utils::{ExtInfoProvider, ExtensionInformation}, 14 | }; 15 | 16 | /// A map of extension names to extension information. 17 | pub(crate) struct ExtensionMap { 18 | inner: HashMap<&'static str, Prefetch>>, 19 | } 20 | 21 | impl Default for ExtensionMap { 22 | fn default() -> Self { 23 | Self { 24 | inner: HashMap::with_hasher(RandomState::new()), 25 | } 26 | } 27 | } 28 | 29 | impl ExtensionMap { 30 | #[allow(clippy::option_option)] 31 | pub(crate) fn get(&self, name: &'static str) -> Option> { 32 | self.inner 33 | .get(&name) 34 | .and_then(Prefetch::get_if_resolved) 35 | .copied() 36 | } 37 | 38 | pub(crate) fn take_pf( 39 | &mut self, 40 | name: &'static str, 41 | ) -> Option>> { 42 | self.inner.remove(&name) 43 | } 44 | 45 | pub(crate) fn insert( 46 | &mut self, 47 | name: &'static str, 48 | pf: Prefetch>, 49 | ) { 50 | self.inner.insert(name, pf); 51 | } 52 | 53 | /// Utility function to find an extension that matches a closure. 54 | fn find( 55 | &self, 56 | mut closure: impl FnMut(&ExtensionInformation) -> bool, 57 | ) -> Option<(&'static str, ExtensionInformation)> { 58 | self.inner 59 | .iter() 60 | .filter_map(|(name, pf)| { 61 | pf.get_if_resolved() 62 | .copied() 63 | .flatten() 64 | .map(|ext_info| (*name, ext_info)) 65 | }) 66 | .find(|(_, ext_info)| closure(ext_info)) 67 | } 68 | } 69 | 70 | impl ExtInfoProvider for ExtensionMap { 71 | fn get_from_major_opcode(&self, major_opcode: u8) -> Option<(&str, ExtensionInformation)> { 72 | self.find(|info| info.major_opcode == major_opcode) 73 | } 74 | 75 | fn get_from_event_code(&self, event_code: u8) -> Option<(&str, ExtensionInformation)> { 76 | self.find(|info| info.first_event == event_code) 77 | } 78 | 79 | fn get_from_error_code(&self, error_code: u8) -> Option<(&str, ExtensionInformation)> { 80 | self.find(|info| info.first_error == error_code) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /breadx/src/display/poison.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use crate::{Error, Result}; 7 | 8 | /// A wrapper around a type that may be able to be poisoned if it runs 9 | /// an operation that may put it into an invalid state. 10 | pub(crate) struct Poisonable { 11 | inner: Option, 12 | } 13 | 14 | impl Poisonable { 15 | /// Run a poison-aware operation on the inner value. 16 | pub(crate) fn with(&mut self, f: impl FnOnce(&mut T) -> Result) -> Result { 17 | let inner = match self.inner { 18 | Some(ref mut inner) => inner, 19 | None => return Err(Error::make_poisoned::()), 20 | }; 21 | 22 | match f(inner) { 23 | Err(err) if err.invalid_state() => { 24 | // we've been put into an invalid state, so poison ourselves 25 | self.inner = None; 26 | Err(err) 27 | } 28 | other_result => other_result, 29 | } 30 | } 31 | 32 | /// Sometimes we just want a ref. 33 | #[cfg(feature = "std")] 34 | pub(crate) fn with_ref(&self, f: impl FnOnce(&T) -> Result) -> Result { 35 | let inner = match self.inner { 36 | Some(ref inner) => inner, 37 | None => return Err(Error::make_poisoned::()), 38 | }; 39 | 40 | f(inner) 41 | } 42 | } 43 | 44 | impl From for Poisonable { 45 | fn from(inner: T) -> Self { 46 | Self { inner: Some(inner) } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /breadx/src/display/prefetch.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use x11rb_protocol::{ 7 | protocol::{ 8 | bigreq::EnableRequest, 9 | xc_misc::{GetXIDRangeReply, GetXIDRangeRequest}, 10 | xproto::{GetInputFocusReply, GetInputFocusRequest, QueryExtensionRequest}, 11 | }, 12 | x11_utils::{ExtensionInformation, ReplyRequest}, 13 | SequenceNumber, 14 | }; 15 | 16 | use super::{ 17 | raw_request::{from_reply_request, BufferedRequest}, 18 | Display, RawReply, 19 | }; 20 | use crate::Result; 21 | 22 | cfg_async! { 23 | use super::{AsyncStatus, CanBeAsyncDisplay}; 24 | use core::task::Context; 25 | } 26 | 27 | /// Some internal data that needs to be fetched through 28 | /// another request. 29 | pub struct Prefetch { 30 | state: PrefetchState, 31 | } 32 | 33 | enum PrefetchState { 34 | /// In the process of formatting. 35 | Formatting(Option), 36 | /// We're in the process of sending this data. 37 | #[allow(dead_code)] 38 | Sending(Option, u64), 39 | /// We've sent this data and are waiting for a reply. 40 | Waiting(SequenceNumber), 41 | /// The data is available for us; the request is complete. 42 | Complete(T::Target), 43 | } 44 | 45 | /// A request that needs to be prefetched. 46 | pub trait PrefetchTarget: ReplyRequest { 47 | /// The resulting data from the prefetch. 48 | type Target; 49 | 50 | /// Map the reply to the prefetch target. 51 | fn map_reply(reply: Self::Reply) -> Self::Target; 52 | 53 | /// The target when an X11 error occurs. 54 | fn on_x11_error() -> Self::Target; 55 | } 56 | 57 | impl Default for Prefetch { 58 | fn default() -> Self { 59 | Self::new(Default::default()) 60 | } 61 | } 62 | 63 | impl From> for Prefetch { 64 | fn from(state: PrefetchState) -> Self { 65 | Self { state } 66 | } 67 | } 68 | 69 | impl Prefetch { 70 | pub fn new(req: T) -> Self { 71 | #[allow(clippy::redundant_closure_for_method_calls)] 72 | let req = from_reply_request(req, |req| req.into()); 73 | PrefetchState::Formatting(Some(req)).into() 74 | } 75 | 76 | /// Get the target if and only if this prefetch has 77 | /// already resolved. 78 | pub(crate) fn get_if_resolved(&self) -> Option<&T::Target> { 79 | match self.state { 80 | PrefetchState::Complete(ref c) => Some(c), 81 | _ => None, 82 | } 83 | } 84 | 85 | /// Format the request. 86 | /// 87 | /// The user may use this in order to format the request. 88 | pub(crate) fn request_to_format(&mut self) -> &mut BufferedRequest { 89 | match self.state { 90 | PrefetchState::Formatting(Some(ref mut req)) => req, 91 | _ => panic!("Prefetch is not formatting"), 92 | } 93 | } 94 | 95 | /// Send with an overridden sequence number. 96 | pub(crate) fn sent_override(&mut self, seq: u64) { 97 | match self.state { 98 | PrefetchState::Sending(..) | PrefetchState::Formatting(..) => { 99 | *self = PrefetchState::Waiting(seq).into(); 100 | } 101 | _ => panic!("Prefetch is not sending or formatting"), 102 | } 103 | } 104 | 105 | /// The sequence number of the reply we're waiting for. 106 | pub(crate) fn sequence(&self) -> u64 { 107 | match self.state { 108 | PrefetchState::Waiting(ref seq) => *seq, 109 | _ => panic!("Prefetch is not waiting"), 110 | } 111 | } 112 | 113 | /// Read in the reply. 114 | pub(crate) fn read_reply(&mut self, reply: RawReply) -> Result<()> { 115 | let reply: T::Reply = reply.into_reply()?; 116 | let mapped = T::map_reply(reply); 117 | *self = PrefetchState::Complete(mapped).into(); 118 | Ok(()) 119 | } 120 | 121 | /// Error out. 122 | pub(crate) fn on_x11_error(&mut self) { 123 | *self = PrefetchState::Complete(T::on_x11_error()).into(); 124 | } 125 | 126 | /// Evaluate the prefetch while blocking. 127 | pub fn evaluate(&mut self, display: &mut impl Display) -> Result<&T::Target> { 128 | // call all functions in order 129 | let request = self.request_to_format(); 130 | let seq = request.take(|request| display.send_request_raw(request))?; 131 | self.sent_override(seq); 132 | match display.wait_for_reply_raw(self.sequence()) { 133 | Ok(reply) => { 134 | self.read_reply(reply)?; 135 | } 136 | Err(e) if e.is_protocol_error() => { 137 | self.on_x11_error(); 138 | } 139 | Err(e) => return Err(e), 140 | }; 141 | Ok(self.get_if_resolved().unwrap()) 142 | } 143 | } 144 | 145 | cfg_async! { 146 | impl Prefetch { 147 | fn not_yet_formatted(&self) -> bool { 148 | matches!(self.state, PrefetchState::Formatting(_)) 149 | } 150 | 151 | fn not_yet_sent(&self) -> bool { 152 | matches!(self.state, PrefetchState::Formatting(_) | PrefetchState::Sending(..)) 153 | } 154 | 155 | fn not_yet_read(&self) -> bool { 156 | !matches!(self.state, PrefetchState::Complete(_)) 157 | } 158 | 159 | /// Indicate that the request has been sent. 160 | pub(crate) fn sent(&mut self) { 161 | match self.state { 162 | PrefetchState::Sending(_, seq) => *self = PrefetchState::Waiting(seq).into(), 163 | _ => panic!("Prefetch is not sending"), 164 | } 165 | } 166 | 167 | /// Indicate that we've formatted the request. 168 | pub(crate) fn formatted(&mut self, seq: u64) { 169 | match self.state { 170 | PrefetchState::Formatting(ref mut request) => { 171 | *self = PrefetchState::Sending(request.take(), seq).into(); 172 | } 173 | _ => panic!("Prefetch is not formatting"), 174 | } 175 | } 176 | 177 | /// Get the request to send. 178 | pub(crate) fn request_to_send(&mut self) -> &mut BufferedRequest { 179 | match self.state { 180 | PrefetchState::Sending(Some(ref mut req), ..) 181 | | PrefetchState::Formatting(Some(ref mut req)) => req, 182 | _ => panic!("Prefetch is not sending"), 183 | } 184 | } 185 | 186 | /// Evaluate the prefetch, but avoid blocking. 187 | pub fn try_evaluate( 188 | &mut self, 189 | display: &mut impl CanBeAsyncDisplay, 190 | ctx: &mut Context<'_>, 191 | ) -> Result> { 192 | let span = tracing::trace_span!( 193 | "try_evaluate", 194 | formatted = !self.not_yet_formatted(), 195 | sent = !self.not_yet_sent(), 196 | read = !self.not_yet_read() 197 | ); 198 | let _enter = span.enter(); 199 | 200 | // call all functions in order 201 | if self.not_yet_formatted() { 202 | tracing::trace!("formatting request"); 203 | let request = self.request_to_format(); 204 | let seq = mtry! { 205 | request.borrow(|request| { 206 | display.format_request(request, ctx) 207 | }) 208 | }; 209 | self.formatted(seq); 210 | } 211 | 212 | if self.not_yet_sent() { 213 | tracing::trace!("sending request"); 214 | let request = self.request_to_send(); 215 | mtry! { 216 | request.borrow(|request| { 217 | display.try_send_request_raw(request, ctx) 218 | }) 219 | }; 220 | self.sent(); 221 | } 222 | 223 | if self.not_yet_read() { 224 | tracing::trace!("receiving reply"); 225 | match display.try_wait_for_reply_raw(self.sequence(), ctx) { 226 | Ok(AsyncStatus::Ready(t)) => self.read_reply(t)?, 227 | Ok(status) => return Ok(status.map(|_| unreachable!())), 228 | Err(e) if e.is_protocol_error() => { 229 | self.on_x11_error(); 230 | } 231 | Err(e) => return Err(e), 232 | } 233 | } 234 | 235 | Ok(AsyncStatus::Ready(self.get_if_resolved().unwrap())) 236 | } 237 | } 238 | } 239 | 240 | // prefetch targets: query for bigreq and query for extension 241 | 242 | impl PrefetchTarget for EnableRequest { 243 | type Target = Option; 244 | 245 | fn map_reply(reply: Self::Reply) -> Self::Target { 246 | let maxlen = reply.maximum_request_length as usize; 247 | Some(maxlen) 248 | } 249 | 250 | fn on_x11_error() -> Self::Target { 251 | None 252 | } 253 | } 254 | 255 | impl<'a> PrefetchTarget for QueryExtensionRequest<'a> { 256 | type Target = Option; 257 | 258 | fn map_reply(reply: Self::Reply) -> Self::Target { 259 | if !reply.present { 260 | return None; 261 | } 262 | 263 | let info = ExtensionInformation { 264 | major_opcode: reply.major_opcode, 265 | first_error: reply.first_error, 266 | first_event: reply.first_event, 267 | }; 268 | 269 | Some(info) 270 | } 271 | 272 | fn on_x11_error() -> Self::Target { 273 | None 274 | } 275 | } 276 | 277 | impl PrefetchTarget for GetInputFocusRequest { 278 | type Target = GetInputFocusReply; 279 | 280 | fn map_reply(reply: Self::Reply) -> Self::Target { 281 | reply 282 | } 283 | 284 | fn on_x11_error() -> Self::Target { 285 | tracing::error!("synchronization should never error out"); 286 | GetInputFocusReply::default() 287 | } 288 | } 289 | 290 | impl PrefetchTarget for GetXIDRangeRequest { 291 | type Target = GetXIDRangeReply; 292 | 293 | fn map_reply(reply: Self::Reply) -> Self::Target { 294 | reply 295 | } 296 | 297 | fn on_x11_error() -> Self::Target { 298 | tracing::error!("XID refresh should never error out"); 299 | GetXIDRangeReply::default() 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /breadx/src/display/sans_io.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use crate::{Error, Fd, InvalidState, Result}; 7 | use alloc::vec::Vec; 8 | use x11rb_protocol::{ 9 | connection::{Connection as ProtoConnection, PollReply, ReplyFdKind}, 10 | id_allocator::{IdAllocator, IdsExhausted}, 11 | protocol::{xc_misc::GetXIDRangeReply, xproto::Setup, Event}, 12 | x11_utils::{ExtInfoProvider, X11Error}, 13 | DiscardMode, 14 | }; 15 | 16 | use super::RawReply; 17 | 18 | /// The core of all of the `breadx` displays. 19 | /// 20 | /// This structure is configured such that all other `Display` implementors 21 | /// build around it. 22 | pub(crate) struct X11Core { 23 | /// The inner sans-I/O implementation of X11. 24 | proto: ProtoConnection, 25 | /// Keeps track of the XIDs we have allocated. 26 | id_allocator: IdAllocator, 27 | } 28 | 29 | impl X11Core { 30 | /// Create a new `X11Core` object wrapping around a `Setup`. 31 | pub(crate) fn from_setup(setup: &Setup) -> Result { 32 | Ok(Self { 33 | proto: ProtoConnection::new(), 34 | id_allocator: IdAllocator::new(setup.resource_id_base, setup.resource_id_mask) 35 | .map_err(Error::make_connect_error)?, 36 | }) 37 | } 38 | 39 | /// Enqueue file descriptors into this connection. 40 | pub(crate) fn enqueue_fds(&mut self, fds: Vec) { 41 | self.proto.enqueue_fds(fds); 42 | } 43 | 44 | /// Enqueue a packet into this connection. 45 | pub(crate) fn enqueue_packet(&mut self, packet: Vec) { 46 | self.proto.enqueue_packet(packet); 47 | } 48 | 49 | #[allow(clippy::unused_self)] 50 | fn try_parse_error(&self, err: Vec, ext_info: &dyn ExtInfoProvider) -> Result> { 51 | // first number has to be zero 52 | if err[0] != 0 { 53 | return Ok(err); 54 | } 55 | 56 | // parse it 57 | let err = X11Error::try_parse(&err, ext_info) 58 | .map_err(|_| Error::make_invalid_state(InvalidState::BadError))?; 59 | 60 | Err(err.into()) 61 | } 62 | 63 | /// Fetch the reply matching the sequence number, if we have it. 64 | pub(crate) fn fetch_reply( 65 | &mut self, 66 | reply: u64, 67 | ext_info: &dyn ExtInfoProvider, 68 | ) -> Result> { 69 | let (buf, fds) = match self.proto.poll_for_reply_or_error(reply) { 70 | Some(a) => a, 71 | None => return Ok(None), 72 | }; 73 | 74 | // try to parse this into an error 75 | let buf = self.try_parse_error(buf, ext_info)?; 76 | 77 | Ok(Some(RawReply::new(buf.into_boxed_slice(), fds))) 78 | } 79 | 80 | /// Can we check to see if this sequence number has errored out? 81 | pub(crate) fn ready_for_error_check(&mut self, reply: u64) -> bool { 82 | self.proto.prepare_check_for_reply_or_error(reply) 83 | } 84 | 85 | /// Check to see if this sequence number has errored out. 86 | /// 87 | /// Returns `true` if the sequence number is ready. Returns an 88 | /// error if it's errored out. 89 | pub(crate) fn check_for_error( 90 | &mut self, 91 | reply: u64, 92 | ext_info: &dyn ExtInfoProvider, 93 | ) -> Result { 94 | match self.proto.poll_check_for_reply_or_error(reply) { 95 | PollReply::NoReply => Ok(true), 96 | PollReply::Reply(buf) => self.try_parse_error(buf, ext_info).map(|_| true), 97 | PollReply::TryAgain => Ok(false), 98 | } 99 | } 100 | 101 | /// Fetch the next event, if we have it. 102 | pub(crate) fn fetch_event(&mut self, ext_info: &dyn ExtInfoProvider) -> Result> { 103 | let (buf, _) = match self.proto.poll_for_event_with_sequence() { 104 | Some(a) => a, 105 | None => return Ok(None), 106 | }; 107 | 108 | let buf = self.try_parse_error(buf, ext_info)?; 109 | 110 | let event = Event::parse(&buf, ext_info).map_err(Error::make_parse_error)?; 111 | 112 | Ok(Some(event)) 113 | } 114 | 115 | pub(crate) fn send_request(&mut self, variant: ReplyFdKind) -> Option { 116 | self.proto.send_request(variant) 117 | } 118 | 119 | pub(crate) fn discard_reply(&mut self, seq: u64, mode: DiscardMode) { 120 | self.proto.discard_reply(seq, mode); 121 | } 122 | 123 | pub(crate) fn generate_xid(&mut self) -> Option { 124 | self.id_allocator.generate_id() 125 | } 126 | 127 | pub(crate) fn update_xid_range( 128 | &mut self, 129 | range: GetXIDRangeReply, 130 | ) -> core::result::Result<(), IdsExhausted> { 131 | self.id_allocator.update_xid_range(&range) 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /breadx/src/display/tests/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | mod sequencing; 7 | pub(crate) use sequencing::*; 8 | 9 | mod setup; 10 | pub(crate) use setup::test_setup; 11 | -------------------------------------------------------------------------------- /breadx/src/display/tests/sequencing.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use super::test_setup; 7 | use crate::{ 8 | connection::with_test_connection, 9 | display::{from_reply_fds_request, from_reply_request, from_void_request, RawRequest}, 10 | Fd, 11 | }; 12 | use alloc::vec::Vec; 13 | use x11rb_protocol::{ 14 | protocol::xproto::SetupRequest, 15 | x11_utils::{ReplyFDsRequest, ReplyRequest, Serialize, TryParseFd, VoidRequest}, 16 | }; 17 | 18 | /// An expected action from either the client or the server. 19 | #[derive(Debug)] 20 | pub(crate) struct Action { 21 | sent_from_client: Vec, 22 | fds_from_client: usize, 23 | sent_from_server: Vec, 24 | fds_from_server: usize, 25 | dummy_max_len: usize, 26 | sequence: u64, 27 | } 28 | 29 | impl Action { 30 | /// Creates a new, empty `Action`. 31 | pub(crate) fn new() -> Self { 32 | Self { 33 | sent_from_client: Vec::new(), 34 | sent_from_server: Vec::new(), 35 | fds_from_client: 0, 36 | fds_from_server: 0, 37 | dummy_max_len: core::u16::MAX as usize * 4, 38 | sequence: 1, 39 | } 40 | } 41 | 42 | /// Receive bytes from the client. 43 | pub(crate) fn sent_from_client(&mut self, bytes: impl AsRef<[u8]>) -> &mut Self { 44 | self.sent_from_client.extend_from_slice(bytes.as_ref()); 45 | self 46 | } 47 | 48 | /// Receive bytes from the server. 49 | pub(crate) fn sent_from_server(&mut self, bytes: impl AsRef<[u8]>) -> &mut Self { 50 | self.sent_from_server.extend_from_slice(bytes.as_ref()); 51 | self 52 | } 53 | 54 | /// Update the maximum length. 55 | pub(crate) fn update_max_len(&mut self, max_len: usize) -> &mut Self { 56 | self.dummy_max_len = max_len; 57 | self 58 | } 59 | 60 | /// Indicate that the client should send a raw request. 61 | pub(crate) fn raw_request(&mut self, mut raw_req: RawRequest<'_, '_>) -> &mut Self { 62 | let (slices, fds) = raw_req.mut_parts(); 63 | 64 | for (i, slice) in slices.iter().enumerate() { 65 | self.sent_from_client.extend_from_slice(slice); 66 | } 67 | 68 | self.fds_from_client += fds.len(); 69 | 70 | self 71 | } 72 | 73 | /// Indicate that the client should send a request with a void 74 | /// reply. 75 | pub(crate) fn void_request( 76 | &mut self, 77 | void: impl VoidRequest, 78 | discard_reply: bool, 79 | ext_opcode: Option, 80 | ) -> &mut Self { 81 | from_void_request(void, discard_reply, move |mut req| { 82 | req.format(ext_opcode, self.dummy_max_len) 83 | .expect("Failed to format request"); 84 | self.raw_request(req) 85 | }) 86 | } 87 | 88 | /// Indicate that the client should send a request with a reply. 89 | pub(crate) fn reply_request( 90 | &mut self, 91 | reply: impl ReplyRequest, 92 | ext_opcode: Option, 93 | ) -> &mut Self { 94 | from_reply_request(reply, move |mut req| { 95 | req.format(ext_opcode, self.dummy_max_len) 96 | .expect("Failed to format request"); 97 | self.raw_request(req) 98 | }) 99 | } 100 | 101 | /// Indicate that the client should send a request with a reply 102 | /// and FDs. 103 | pub(crate) fn reply_fds_request( 104 | &mut self, 105 | reply: impl ReplyFDsRequest, 106 | ext_opcode: Option, 107 | ) -> &mut Self { 108 | from_reply_fds_request(reply, move |mut req| { 109 | req.format(ext_opcode, self.dummy_max_len) 110 | .expect("Failed to format request"); 111 | self.raw_request(req) 112 | }) 113 | } 114 | 115 | /// Write a setup request from the client. 116 | pub(crate) fn setup_request(&mut self, sr: &SetupRequest) -> &mut Self { 117 | let bytes = sr.serialize(); 118 | self.sent_from_client.extend_from_slice(&bytes); 119 | self 120 | } 121 | 122 | /// Write the testing setup from the server. 123 | pub(crate) fn respond_with_setup(&mut self) -> &mut Self { 124 | let bytes = test_setup().serialize(); 125 | self.sent_from_server.extend_from_slice(&bytes); 126 | self 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /breadx/src/futures/check_for_error.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use super::TryWithDyn; 7 | use crate::{ 8 | display::{AsyncDisplay, AsyncDisplayExt, AsyncStatus}, 9 | Result, 10 | }; 11 | use alloc::boxed::Box; 12 | use core::{ 13 | future::Future, 14 | pin::Pin, 15 | task::{Context, Poll}, 16 | }; 17 | use futures_util::FutureExt; 18 | 19 | /// The future returned by the `check_for_error` function. 20 | pub struct CheckForError<'this, Dpy: ?Sized> { 21 | innards: TryWithDyn<'this, (), Dpy>, 22 | } 23 | 24 | type FnTy = Box< 25 | dyn FnMut(&mut dyn AsyncDisplay, &mut Context<'_>) -> Result> + Send + 'static, 26 | >; 27 | 28 | impl<'this, Dpy: AsyncDisplay + ?Sized> CheckForError<'this, Dpy> { 29 | pub(crate) fn polling(display: &'this mut Dpy, seq: u64) -> Self { 30 | // setup the function 31 | let func: FnTy = Box::new(move |display, ctx| display.try_check_for_error(seq, ctx)); 32 | 33 | let try_with = display.try_with(func); 34 | 35 | Self { innards: try_with } 36 | } 37 | 38 | pub(crate) fn cannibalize(self) -> &'this mut Dpy { 39 | self.innards.cannibalize() 40 | } 41 | } 42 | 43 | impl<'this, Dpy: AsyncDisplay + ?Sized> Future for CheckForError<'this, Dpy> { 44 | type Output = Result<()>; 45 | 46 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 47 | let this = self.get_mut(); 48 | 49 | this.innards.poll_unpin(ctx) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /breadx/src/futures/checked.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use core::{mem, task::Poll}; 7 | use futures_util::{Future, FutureExt}; 8 | use tracing::Span; 9 | use x11rb_protocol::x11_utils::TryParseFd; 10 | 11 | use super::{SendRequest, WaitForReply}; 12 | use crate::{ 13 | display::{AsyncDisplay, AsyncDisplayExt}, 14 | Result, 15 | }; 16 | 17 | /// A wrapper around [`SendRequest`] that immediately resolves the cookie. 18 | pub struct CheckedSendRequest<'this, Dpy: ?Sized, Reply> { 19 | inner: Innards<'this, Dpy, Reply>, 20 | span: Option, 21 | } 22 | 23 | enum Innards<'this, Dpy: ?Sized, Reply> { 24 | SendRequest(SendRequest<'this, Dpy, Reply>), 25 | WaitForReply(WaitForReply<'this, Dpy, Reply>), 26 | Hole, 27 | } 28 | 29 | impl<'this, Dpy: ?Sized, Reply> From> 30 | for CheckedSendRequest<'this, Dpy, Reply> 31 | { 32 | fn from(mut inner: SendRequest<'this, Dpy, Reply>) -> Self { 33 | Self { 34 | span: inner.take_span(), 35 | inner: Innards::SendRequest(inner), 36 | } 37 | } 38 | } 39 | 40 | impl<'this, Dpy: AsyncDisplay + ?Sized, Reply: Unpin + TryParseFd> Future 41 | for CheckedSendRequest<'this, Dpy, Reply> 42 | { 43 | type Output = Result; 44 | 45 | fn poll( 46 | self: core::pin::Pin<&mut Self>, 47 | ctx: &mut core::task::Context<'_>, 48 | ) -> Poll { 49 | let this = self.get_mut(); 50 | 51 | // take the span 52 | let _enter = this.span.as_ref().map(Span::enter); 53 | 54 | loop { 55 | match this.inner { 56 | Innards::SendRequest(ref mut inner) => { 57 | match inner.poll_unpin(ctx) { 58 | Poll::Ready(Ok(cookie)) => { 59 | // get the display references 60 | let display_ref = match mem::replace(&mut this.inner, Innards::Hole) { 61 | Innards::SendRequest(inner) => inner.cannibalize(), 62 | _ => unreachable!(), 63 | }; 64 | 65 | this.inner = Innards::WaitForReply(display_ref.wait_for_reply(cookie)); 66 | } 67 | res => return res.map_ok(|_| unreachable!()), 68 | } 69 | } 70 | Innards::WaitForReply(ref mut wfr) => return wfr.poll_unpin(ctx), 71 | Innards::Hole => unreachable!("cannot poll an empty hole"), 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /breadx/src/futures/flush.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use super::TryWithDyn; 7 | use crate::{ 8 | display::{AsyncDisplay, AsyncDisplayExt, AsyncStatus}, 9 | Result, 10 | }; 11 | use alloc::boxed::Box; 12 | use core::{ 13 | future::Future, 14 | pin::Pin, 15 | task::{Context, Poll}, 16 | }; 17 | use futures_util::FutureExt; 18 | 19 | /// The future returned by the `flush()` function. 20 | pub struct Flush<'this, Dpy: ?Sized> { 21 | innards: TryWithDyn<'this, (), Dpy>, 22 | } 23 | 24 | type FnTy = Box< 25 | dyn FnMut(&mut dyn AsyncDisplay, &mut Context<'_>) -> Result> + Send + 'static, 26 | >; 27 | 28 | impl<'this, Dpy: AsyncDisplay + ?Sized> Flush<'this, Dpy> { 29 | #[allow(clippy::redundant_closure_for_method_calls)] 30 | pub(crate) fn polling(display: &'this mut Dpy) -> Self { 31 | let func: FnTy = Box::new(|display, ctx| display.try_flush(ctx)); 32 | let try_with = display.try_with(func); 33 | 34 | Self { innards: try_with } 35 | } 36 | } 37 | 38 | impl<'this, Dpy: AsyncDisplay + ?Sized> Future for Flush<'this, Dpy> { 39 | type Output = Result<()>; 40 | 41 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 42 | let this = self.get_mut(); 43 | this.innards.poll_unpin(ctx) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /breadx/src/futures/generate_xid.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use super::TryWithDyn; 7 | use crate::{ 8 | display::{AsyncDisplay, AsyncDisplayExt, AsyncStatus}, 9 | Result, 10 | }; 11 | use alloc::boxed::Box; 12 | use core::{ 13 | future::Future, 14 | pin::Pin, 15 | task::{Context, Poll}, 16 | }; 17 | use futures_util::FutureExt; 18 | 19 | /// The future returned by the `generate_xid` function. 20 | pub struct GenerateXid<'this, Dpy: ?Sized> { 21 | innards: TryWithDyn<'this, u32, Dpy>, 22 | } 23 | 24 | type FnTy = Box< 25 | dyn FnMut(&mut dyn AsyncDisplay, &mut Context<'_>) -> Result> + Send + 'static, 26 | >; 27 | 28 | impl<'this, Dpy: AsyncDisplay + ?Sized> GenerateXid<'this, Dpy> { 29 | #[allow(clippy::redundant_closure_for_method_calls)] 30 | pub(crate) fn polling(display: &'this mut Dpy) -> Self { 31 | let func: FnTy = Box::new(|display, ctx| display.try_generate_xid(ctx)); 32 | let try_with = display.try_with(func); 33 | 34 | Self { innards: try_with } 35 | } 36 | } 37 | 38 | impl<'this, Dpy: AsyncDisplay + ?Sized> Future for GenerateXid<'this, Dpy> { 39 | type Output = Result; 40 | 41 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 42 | let this = self.get_mut(); 43 | this.innards.poll_unpin(ctx) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /breadx/src/futures/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! Futures that are used throughout the program. 7 | //! 8 | //! In `breadx`, we manually create our own futures instead of using 9 | //! `async` syntax, in order to allow traits to return either thread-safe or 10 | //! thread-unsafe futures, based on the flavor of [`Display`] being used. 11 | //! These futures are stored here. 12 | //! 13 | //! [`Display`]: crate::display::Display 14 | 15 | mod check_for_error; 16 | pub use check_for_error::*; 17 | 18 | mod checked; 19 | pub use checked::*; 20 | 21 | mod flush; 22 | pub use flush::*; 23 | 24 | mod generate_xid; 25 | pub use generate_xid::*; 26 | 27 | mod send_request; 28 | pub use send_request::*; 29 | 30 | mod send_request_raw; 31 | pub use send_request_raw::*; 32 | 33 | mod synchronize; 34 | pub use synchronize::*; 35 | 36 | mod try_with; 37 | pub use try_with::*; 38 | 39 | mod wait_for_event; 40 | pub use wait_for_event::*; 41 | 42 | mod wait_for_reply; 43 | pub use wait_for_reply::*; 44 | 45 | mod wait_for_reply_raw; 46 | pub use wait_for_reply_raw::*; 47 | -------------------------------------------------------------------------------- /breadx/src/futures/send_request.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use super::SendRequestRaw; 7 | use crate::{ 8 | display::{ 9 | from_reply_fds_request, from_reply_request, from_void_request, AsyncDisplay, 10 | AsyncDisplayExt, Cookie, 11 | }, 12 | Result, 13 | }; 14 | use core::{ 15 | future::Future, 16 | marker::PhantomData, 17 | pin::Pin, 18 | task::{Context, Poll}, 19 | }; 20 | use futures_util::future::FutureExt; 21 | use tracing::Span; 22 | use x11rb_protocol::x11_utils::{ReplyFDsRequest, ReplyRequest, VoidRequest}; 23 | 24 | /// The future returned by the `send_request` method. 25 | pub struct SendRequest<'this, Dpy: ?Sized, Reply> { 26 | innards: SendRequestRaw<'this, Dpy>, 27 | span: Option, 28 | _marker: PhantomData, 29 | } 30 | 31 | impl<'this, Dpy: AsyncDisplay + ?Sized> SendRequest<'this, Dpy, ()> { 32 | pub(crate) fn for_void( 33 | display: &'this mut Dpy, 34 | discard_reply: bool, 35 | req: Req, 36 | ) -> Self { 37 | from_void_request(req, discard_reply, move |req| Self { 38 | innards: display.send_request_raw(req), 39 | span: None, 40 | _marker: PhantomData, 41 | }) 42 | } 43 | } 44 | 45 | impl<'this, Dpy: AsyncDisplay + ?Sized, Reply> SendRequest<'this, Dpy, Reply> { 46 | pub(crate) fn for_reply>( 47 | display: &'this mut Dpy, 48 | req: Req, 49 | ) -> Self { 50 | from_reply_request(req, move |req| Self { 51 | innards: display.send_request_raw(req), 52 | span: None, 53 | _marker: PhantomData, 54 | }) 55 | } 56 | 57 | pub(crate) fn for_reply_fds>( 58 | display: &'this mut Dpy, 59 | req: Req, 60 | ) -> Self { 61 | from_reply_fds_request(req, move |req| Self { 62 | innards: display.send_request_raw(req), 63 | span: None, 64 | _marker: PhantomData, 65 | }) 66 | } 67 | 68 | pub(crate) fn cannibalize(self) -> &'this mut Dpy { 69 | self.innards.cannibalize() 70 | } 71 | } 72 | 73 | impl<'this, Dpy: ?Sized, Reply> SendRequest<'this, Dpy, Reply> { 74 | pub(crate) fn with_span(mut self, span: Span) -> Self { 75 | self.span = Some(span); 76 | self 77 | } 78 | 79 | pub(crate) fn take_span(&mut self) -> Option { 80 | self.span.take() 81 | } 82 | } 83 | 84 | impl<'this, Dpy: AsyncDisplay + ?Sized, Reply: Unpin> Future for SendRequest<'this, Dpy, Reply> { 85 | type Output = Result>; 86 | 87 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll>> { 88 | let this = self.get_mut(); 89 | 90 | // enter the span 91 | let _enter = this.span.as_ref().map(Span::enter); 92 | 93 | // call the innards 94 | this.innards 95 | .poll_unpin(ctx) 96 | .map_ok(|seq| Cookie::from_sequence(seq)) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /breadx/src/futures/send_request_raw.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use super::TryWith; 7 | use crate::{ 8 | display::{AsyncDisplay, AsyncDisplayExt, AsyncStatus, BufferedRequest, RawRequest}, 9 | Result, 10 | }; 11 | use alloc::boxed::Box; 12 | use core::{ 13 | future::Future, 14 | pin::Pin, 15 | task::{Context, Poll}, 16 | }; 17 | use futures_util::FutureExt; 18 | 19 | /// The future returned by the `send_request_raw` function. 20 | pub struct SendRequestRaw<'this, Dpy: ?Sized> { 21 | innards: TryWith<'this, u64, FnTy, Dpy>, 22 | } 23 | 24 | type FnTy = Box< 25 | dyn FnMut(&mut dyn AsyncDisplay, &mut Context<'_>) -> Result> + Send + 'static, 26 | >; 27 | 28 | impl<'this, Dpy: AsyncDisplay + ?Sized> SendRequestRaw<'this, Dpy> { 29 | pub(crate) fn polling(display: &'this mut Dpy, request: RawRequest<'_, '_>) -> Self { 30 | // buffer the request 31 | // TODO: work through the lifetimes here, this can't be the most efficient 32 | // way of doing it 33 | // but it's gotta somehow work with SendRequest<>, selfref or boxing async{} 34 | // may be the only option but each has caveats 35 | let mut request: BufferedRequest = request.into(); 36 | 37 | // set up the function 38 | let mut sequence = None; 39 | let func: FnTy = Box::new(move |display, ctx| { 40 | loop { 41 | match sequence { 42 | None => { 43 | // calculate the sequence number 44 | match request.borrow(|request| display.format_request(request, ctx)) { 45 | Ok(AsyncStatus::Ready(seq)) => sequence = Some(seq), 46 | Ok(status) => return Ok(status.map(|_| unreachable!())), 47 | Err(e) => return Err(e), 48 | } 49 | } 50 | Some(seq) => { 51 | return request 52 | .borrow(|request| display.try_send_request_raw(request, ctx)) 53 | .map(move |res| res.map(move |()| seq)); 54 | } 55 | } 56 | } 57 | }); 58 | 59 | let try_with = display.try_with(func); 60 | 61 | Self { innards: try_with } 62 | } 63 | 64 | /// Destroys this future and returns the inner display reference. 65 | pub(crate) fn cannibalize(self) -> &'this mut Dpy { 66 | self.innards.cannibalize() 67 | } 68 | } 69 | 70 | impl<'this, Dpy: AsyncDisplay + ?Sized> Future for SendRequestRaw<'this, Dpy> { 71 | type Output = Result; 72 | 73 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 74 | let this = self.get_mut(); 75 | 76 | this.innards.poll_unpin(ctx) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /breadx/src/futures/synchronize.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use core::{ 7 | mem, 8 | pin::Pin, 9 | task::{Context, Poll}, 10 | }; 11 | use futures_util::{Future, FutureExt}; 12 | use x11rb_protocol::protocol::xproto::GetInputFocusRequest; 13 | 14 | use crate::{ 15 | display::{from_reply_request, AsyncDisplay, AsyncDisplayExt}, 16 | Result, 17 | }; 18 | 19 | use super::{SendRequestRaw, WaitForReplyRaw}; 20 | 21 | /// Future for the "synchronize" operation. 22 | pub struct Synchronize<'this, Dpy: ?Sized> { 23 | innards: Innards<'this, Dpy>, 24 | } 25 | 26 | enum Innards<'this, Dpy: ?Sized> { 27 | Sending(SendRequestRaw<'this, Dpy>), 28 | Waiting(WaitForReplyRaw<'this, Dpy>), 29 | Hole, 30 | } 31 | 32 | impl<'this, Dpy: AsyncDisplay + ?Sized> Synchronize<'this, Dpy> { 33 | pub(crate) fn new(dpy: &'this mut Dpy) -> Self { 34 | let req = GetInputFocusRequest {}; 35 | 36 | from_reply_request(req, move |req| Self { 37 | innards: Innards::Sending(dpy.send_request_raw(req)), 38 | }) 39 | } 40 | } 41 | 42 | impl<'this, Dpy: AsyncDisplay + ?Sized> Future for Synchronize<'this, Dpy> { 43 | type Output = Result<()>; 44 | 45 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 46 | let this = self.get_mut(); 47 | 48 | loop { 49 | match this.innards { 50 | Innards::Sending(ref mut send_request_raw) => { 51 | match send_request_raw.poll_unpin(ctx) { 52 | Poll::Ready(Ok(seq)) => { 53 | // plug the seq into wait_for_reply_raw 54 | let display_ref = match mem::replace(&mut this.innards, Innards::Hole) { 55 | Innards::Sending(send_request_raw) => { 56 | send_request_raw.cannibalize() 57 | } 58 | _ => unreachable!(), 59 | }; 60 | 61 | this.innards = Innards::Waiting(display_ref.wait_for_reply_raw(seq)); 62 | } 63 | res => return res.map_ok(|_| ()), 64 | } 65 | } 66 | Innards::Waiting(ref mut wait_for_reply_raw) => { 67 | return wait_for_reply_raw.poll_unpin(ctx).map_ok(|_| ()); 68 | } 69 | Innards::Hole => unreachable!("cannot poll an empty hole"), 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /breadx/src/futures/try_with.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use crate::{ 7 | display::{AsyncDisplay, AsyncStatus, Interest}, 8 | Result, 9 | }; 10 | use alloc::boxed::Box; 11 | use core::{ 12 | future::Future, 13 | marker::PhantomData, 14 | pin::Pin, 15 | task::{Context, Poll}, 16 | }; 17 | 18 | /// The future returned by `try_with()`. 19 | pub struct TryWith<'this, R, F, Dpy: ?Sized> { 20 | dpy: &'this mut Dpy, 21 | callback: F, 22 | straight_call: bool, 23 | interest: Option, 24 | 25 | _marker: PhantomData<&'this mut R>, 26 | } 27 | 28 | pub(crate) type TryWithDyn<'this, R, Dpy> = TryWith< 29 | 'this, 30 | R, 31 | Box< 32 | dyn FnMut(&mut dyn AsyncDisplay, &mut Context<'_>) -> Result> 33 | + Send 34 | + 'static, 35 | >, 36 | Dpy, 37 | >; 38 | 39 | impl<'this, R, F, Dpy: ?Sized> TryWith<'this, R, F, Dpy> { 40 | /// Creates a new `TryWith` future. 41 | pub(crate) fn new(dpy: &'this mut Dpy, callback: F) -> Self { 42 | Self { 43 | dpy, 44 | callback, 45 | straight_call: true, 46 | interest: None, 47 | _marker: PhantomData, 48 | } 49 | } 50 | 51 | /// Destroys this object and returns the inner `Dpy` reference. 52 | pub(crate) fn cannibalize(self) -> &'this mut Dpy { 53 | self.dpy 54 | } 55 | } 56 | 57 | impl< 58 | 'this, 59 | R, 60 | F: FnMut(&mut dyn AsyncDisplay, &mut Context<'_>) -> Result> + Unpin, 61 | Dpy: AsyncDisplay + ?Sized, 62 | > Future for TryWith<'this, R, F, Dpy> 63 | { 64 | type Output = Result; 65 | 66 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 67 | let mut this = self.get_mut(); 68 | 69 | debug_assert!(this.straight_call || this.interest.is_some()); 70 | 71 | // if we haven't tried to straight-call the callback, try it now 72 | loop { 73 | if this.straight_call { 74 | this.straight_call = false; 75 | 76 | tracing::trace!("trying straight call"); 77 | 78 | match (this.callback)(&mut this.dpy, ctx) { 79 | Ok(AsyncStatus::Ready(r)) => return Poll::Ready(Ok(r)), 80 | Ok(AsyncStatus::Read) => this.interest = Some(Interest::Readable), 81 | Ok(AsyncStatus::Write) => this.interest = Some(Interest::Writable), 82 | Ok(AsyncStatus::UserControlled) => { 83 | this.straight_call = true; 84 | } 85 | Err(e) => return Poll::Ready(Err(e)), 86 | } 87 | } 88 | 89 | // if we have an interest to poll, poll it 90 | if let Some(interest) = this.interest { 91 | tracing::trace!("re-polling for interest {:?}", interest); 92 | 93 | let mut res: Option> = None; 94 | 95 | // try the polling process 96 | let callback = &mut this.callback; 97 | 98 | match this.dpy.poll_for_interest( 99 | interest, 100 | &mut |dpy, ctx| { 101 | (callback)(dpy, ctx).and_then(|status| { 102 | // create a would_block error if the status is not ready 103 | let is_ready = status.is_ready(); 104 | res = Some(status); 105 | 106 | if is_ready { 107 | Ok(()) 108 | } else { 109 | Err(crate::Error::io(std::io::ErrorKind::WouldBlock.into())) 110 | } 111 | }) 112 | }, 113 | ctx, 114 | ) { 115 | Poll::Pending => return Poll::Pending, 116 | Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), 117 | Poll::Ready(Ok(())) => { 118 | // match on the status 119 | match res.take().expect("malicious poll_for_interest impl") { 120 | AsyncStatus::Ready(r) => return Poll::Ready(Ok(r)), 121 | AsyncStatus::Read => this.interest = Some(Interest::Readable), 122 | AsyncStatus::Write => this.interest = Some(Interest::Writable), 123 | AsyncStatus::UserControlled => { 124 | this.straight_call = true; 125 | } 126 | } 127 | } 128 | } 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /breadx/src/futures/wait_for_event.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use super::TryWith; 7 | use crate::{ 8 | display::{AsyncDisplay, AsyncDisplayExt, AsyncStatus}, 9 | Result, 10 | }; 11 | use core::{ 12 | future::Future, 13 | pin::Pin, 14 | task::{Context, Poll}, 15 | }; 16 | use futures_util::FutureExt; 17 | use x11rb_protocol::protocol::Event; 18 | 19 | /// The future returned by the `wait_for_event` function. 20 | pub struct WaitForEvent<'this, Dpy: ?Sized> { 21 | innards: TryWith<'this, Event, FnTy, Dpy>, 22 | } 23 | 24 | type FnTy = fn(&mut dyn AsyncDisplay, &mut Context<'_>) -> Result>; 25 | 26 | impl<'this, Dpy: AsyncDisplay + ?Sized> WaitForEvent<'this, Dpy> { 27 | #[allow(clippy::redundant_closure_for_method_calls)] 28 | pub(crate) fn polling(display: &'this mut Dpy) -> Self { 29 | let func: FnTy = |display, ctx| display.try_wait_for_event(ctx); 30 | let try_with = display.try_with(func); 31 | 32 | Self { innards: try_with } 33 | } 34 | } 35 | 36 | impl<'this, Dpy: AsyncDisplay + ?Sized> Future for WaitForEvent<'this, Dpy> { 37 | type Output = Result; 38 | 39 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 40 | let this = self.get_mut(); 41 | this.innards.poll_unpin(ctx) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /breadx/src/futures/wait_for_reply.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use super::{CheckForError, WaitForReplyRaw}; 7 | use crate::{ 8 | display::{AsyncDisplay, AsyncDisplayExt, Cookie}, 9 | Result, 10 | }; 11 | use alloc::vec::Vec; 12 | use core::{ 13 | future::Future, 14 | marker::PhantomData, 15 | mem, 16 | pin::Pin, 17 | task::{Context, Poll}, 18 | }; 19 | use futures_util::FutureExt; 20 | use x11rb_protocol::x11_utils::TryParseFd; 21 | 22 | /// The future returned by the `wait_for_reply` function. 23 | pub struct WaitForReply<'this, Dpy: ?Sized, Reply> { 24 | innards: Innards<'this, Dpy>, 25 | _marker: PhantomData, 26 | } 27 | 28 | enum Innards<'this, Dpy: ?Sized> { 29 | Waiting(WaitForReplyRaw<'this, Dpy>), 30 | Checking(CheckForError<'this, Dpy>), 31 | } 32 | 33 | impl<'this, Dpy: AsyncDisplay + ?Sized, Reply> WaitForReply<'this, Dpy, Reply> { 34 | pub(crate) fn new(dpy: &'this mut Dpy, cookie: Cookie) -> Self { 35 | Self { 36 | innards: if mem::size_of::() == 0 { 37 | Innards::Checking(dpy.check_for_error(cookie.sequence())) 38 | } else { 39 | Innards::Waiting(dpy.wait_for_reply_raw(cookie.sequence())) 40 | }, 41 | _marker: PhantomData, 42 | } 43 | } 44 | 45 | pub(crate) fn cannibalize(self) -> &'this mut Dpy { 46 | match self.innards { 47 | Innards::Waiting(innards) => innards.cannibalize(), 48 | Innards::Checking(innards) => innards.cannibalize(), 49 | } 50 | } 51 | } 52 | 53 | impl<'this, Dpy: AsyncDisplay + ?Sized, Reply: TryParseFd + Unpin> Future 54 | for WaitForReply<'this, Dpy, Reply> 55 | { 56 | type Output = Result; 57 | 58 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 59 | let this = self.get_mut(); 60 | 61 | // determine how it goes down 62 | match this.innards { 63 | Innards::Waiting(ref mut wait_for_reply_raw) => { 64 | match wait_for_reply_raw.poll_unpin(ctx) { 65 | Poll::Ready(Ok(reply)) => Poll::Ready({ 66 | // try to resolve the reply 67 | reply.into_reply() 68 | }), 69 | Poll::Ready(Err(e)) => Poll::Ready(Err(e)), 70 | Poll::Pending => Poll::Pending, 71 | } 72 | } 73 | Innards::Checking(ref mut check) => check.poll_unpin(ctx).map_ok(|()| { 74 | Reply::try_parse_fd(&[], &mut Vec::new()) 75 | .unwrap_or_else(|_| unreachable!()) 76 | .0 77 | }), 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /breadx/src/futures/wait_for_reply_raw.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use super::TryWithDyn; 7 | use crate::{ 8 | display::{AsyncDisplay, AsyncDisplayExt, AsyncStatus, RawReply}, 9 | Result, 10 | }; 11 | use alloc::boxed::Box; 12 | use core::{ 13 | future::Future, 14 | pin::Pin, 15 | task::{Context, Poll}, 16 | }; 17 | use futures_util::FutureExt; 18 | 19 | /// The future returned by the `wait_for_reply_raw` function. 20 | pub struct WaitForReplyRaw<'this, Dpy: ?Sized> { 21 | innards: TryWithDyn<'this, RawReply, Dpy>, 22 | } 23 | 24 | type FnTy = Box< 25 | dyn FnMut(&mut dyn AsyncDisplay, &mut Context<'_>) -> Result> 26 | + Send 27 | + 'static, 28 | >; 29 | 30 | impl<'this, Dpy: AsyncDisplay + ?Sized> WaitForReplyRaw<'this, Dpy> { 31 | pub(crate) fn polling(display: &'this mut Dpy, seq: u64) -> Self { 32 | // setup the function 33 | let mut flushed = false; 34 | let func: FnTy = Box::new(move |display, ctx| { 35 | if !flushed { 36 | match display.try_flush(ctx) { 37 | Ok(AsyncStatus::Ready(())) => { 38 | flushed = true; 39 | } 40 | Ok(status) => { 41 | return Ok(status.map(|()| unreachable!())); 42 | } 43 | Err(e) => { 44 | return Err(e); 45 | } 46 | } 47 | } 48 | 49 | display.try_wait_for_reply_raw(seq, ctx) 50 | }); 51 | 52 | let try_with = display.try_with(func); 53 | 54 | Self { innards: try_with } 55 | } 56 | 57 | pub(crate) fn cannibalize(self) -> &'this mut Dpy { 58 | self.innards.cannibalize() 59 | } 60 | } 61 | 62 | impl<'this, Dpy: AsyncDisplay + ?Sized> Future for WaitForReplyRaw<'this, Dpy> { 63 | type Output = Result; 64 | 65 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { 66 | let this = self.get_mut(); 67 | 68 | this.innards.poll_unpin(ctx) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /breadx/src/gates.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #[cfg(feature = "std")] 7 | #[doc(hidden)] 8 | #[macro_export] 9 | macro_rules! cfg_std { 10 | ($($i:item)*) => {$($i)*}; 11 | } 12 | 13 | #[cfg(not(feature = "std"))] 14 | #[doc(hidden)] 15 | #[macro_export] 16 | macro_rules! cfg_std { 17 | ($($i:item)*) => {}; 18 | } 19 | 20 | #[cfg(feature = "std")] 21 | #[doc(hidden)] 22 | #[macro_export] 23 | macro_rules! cfg_no_std { 24 | ($($i:item)*) => {}; 25 | } 26 | 27 | #[cfg(not(feature = "std"))] 28 | #[doc(hidden)] 29 | #[macro_export] 30 | macro_rules! cfg_no_std { 31 | ($($i:item)*) => {$($i)*}; 32 | } 33 | 34 | #[cfg(all(feature = "std", unix))] 35 | #[doc(hidden)] 36 | #[macro_export] 37 | macro_rules! cfg_std_unix { 38 | ($($i:item)*) => {$($i)*}; 39 | } 40 | 41 | #[cfg(not(all(feature = "std", unix)))] 42 | #[doc(hidden)] 43 | #[macro_export] 44 | macro_rules! cfg_std_unix { 45 | ($($i:item)*) => {}; 46 | } 47 | 48 | #[cfg(all(feature = "std", windows))] 49 | #[doc(hidden)] 50 | #[macro_export] 51 | macro_rules! cfg_std_windows { 52 | ($($i:item)*) => {$($i)*}; 53 | } 54 | 55 | #[cfg(not(all(feature = "std", windows)))] 56 | #[doc(hidden)] 57 | #[macro_export] 58 | macro_rules! cfg_std_windows { 59 | ($($i:item)*) => {}; 60 | } 61 | 62 | #[cfg(feature = "async")] 63 | #[doc(hidden)] 64 | #[macro_export] 65 | macro_rules! cfg_async { 66 | ($($i:item)*) => {$($i)*}; 67 | } 68 | 69 | #[cfg(not(feature = "async"))] 70 | #[doc(hidden)] 71 | #[macro_export] 72 | macro_rules! cfg_async { 73 | ($($i:item)*) => {}; 74 | } 75 | 76 | #[cfg(feature = "sync_display")] 77 | #[doc(hidden)] 78 | #[macro_export] 79 | macro_rules! cfg_sync { 80 | ($($i:item)*) => {$($i)*}; 81 | } 82 | 83 | #[cfg(not(feature = "sync_display"))] 84 | #[doc(hidden)] 85 | #[macro_export] 86 | macro_rules! cfg_sync { 87 | ($($i:item)*) => {}; 88 | } 89 | 90 | #[cfg(feature = "pl")] 91 | #[doc(hidden)] 92 | #[macro_export] 93 | macro_rules! cfg_pl { 94 | ($($i:item)*) => {$($i)*}; 95 | } 96 | 97 | #[cfg(not(feature = "pl"))] 98 | #[doc(hidden)] 99 | #[macro_export] 100 | macro_rules! cfg_pl { 101 | ($($i:item)*) => {}; 102 | } 103 | 104 | #[cfg(not(feature = "pl"))] 105 | #[doc(hidden)] 106 | #[macro_export] 107 | macro_rules! cfg_not_pl { 108 | ($($i:item)*) => {$($i)*}; 109 | } 110 | 111 | #[cfg(feature = "pl")] 112 | #[doc(hidden)] 113 | #[macro_export] 114 | macro_rules! cfg_not_pl { 115 | ($($i:item)*) => {}; 116 | } 117 | 118 | #[cfg(test)] 119 | #[doc(hidden)] 120 | #[macro_export] 121 | macro_rules! cfg_test { 122 | ($($i:item)*) => {$($i)*}; 123 | } 124 | 125 | #[cfg(not(test))] 126 | #[doc(hidden)] 127 | #[macro_export] 128 | macro_rules! cfg_test { 129 | ($($i:item)*) => {}; 130 | } 131 | -------------------------------------------------------------------------------- /breadx/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! `breadx` is a comprehensive implementation of the [X11 client protocol] 7 | //! with an aim to be featureful and powerful, but also easy to use. 8 | //! 9 | //! `breadx` aims to be a minimal implementation of the X11 protocol that 10 | //! can be used in any case where a client is needed. `breadx` comes built 11 | //! in with the following features: 12 | //! 13 | //! - **Comprehensive:** `breadx` has first class support for all X11 protocol 14 | //! extensions. These extensions can be enabled and disabled as features. 15 | //! - **Lock-free**: The [default connection implementation] uses no locks and 16 | //! no waiting outside of standard I/O primitives. The goal is to ensure that 17 | //! there are as few layers as possible between the user's intended goal and 18 | //! actually sending data to the server. 19 | //! - **Safe**: `breadx` has `#[forbid(unsafe_code)]`, which means that there never 20 | //! will be any unsafe code in `breadx`. This means that `breadx` will never be 21 | //! the cause of any undefined behavior. 22 | //! - **Versatile:** For cases where sharing the connection is necessary, `breadx` 23 | //! provides [thread unsafe] and [thread safe] variants. 24 | //! - **`no_std`:** By disabling the `std` feature, `breadx` can be used 25 | //! without depending on the standard library. 26 | //! - **Asynchronous:** With the `async` feature enabled, `breadx`'s primitives 27 | //! can be used in asynchronous contexts. By default, `breadx` is runtime-agnostic, 28 | //! but support can be enabled for [`tokio`] and [`async-std`]. 29 | //! - **Simple:** With all of this, a client can be created and used in `breadx` 30 | //! in very few lines of code. 31 | //! 32 | //! Features that `breadx` does not provide: 33 | //! 34 | //! - **Data Manipulation** - APIs to make image manipulation/ICCCM/etc easier are 35 | //! located in other crates. 36 | //! - **Interfacing with Xlib/XCB** - `breadx` does not provide a way to interact 37 | //! with Xlib/XCB directly. 38 | //! 39 | //! [X11 client protocol]: https://en.wikipedia.org/wiki/X_Window_System 40 | //! [default connection implementation]: crate::display::BasicDisplay 41 | //! [thread unsafe]: crate::display::CellDisplay 42 | //! [thread safe]: crate::display::SyncDisplay 43 | //! [`tokio`]: crate::rt_support::tokio_support 44 | //! [`async-std`]: crate::rt_support::async_std_support 45 | //! 46 | //! # Usage 47 | //! 48 | //! All functions in `breadx` exist in the context of a [`Display`]. There are 49 | //! many ways to create a [`Display`], but in most cases, [`DisplayConnection::connect()`] 50 | //! will connect to the currently running X11 server without any fuss. 51 | //! 52 | //! From there, most functions that are actually used in `breadx` exist on the 53 | //! [`DisplayFunctionsExt`] extension trait. 54 | //! 55 | //! [`Display`]: crate::display::Display 56 | //! [`DisplayConnection::connect()`]: crate::display::DisplayConnection::connect 57 | //! [`DisplayFunctionsExt`]: crate::display::DisplayFunctionsExt 58 | //! 59 | //! ```rust,no_run 60 | //! use breadx::{prelude::*, display::DisplayConnection, protocol::xproto}; 61 | //! 62 | //! # fn main() -> breadx::Result<()> { 63 | //! // establish a connection to the X11 server 64 | //! let mut connection = DisplayConnection::connect(None)?; 65 | //! 66 | //! // create a window 67 | //! // note the "_checked" suffix, this indicates that the result of the 68 | //! // function will be checked by the server after it is run 69 | //! // also note that we need to create an XID for the window ahead of time 70 | //! let wid = connection.generate_xid()?; 71 | //! connection.create_window_checked( 72 | //! 0, // depth 73 | //! wid, 74 | //! connection.default_screen().root, // parent 75 | //! 0, // x 76 | //! 0, // y 77 | //! 600, // width 78 | //! 400, // height 79 | //! 0, // border width 80 | //! xproto::WindowClass::COPY_FROM_PARENT, 81 | //! 0, // visual 82 | //! xproto::CreateWindowAux::new() 83 | //! .background_pixel(connection.default_screen().white_pixel) 84 | //! )?; 85 | //! 86 | //! // map the window to the screen 87 | //! // note the lack of _checked here 88 | //! connection.map_window(wid)?; 89 | //! 90 | //! // primary event loop 91 | //! loop { 92 | //! let event = connection.wait_for_event()?; 93 | //! 94 | //! match event { 95 | //! // match on the Event struct in here 96 | //! # _ => {}, 97 | //! } 98 | //! } 99 | //! # Ok(()) } 100 | //! ``` 101 | //! 102 | //! See the [tutorial](crate::tutorials::introduction) for more information 103 | //! on the usage of `breadx`. 104 | 105 | #![no_std] 106 | #![forbid(future_incompatible, unsafe_code, rust_2018_idioms)] 107 | #![warn(clippy::pedantic)] 108 | #![allow( 109 | clippy::module_name_repetitions, 110 | clippy::missing_errors_doc, 111 | clippy::wildcard_imports 112 | )] 113 | 114 | extern crate alloc; 115 | 116 | #[cfg(feature = "std")] 117 | extern crate std; 118 | 119 | #[macro_use] 120 | mod gates; 121 | 122 | mod error; 123 | pub use error::*; 124 | 125 | mod utils; 126 | pub(crate) use utils::*; 127 | 128 | cfg_async! { 129 | pub mod rt_support; 130 | } 131 | 132 | mod void; 133 | pub use void::Void; 134 | 135 | cfg_std! { 136 | mod name; 137 | pub use name::*; 138 | } 139 | 140 | cfg_sync! { 141 | mod mutex; 142 | } 143 | 144 | cfg_async! { 145 | pub mod futures; 146 | } 147 | 148 | // inline some of x11rb_protocol's docs into ours 149 | #[doc(inline)] 150 | pub use x11rb_protocol::{connect, x11_utils}; 151 | 152 | /// The protocol used during communication. 153 | pub mod protocol { 154 | #[doc(inline)] 155 | pub use x11rb_protocol::{connection::ReplyFdKind, protocol::*, x11_utils::*, DiscardMode}; 156 | } 157 | 158 | cfg_std! { 159 | #[doc(inline)] 160 | pub use x11rb_protocol::resource_manager; 161 | } 162 | 163 | #[allow( 164 | clippy::cast_possible_truncation, 165 | clippy::let_and_return, 166 | clippy::needless_lifetimes, 167 | clippy::too_many_arguments 168 | )] 169 | pub(crate) mod automatically_generated; 170 | 171 | #[doc(inline)] 172 | pub use x11rb_protocol::RawFdContainer as Fd; 173 | 174 | pub mod connection; 175 | pub mod display; 176 | 177 | mod mutlireply; 178 | pub use mutlireply::*; 179 | 180 | /// Contains a set of traits to be automatically imported for full 181 | /// functionality. 182 | pub mod prelude { 183 | pub use super::display::{ 184 | Display, DisplayBase, DisplayBaseExt, DisplayExt, DisplayFunctionsExt, 185 | }; 186 | 187 | cfg_async! { 188 | pub use super::display::{ 189 | AsyncDisplay, AsyncDisplayExt, AsyncDisplayFunctionsExt 190 | }; 191 | } 192 | } 193 | 194 | pub mod tutorials; 195 | -------------------------------------------------------------------------------- /breadx/src/mutex.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #![allow(dead_code)] 7 | #![cfg(feature = "sync_display")] 8 | 9 | cfg_not_pl! { 10 | use std::sync::{self, TryLockError}; 11 | } 12 | 13 | /// A wrapper around `std::sync::Mutex` when `parking_lot` is not enabled, 14 | /// and `parking_lot::Mutex` when it is. 15 | pub(crate) struct Mutex { 16 | #[cfg(feature = "pl")] 17 | mutex: parking_lot::Mutex, 18 | #[cfg(not(feature = "pl"))] 19 | mutex: sync::Mutex, 20 | } 21 | 22 | /// A wrapper around `std::sync::RwLock` when `parking_lot` is not enabled, 23 | /// and `parking_lot::RwLock` when it is. 24 | pub(crate) struct RwLock { 25 | #[cfg(feature = "pl")] 26 | rwlock: parking_lot::RwLock, 27 | #[cfg(not(feature = "pl"))] 28 | rwlock: sync::RwLock, 29 | } 30 | 31 | /* mutex guard type aliases */ 32 | 33 | cfg_pl! { 34 | pub(crate) type MutexGuard<'a, T> = parking_lot::MutexGuard<'a, T>; 35 | pub(crate) type RwLockReadGuard<'a, T> = parking_lot::RwLockReadGuard<'a, T>; 36 | pub(crate) type RwLockWriteGuard<'a, T> = parking_lot::RwLockWriteGuard<'a, T>; 37 | } 38 | 39 | cfg_not_pl! { 40 | pub(crate) type MutexGuard<'a, T> = sync::MutexGuard<'a, T>; 41 | pub(crate) type RwLockReadGuard<'a, T> = sync::RwLockReadGuard<'a, T>; 42 | pub(crate) type RwLockWriteGuard<'a, T> = sync::RwLockWriteGuard<'a, T>; 43 | } 44 | 45 | cfg_pl! { 46 | impl Mutex { 47 | pub(crate) fn new(t: T) -> Self { 48 | Self { 49 | mutex: parking_lot::Mutex::new(t), 50 | } 51 | } 52 | } 53 | 54 | impl Mutex { 55 | pub(crate) fn lock(&self) -> MutexGuard<'_, T> { 56 | self.mutex.lock() 57 | } 58 | 59 | pub(crate) fn try_lock(&self) -> Option> { 60 | self.mutex.try_lock() 61 | } 62 | 63 | pub(crate) fn get_mut(&mut self) -> &mut T { 64 | self.mutex.get_mut() 65 | } 66 | } 67 | 68 | impl RwLock { 69 | pub(crate) fn new(t: T) -> Self { 70 | Self { 71 | rwlock: parking_lot::RwLock::new(t), 72 | } 73 | } 74 | 75 | pub(crate) fn read(&self) -> RwLockReadGuard<'_, T> { 76 | self.rwlock.read() 77 | } 78 | 79 | pub(crate) fn try_read(&self) -> Option> { 80 | self.rwlock.try_read() 81 | } 82 | 83 | pub(crate) fn write(&self) -> RwLockWriteGuard<'_, T> { 84 | self.rwlock.write() 85 | } 86 | 87 | pub(crate) fn try_write(&self) -> Option> { 88 | self.rwlock.try_write() 89 | } 90 | } 91 | } 92 | 93 | cfg_not_pl! { 94 | // macros to help bypass the poison guard 95 | 96 | macro_rules! lock { 97 | ($res: expr) => { 98 | match $res { 99 | Ok(guard) => guard, 100 | Err(poison) => { 101 | // we don't care about poisoning, just call 102 | // the into_inner method to get the value 103 | // out of the poisoned state 104 | poison.into_inner() 105 | } 106 | } 107 | } 108 | } 109 | 110 | macro_rules! try_lock { 111 | ($res: expr) => { 112 | match $res { 113 | Ok(guard) => Some(guard), 114 | Err(TryLockError::WouldBlock) => None, 115 | Err(TryLockError::Poisoned(poison)) => { 116 | Some(poison.into_inner()) 117 | } 118 | } 119 | } 120 | } 121 | 122 | impl Mutex { 123 | pub(crate) fn new(t: T) -> Self { 124 | Self { 125 | mutex: sync::Mutex::new(t), 126 | } 127 | } 128 | } 129 | 130 | impl Mutex { 131 | pub(crate) fn lock(&self) -> MutexGuard<'_, T> { 132 | lock!(self.mutex.lock()) 133 | } 134 | 135 | pub(crate) fn try_lock(&self) -> Option> { 136 | try_lock!(self.mutex.try_lock()) 137 | } 138 | 139 | pub(crate) fn get_mut(&self) -> &mut T { 140 | self.mutex.get_mut() 141 | } 142 | } 143 | 144 | impl RwLock { 145 | pub(crate) fn new(t: T) -> Self { 146 | Self { 147 | rwlock: sync::RwLock::new(t), 148 | } 149 | } 150 | 151 | pub(crate) fn read(&self) -> RwLockReadGuard<'_, T> { 152 | lock!(self.rwlock.read()) 153 | } 154 | 155 | pub(crate) fn try_read(&self) -> Option> { 156 | try_lock!(self.rwlock.try_read()) 157 | } 158 | 159 | pub(crate) fn write(&self) -> RwLockWriteGuard<'_, T> { 160 | lock!(self.rwlock.write()) 161 | } 162 | 163 | pub(crate) fn try_write(&self) -> Option> { 164 | try_lock!(self.rwlock.try_write()) 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /breadx/src/mutlireply.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use crate::{ 7 | display::{Cookie, Display, DisplayExt}, 8 | Result, 9 | }; 10 | use x11rb_protocol::{protocol::xproto::ListFontsWithInfoReply, x11_utils::TryParseFd}; 11 | 12 | #[cfg(feature = "record")] 13 | use x11rb_protocol::protocol::record::EnableContextReply; 14 | 15 | cfg_async! { 16 | use crate::{ 17 | display::{AsyncDisplay, AsyncDisplayExt}, 18 | futures::WaitForReply, 19 | }; 20 | use core::{mem, task::Poll}; 21 | use futures_util::{future::FutureExt, stream::Stream}; 22 | } 23 | 24 | /// A reply that can have more than one reply. 25 | pub trait Multireply { 26 | /// Tell if this reply will be the last reply in the sequence. 27 | fn is_last(&self) -> bool; 28 | } 29 | 30 | impl Multireply for ListFontsWithInfoReply { 31 | fn is_last(&self) -> bool { 32 | self.name.is_empty() 33 | } 34 | } 35 | 36 | #[cfg(feature = "record")] 37 | impl Multireply for EnableContextReply { 38 | fn is_last(&self) -> bool { 39 | self.category == 5 40 | } 41 | } 42 | 43 | impl Cookie { 44 | /// Get an iterator over the replies of this request. 45 | pub fn into_iter<'dpy, Dpy: Display + ?Sized>( 46 | self, 47 | display: &'dpy mut Dpy, 48 | ) -> impl Iterator> + 'dpy 49 | where 50 | R: 'dpy, 51 | { 52 | MultireplyIter { 53 | cookie: self, 54 | display, 55 | } 56 | } 57 | } 58 | 59 | cfg_async! { 60 | impl Cookie { 61 | /// Get a stream over the replies of this request. 62 | pub fn into_stream<'dpy, Dpy: AsyncDisplay + ?Sized>( 63 | self, 64 | display: &'dpy mut Dpy, 65 | ) -> impl Stream> + 'dpy 66 | where 67 | R: 'dpy, 68 | { 69 | MultireplyStream { 70 | cookie: self, 71 | state: State::NotWaiting(display), 72 | } 73 | } 74 | } 75 | } 76 | 77 | struct MultireplyIter<'dpy, R, Dpy: ?Sized> { 78 | cookie: Cookie, 79 | display: &'dpy mut Dpy, 80 | } 81 | 82 | impl<'dpy, R: Multireply + TryParseFd, Dpy: Display + ?Sized> Iterator 83 | for MultireplyIter<'dpy, R, Dpy> 84 | { 85 | type Item = Result; 86 | 87 | fn next(&mut self) -> Option> { 88 | // wait for the given reply 89 | let reply = match self.display.wait_for_reply(self.cookie) { 90 | Ok(reply) => reply, 91 | Err(e) => return Some(Err(e)), 92 | }; 93 | 94 | // see if it's the end 95 | if reply.is_last() { 96 | Some(Ok(reply)) 97 | } else { 98 | None 99 | } 100 | } 101 | } 102 | 103 | cfg_async! { 104 | struct MultireplyStream<'dpy, R, Dpy: ?Sized> { 105 | cookie: Cookie, 106 | state: State<'dpy, R, Dpy>, 107 | } 108 | 109 | enum State<'dpy, R, Dpy: ?Sized> { 110 | NotWaiting(&'dpy mut Dpy), 111 | Waiting(WaitForReply<'dpy, Dpy, R>), 112 | Hole, 113 | } 114 | 115 | impl<'dpy, R: Multireply + TryParseFd + Unpin, Dpy: AsyncDisplay + ?Sized> Stream for MultireplyStream<'dpy, R, Dpy> { 116 | type Item = Result; 117 | 118 | fn poll_next(self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll> { 119 | let this = self.get_mut(); 120 | 121 | // get the underlying future to poll 122 | let underlying_fut = match this.state { 123 | State::NotWaiting(_) => { 124 | let display_ref = match mem::replace(&mut this.state, State::Hole) { 125 | State::NotWaiting(dpy) => dpy, 126 | _ => unreachable!(), 127 | }; 128 | 129 | this.state = State::Waiting(display_ref.wait_for_reply(this.cookie)); 130 | 131 | // get the ref out 132 | match this.state { 133 | State::Waiting(ref mut fut) => fut, 134 | _ => unreachable!(), 135 | } 136 | } 137 | State::Waiting(ref mut fut) => fut, 138 | State::Hole => unreachable!("Cannot poll an empty hole"), 139 | }; 140 | 141 | // poll it 142 | let reply = match underlying_fut.poll_unpin(cx) { 143 | Poll::Pending => return Poll::Pending, 144 | Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e))), 145 | Poll::Ready(Ok(r)) => r, 146 | }; 147 | 148 | // cannibalize the future 149 | let display_ref = match mem::replace(&mut this.state, State::Hole) { 150 | State::Waiting(fut) => fut.cannibalize(), 151 | _ => unreachable!(), 152 | }; 153 | this.state = State::NotWaiting(display_ref); 154 | 155 | // see if this is a whole 156 | if reply.is_last() { 157 | Poll::Ready(None) 158 | } else { 159 | Poll::Ready(Some(Ok(reply))) 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /breadx/src/name/nb_connect.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #![cfg(feature = "async")] 7 | #![allow(clippy::needless_pass_by_value)] 8 | 9 | use crate::{Error, NameConnection, Result, Unblock}; 10 | use alloc::{boxed::Box, format, string::ToString}; 11 | use core::{ 12 | future::{self, Future}, 13 | pin::Pin, 14 | }; 15 | use futures_util::{ 16 | stream::{self, StreamExt}, 17 | Stream, 18 | }; 19 | use socket2::{Domain, Protocol, SockAddr, Socket, Type}; 20 | use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; 21 | use x11rb_protocol::parse_display::{ConnectAddress, ParsedDisplay}; 22 | 23 | cfg_std_unix! { 24 | use std::path::PathBuf; 25 | } 26 | 27 | #[allow(unused_imports)] 28 | use crate::Unsupported; 29 | 30 | pub(crate) async fn nb_connect( 31 | pd: &ParsedDisplay, 32 | is_env: bool, 33 | mut resolv: impl FnMut(NameConnection) -> Fut, 34 | ) -> Result 35 | where 36 | Fut: Future>, 37 | { 38 | // create a stream iterating over the possible connections 39 | let mut conns = stream::iter(pd.connect_instruction()) 40 | .flat_map(instruction_into_socket) 41 | .map(|sd| sd.and_then(|(sd, mode)| sd.connect().map(move |sd| (sd, mode)))) 42 | .map(|sd| { 43 | sd.map(|(socket, mode)| { 44 | // determine what mode to put them in 45 | if matches!(mode, SocketMode::Tcp) { 46 | NameConnection::from_tcp_stream(socket.into()) 47 | } else { 48 | #[cfg(unix)] 49 | { 50 | NameConnection::from_unix_stream(socket.into()) 51 | } 52 | 53 | #[cfg(not(unix))] 54 | { 55 | unreachable!() 56 | } 57 | } 58 | }) 59 | }); 60 | 61 | // test them to see the first one that works 62 | // swap Ok to Err for try_fold 63 | let mut err: Option = None; 64 | 65 | while let Some(conn) = conns.next().await { 66 | match conn { 67 | Ok(conn) => match resolv(conn).await { 68 | Ok(conn) => return Ok(conn), 69 | Err(e) => { 70 | err = Some(e); 71 | } 72 | }, 73 | Err(e) => { 74 | err = Some(e); 75 | } 76 | } 77 | } 78 | 79 | Err(err.unwrap_or_else(|| Error::couldnt_parse_display(is_env))) 80 | } 81 | 82 | type SockAddrStream<'a> = 83 | Pin> + Send + 'a>>; 84 | 85 | /// Convert a `ConnectInstruction` into a `Stream` iterating over the potential 86 | /// socket details. 87 | fn instruction_into_socket(ci: ConnectAddress<'_>) -> SockAddrStream<'_> { 88 | match ci { 89 | ConnectAddress::Hostname(hostname, port) => { 90 | // collect the potential addresses 91 | tcp_ip_addrs(hostname, port) 92 | .map(|addr| { 93 | addr.map(|addr| { 94 | let domain = Domain::for_address(addr); 95 | 96 | ( 97 | SocketDetails { 98 | addr: addr.into(), 99 | domain, 100 | protocol: Some(Protocol::TCP), 101 | }, 102 | SocketMode::Tcp, 103 | ) 104 | }) 105 | }) 106 | .boxed() 107 | } 108 | ConnectAddress::Socket(path) => { 109 | cfg_if::cfg_if! { 110 | if #[cfg(unix)] { 111 | // unix socket for the path 112 | unix_connections(path) 113 | .map(|sock_addr| sock_addr.map(|sock_addr| { (SocketDetails { 114 | addr: sock_addr, 115 | domain: Domain::UNIX, 116 | protocol: None, 117 | }, SocketMode::Unix) })).boxed() 118 | } else { 119 | let _ = path; 120 | one( 121 | Err(Error::make_unsupported(crate::Unsupported::Socket)) 122 | ) 123 | } 124 | } 125 | } 126 | addr => one(Err(Error::make_msg(format!( 127 | "Unsupported connection address: {:?}", 128 | addr, 129 | )))), 130 | } 131 | } 132 | 133 | fn tcp_ip_addrs( 134 | hostname: &str, 135 | port: u16, 136 | ) -> Pin> + Send + '_>> { 137 | // fast paths that don't involve blocking 138 | if let Ok(ip) = hostname.parse::() { 139 | return one(Ok(SocketAddr::new(ip.into(), port))); 140 | } 141 | 142 | if let Ok(ip) = hostname.parse::() { 143 | return one(Ok(SocketAddr::new(ip.into(), port))); 144 | } 145 | 146 | // slow path, use the Unblock struct with ToSocketAddrs 147 | let socket_addr = (hostname.to_string(), port); 148 | Unblock::new(move || std::net::ToSocketAddrs::to_socket_addrs(&socket_addr)) 149 | .map(|res| res.map_err(Error::io)) 150 | .boxed() 151 | } 152 | 153 | #[cfg(all(unix, any(target_os = "linux", target_os = "android")))] 154 | fn unix_connections(path: PathBuf) -> impl Stream> + Send { 155 | use alloc::vec; 156 | use std::ffi::OsStr; 157 | use std::os::unix::ffi::OsStrExt; 158 | 159 | // first, try connecting to the abstract socket (prepend with zero) 160 | let path_bytes = path.as_os_str().as_bytes(); 161 | let mut abstract_path_buf = vec![0; path_bytes.len() + 1]; 162 | abstract_path_buf[1..].copy_from_slice(path_bytes); 163 | let abstract_path = OsStr::from_bytes(&abstract_path_buf); 164 | 165 | // setup vec with both options 166 | let paths_to_try = vec![ 167 | SockAddr::unix(abstract_path).map_err(Into::into), 168 | SockAddr::unix(&path).map_err(Into::into), 169 | ]; 170 | 171 | stream::iter(paths_to_try) 172 | } 173 | 174 | #[cfg(all(unix, not(any(target_os = "linux", target_os = "android"))))] 175 | fn unix_connections(path: PathBuf) -> impl Stream> + Send { 176 | // abstract sockets are only supported on linux/android 177 | one(SockAddr::unix(&path).map_err(Into::into)) 178 | } 179 | 180 | fn one<'a, T: 'a + Send>(item: T) -> Pin + Send + 'a>> { 181 | stream::once(future::ready(item)).boxed() 182 | } 183 | 184 | struct SocketDetails { 185 | addr: SockAddr, 186 | domain: Domain, 187 | protocol: Option, 188 | } 189 | 190 | enum SocketMode { 191 | Tcp, 192 | #[cfg(unix)] 193 | Unix, 194 | } 195 | 196 | impl SocketDetails { 197 | fn connect(self) -> Result { 198 | // impl here is taken from: 199 | // https://github.com/smol-rs/nb-connect/blob/master/src/lib.rs 200 | // nb-connect is dual-licensed under MIT or Apache 2.0 201 | 202 | let SocketDetails { 203 | addr, 204 | domain, 205 | protocol, 206 | } = self; 207 | 208 | let sock_type = Type::STREAM; 209 | #[cfg(any( 210 | target_os = "android", 211 | target_os = "dragonfly", 212 | target_os = "freebsd", 213 | target_os = "fuchsia", 214 | target_os = "illumos", 215 | target_os = "linux", 216 | target_os = "netbsd", 217 | target_os = "openbsd" 218 | ))] 219 | // If we can, set nonblocking at socket creation for unix 220 | let sock_type = sock_type.nonblocking(); 221 | // This automatically handles cloexec on unix, no_inherit on windows and nosigpipe on macos 222 | let socket = Socket::new(domain, sock_type, protocol).map_err(Error::io)?; 223 | #[cfg(not(any( 224 | target_os = "android", 225 | target_os = "dragonfly", 226 | target_os = "freebsd", 227 | target_os = "fuchsia", 228 | target_os = "illumos", 229 | target_os = "linux", 230 | target_os = "netbsd", 231 | target_os = "openbsd" 232 | )))] 233 | // If the current platform doesn't support nonblocking at creation, enable it after creation 234 | socket.set_nonblocking(true).map_err(Error::io)?; 235 | match socket.connect(&addr) { 236 | Ok(_) => {} 237 | #[cfg(unix)] 238 | Err(err) if err.raw_os_error() == Some(nix::libc::EINPROGRESS) => {} 239 | Err(err) if err.kind() == std::io::ErrorKind::WouldBlock => {} 240 | Err(err) => return Err(Error::io(err)), 241 | } 242 | Ok(socket) 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /breadx/src/rt_support/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! This module contains runtime support for the most common runtimes: 7 | //! [`tokio`] and [`async-std`]. 8 | //! 9 | //! [`tokio`]: crate::rt_support::tokio_support 10 | //! [`async-std`]: crate::rt_support::async_std_support 11 | 12 | #[cfg(feature = "async-std-support")] 13 | pub mod async_std_support; 14 | #[cfg(all(feature = "tokio-support", unix))] 15 | pub mod tokio_support; 16 | -------------------------------------------------------------------------------- /breadx/src/tutorials/basics.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! # Basic Usage of `breadx` 7 | //! 8 | //! [Last Tutorial] 9 | //! 10 | //! Let's create a simple application using `breadx`. I'll explain each of 11 | //! the pieces along the way. 12 | //! 13 | //! [Last Tutorial]: crate::tutorials::introduction 14 | //! 15 | //! ## Application Setup 16 | //! 17 | //! First, let's just create a simple application that depends on `breadx`. 18 | //! 19 | //! TODO: application setup once breadx is on crates.io 20 | //! 21 | //! You should see a `main.rs` file. This is the entry point for your 22 | //! application. It's a good place to put your main logic. 23 | //! 24 | //! ## Connect to the Server 25 | //! 26 | //! The first thing you do is connect to the X11 server. You can use the 27 | //! [`DisplayConnection::connect()`] method, which connects to the server. 28 | //! 29 | //! ```rust,no_run 30 | //! use breadx::{display::DisplayConnection, Result}; 31 | //! 32 | //! fn main() -> Result<()> { 33 | //! let mut connection = DisplayConnection::connect(None)?; 34 | //! Ok(()) 35 | //! } 36 | //! ``` 37 | //! 38 | //! For those unfamiliar with Rust, I'll go into each part of the program: 39 | //! 40 | //! - `use breadx::{...}` imports the [`DisplayConnection`] and [`Result`] 41 | //! types from the `breadx` crate. 42 | //! - `fn main() -> Result<()>` defines the main function, which is the 43 | //! entry point for the application. It returns [`Result`], which is a 44 | //! [sum type] consisting of the success state (`Ok`) and the failure 45 | //! state (`Err`). 46 | //! - `let mut connection = ...` defines the `connection` variable, 47 | //! makes it a [mutable] variable, and initializes it to the result of 48 | //! the following expression. 49 | //! - `DisplayConnection::connect(...)` creates a new [`DisplayConnection`] 50 | //! that is connected to the currently running X11 server. It opens a 51 | //! TCP or Unix connection, preforms the initial setup handshake, and 52 | //! then returns a type that can be used to communicate with the X11 53 | //! server. 54 | //! - `None` is a version of another sum type, [`Option`]. In this case, 55 | //! it is a parameter passed to [`DisplayConnection::connect()`]. The 56 | //! first parameter of the function indicates the *display name* to be 57 | //! used, which determines what X11 server to connect to. In this case, 58 | //! `None` indicates to use the display name contained in the [`DISPLAY`] 59 | //! environment variable. 60 | //! - The `?` at the end is syntactic sugar. `connect()` returns a [`Result`], 61 | //! and if it is `Err`, the `?` operator will cause `main()` to return the 62 | //! error early. 63 | //! - `Ok(())` at the end is used to tell the function to return the success 64 | //! status, since we made it through all right. The `()` is the [empty tuple], 65 | //! which is often used in Rust to express that we have no data. 66 | //! 67 | //! To summarize, all this does is open the connection to the X11 server, 68 | //! and then immediately closes it. That won't do. How about we do something 69 | //! with it? 70 | //! 71 | //! [`DisplayConnection`]: crate::display::DisplayConnection 72 | //! [`Result`]: std::result::Result 73 | //! [sum type]: https://en.wikipedia.org/wiki/Tagged_union 74 | //! [`DISPLAY`]: https://askubuntu.com/a/919913 75 | //! [mutable]: https://doc.rust-lang.org/book/ch03-01-variables-and-mutability.html 76 | //! [`DisplayConnection::connect()`]: crate::display::DisplayConnection::connect 77 | //! [empty tuple]: https://doc.rust-lang.org/std/primitive.unit.html 78 | //! [`Option`]: std::option::Option 79 | //! 80 | //! ## Create a Window 81 | //! 82 | //! Let's send our first request. There's pretty much nothing better we can 83 | //! do than create a window at this point. 84 | //! 85 | //! There are a few things we have to take care of first. We need to determine 86 | //! the "parent" of the window, or the window that it's inside of. We'll go more 87 | //! in depth as to what exactly a window is earlier, but you should know that the 88 | //! actual screen can be used as a parent. 89 | //! 90 | //! ```rust,no_run 91 | //! use breadx::{display::DisplayConnection, prelude::*, protocol::xproto, Result}; 92 | //! 93 | //! fn main() -> Result<()> { 94 | //! let mut connection = DisplayConnection::connect(None)?; 95 | //! let parent = connection.default_screen().root; 96 | //! Ok(()) 97 | //! } 98 | //! ``` 99 | //! 100 | //! Some things to take note of: 101 | //! 102 | //! - Note that we are now importing the [prelude], which contains 103 | //! certain [traits] to enhance the functionality of the 104 | //! [`DisplayConnection`] type. 105 | //! - We also import the [`xproto`] module, which contains some useful 106 | //! types that we'll use later. 107 | //! - We set parent to the value of `root` of [`connection.default_screen()`], 108 | //! which returns the screen we're assigned to use by default. 109 | //! 110 | //! [prelude]: https://doc.rust-lang.org/reference/names/preludes.html 111 | //! [traits]: https://doc.rust-lang.org/book/ch10-02-traits.html 112 | //! [`DisplayConnection`]: crate::display::DisplayConnection 113 | //! [`connection.default_screen()`]: crate::display::DisplayBase::default_screen 114 | //! [`xproto`]: crate::protocol::xproto 115 | //! 116 | //! Now that everything is in place, we can create the window. 117 | //! 118 | //! ```rust,no_run 119 | //! use breadx::{display::DisplayConnection, prelude::*, protocol::xproto, Result}; 120 | //! 121 | //! fn main() -> Result<()> { 122 | //! # let mut connection = DisplayConnection::connect(None)?; 123 | //! # let parent = connection.default_screen().root; 124 | //! // snip existing code 125 | //! 126 | //! let wid = connection.generate_xid()?; 127 | //! let window = connection.create_window( 128 | //! 0, // depth 129 | //! wid, 130 | //! parent, 131 | //! 0, // x 132 | //! 0, // y 133 | //! 600, // width 134 | //! 400, // height 135 | //! 0, // border width 136 | //! xproto::WindowClass::COPY_FROM_PARENT, 137 | //! 0, // visual class 138 | //! xproto::CreateWindowAux::new(), 139 | //! )?; 140 | //! 141 | //! Ok(()) 142 | //! } 143 | //! ``` 144 | //! 145 | //! First, using the [`generate_xid()`] function, we generate a unique *XID*. This is 146 | //! a value that the client and server will share to identify resources. XIDs 147 | //! aren't just unique to the process, they're unique across the entire system. This 148 | //! means that you can use XIDs that originate in other processes, and they will 149 | //! still be unique. 150 | //! 151 | //! Then, we call the [`create_window()`] function. This function takes the following 152 | //! parameters: 153 | //! 154 | //! - The depth of the window. This is the number of bits per pixel. Setting it 155 | //! to `0` means that the window will use the depth of its parent. 156 | //! - The window ID. This is the value that the client and server will share to 157 | //! identify the window. 158 | //! - The parent window. This is the window that the new window will be inside of. 159 | //! Remember that `parent` refers to the screen. 160 | //! - The x and y coordinates of the window. 161 | //! - The width and height of the window. 162 | //! - The border width. This is the width of the border around the window. However, 163 | //! you don't actually have a border around a top-level window, so this value doesn't 164 | //! really matter. 165 | //! - The *window class*, which determines whether the window is an "input only" window 166 | //! or an "input-output" window. The distinction isn't important for this example, 167 | //! so we just copy it from the parent. 168 | //! - The *visual class* defines certain properties of the window, like the range of colors 169 | //! that are available. `0` once again indicates that we need to inherit from the parent. 170 | //! - [`CreateWindowAux`] is used to contain properties that are not strictly necessary for 171 | //! creating a window, but are still useful. In this case, we don't need to specify any 172 | //! properties, so we just use the default constructor. 173 | //! 174 | //! [`generate_xid()`]: crate::display::Display::generate_xid 175 | //! [`create_window()`]: crate::display::DisplayFunctionsExt::create_window 176 | //! [`CreateWindowAux`]: crate::protocol::xproto::CreateWindowAux 177 | //! 178 | //! If you run this code, you'll notice that nothing happens. You see, while the 179 | //! window is created, it's not actually visible. We need to tell the server to 180 | //! actually show the window. 181 | -------------------------------------------------------------------------------- /breadx/src/tutorials/introduction.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #![allow(clippy::doc_markdown)] 7 | 8 | //! # An Introduction to X11 9 | //! 10 | //! Hello! My name is John, and I'm the author of `breadx`. While writing 11 | //! `breadx`, I quickly realized that X11 is a confusing beast, so I had 12 | //! to write something that explained it. 13 | //! 14 | //! Actually, that's not my real reason for writing this, or my real name 15 | //! (or is it?) The real reason I'm writing this is because I haven't 16 | //! been able to find a *good* X11 tutorial anywhere on the internet. 17 | //! The best one I've been able to find is Cristopher Tronche's 18 | //! translation of the [Xlib manual], and even then it's kind of hard to 19 | //! follow. Tutorials for extensions are even more rare, to the point 20 | //! that everything I've learned about extensions, I've learned orally. 21 | //! That doesn't seem right for something as critical to the ecosystem 22 | //! as X11. 23 | //! 24 | //! I'm going to try to put everything I know about X11 in this inline 25 | //! documentation. I'll also try to make it parsable to the average 26 | //! software hobbyist. This won't be a complete manual, nor will it be 27 | //! entirely comprehensive, but it should be enough for anyone who wants 28 | //! to learn X11. 29 | //! 30 | //! Before you start reading, grab a cup of water. If you've found yourself 31 | //! this deep in the weeds, you're probably thirsty. 32 | //! 33 | //! [Xlib manual]: https://tronche.com/gui/x/xlib/ 34 | //! 35 | //! ## Why X11? 36 | //! 37 | //! I'm going to put this disclaimer at the top here, for anyone who's 38 | //! reading this casually: **you probably don't need to know X11**. It's 39 | //! 2021. Higher level GUI frameworks have existed since GUIs themselves. 40 | //! Not to mention, it's far more difficult to do anything through X11 41 | //! than with these frameworks. 42 | //! 43 | //! So if you're just looking for something to construct your app with, 44 | //! you won't find it here. Well, you *can* find it here. It's just that 45 | //! it'll take a lot more effort to find, and that effort won't be worth it, 46 | //! since it won't work on Windows. 47 | //! 48 | //! So, why *do* you need X11? Well, here are a handful of scattered reasons. 49 | //! 50 | //! - You want something close and low to the hardware. Although GUI 51 | //! frameworks have made excellent strides in being as low to the 52 | //! hardware as possible, there are some things directly tied to X11. 53 | //! - You're *writing* a GUI framework, and need X11 to implement it. 54 | //! - Your program needs a feature that your GUI framework doesn't offer, 55 | //! but an X11 extension does. 56 | //! - You just want to learn something cool (although it escapes me 57 | //! why anyone would consider X11 to be trendy). 58 | //! 59 | //! I'm going to assume you fit into one of these categories. In which 60 | //! case, welcome! I hope you find this tutorial useful for your usecase. 61 | //! 62 | //! ## A Brief History 63 | //! 64 | //! X began its life at MIT, as a sub-project of Project Athena. They 65 | //! needed a windowing system, so like true programmers, they stole it. 66 | //! X's name comes from the letter before W, which was the system they 67 | //! modified that would eventually become X. 68 | //! 69 | //! The Digital Equipment Corporation then used X version 6 (abbreviated 70 | //! to X6) in its Ultrix workstation. Although X6 originally required a 71 | //! fee, by X9 the protocol was released under an open-source license that 72 | //! would eventually become the [MIT License]. This model allowed corporations 73 | //! like DEC and HP to integrate X into their products, causing X10 to become 74 | //! widely spread. Eventually, X11 was released, building on previous versions 75 | //! while also being significantly more hardware neutral. 76 | //! 77 | //! X11 required both a client and a server. Originally, OS developers 78 | //! released their own versions of the X server, with [XFree86] being the 79 | //! dominant option on Linux and the BSDs. However, due to certain controveries 80 | //! over XFree86, the developers eventually migrated to the [X.Org] project. 81 | //! To this day, you can find X.Org running on nearly every Linux desktop. 82 | //! 83 | //! But let's focus on the client. The original client library for X11 was 84 | //! [Xlib]. Now, Xlib was very much a product of its time. By that I mean that, 85 | //! by modern standards, Xlib is a dumpster fire. Namely: 86 | //! 87 | //! - The lack of support for sending requests in parallel. 88 | //! - Providing little direct access to the X11 protocol. 89 | //! - Containing a litany of code that no one really needed. 90 | //! - Being bloated in general. In fact, the size of binaries statically 91 | //! linked to Xlib was an early incentive to use dynamic linking. 92 | //! - Having an interface that is so arcane, programming in Xlib has 93 | //! become synonymous with dealing with dysfunctional APIs. 94 | //! - Being known to contain errors, but being such a pain to debug that 95 | //! no one really tried to fix those errors. 96 | //! 97 | //! [XCB] was released in 2001 to address some of the above problems. It's 98 | //! become the standard for programming X11 in the modern day. However, 99 | //! many applications still use Xlib for legacy compatibility. 100 | //! 101 | //! `breadx` aims to take a similar approach to XCB: provide as direct of 102 | //! access as possible to the underlying protocol as possible. 103 | //! 104 | //! [MIT License]: https://en.wikipedia.org/wiki/MIT_License 105 | //! [Xlib]: https://en.wikipedia.org/wiki/Xlib 106 | //! [XCB]: https://en.wikipedia.org/wiki/XCB 107 | //! [XFree86]: https://en.wikipedia.org/wiki/XFree86 108 | //! [X.Org]: https://en.wikipedia.org/wiki/X.Org 109 | //! 110 | //! ## What is X11? 111 | //! 112 | //! X11 is a communication protocol, similar to TCP or HTTP. It can take 113 | //! place over "any reliable byte stream". However, in most cases, it's 114 | //! used over TCP or a Unix socket. Communication takes place between a 115 | //! client and a server. The client is your application, while the 116 | //! server is a designated application with control over the hardware 117 | //! involving your screen (most often X.org or Wayland). 118 | //! 119 | //! X11 opens with an initial handshake. The client sends a "setup request" 120 | //! to the server, containing the client's endianness and authorization 121 | //! information. In response, the server will either indicate a failure 122 | //! state (one of the parameters is wrong) or send back a "setup". This 123 | //! object contains initial information about the server, such as the 124 | //! maximum request length, the sizes of attached screens, and the 125 | //! preferred image bit ordering. 126 | //! 127 | //! From here, the client can send requests to the server. Requests 128 | //! are things that the client wants to happen, such as "create a window", 129 | //! "draw these trapezoids" or "copy data between two windows". 130 | //! 131 | //! The server, on the other hand, can send one of three types of 132 | //! response: 133 | //! 134 | //! - **Replies** are responses to requests. They are sent back to the 135 | //! client in response to a request. Not all requests have replies. 136 | //! - **Events** are responses to user events. For instance, clicking on 137 | //! a window will generate an event. 138 | //! - **Errors** are responses to requests that failed. 139 | //! 140 | //! One of X11's major advantages is its fully asynchronous nature. You don't 141 | //! have to wait for a response to send another request. You can, for instance, 142 | //! send five requests, and then wait for each of those request's responses 143 | //! in order to pipeline requests. 144 | //! 145 | //! At a broad level, that's it. There are some additional details, which we'll 146 | //! go into in later chapters. But really, that's it. 147 | //! 148 | //! It's time to create an application, using what we know. 149 | //! 150 | //! [Next Tutorial](crate::tutorials::basics) 151 | -------------------------------------------------------------------------------- /breadx/src/tutorials/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! This module contains a tutorial for the X Window System Protocol, 7 | //! specifically from the perspective of using `breadx`. 8 | //! 9 | //! The tutorial starts at the [introduction](introduction). 10 | 11 | pub mod basics; 12 | pub mod introduction; 13 | -------------------------------------------------------------------------------- /breadx/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | cfg_async! { 7 | mod unblock; 8 | pub(crate) use unblock::Unblock; 9 | } 10 | 11 | cfg_test! { 12 | mod test; 13 | #[allow(unused_imports)] 14 | pub(crate) use test::setup_tracing; 15 | } 16 | 17 | use crate::{display::AsyncStatus, Result}; 18 | use ahash::RandomState; 19 | use hashbrown::HashMap as HbHashMap; 20 | 21 | /// A hash map that uses the `ahash` algorithm. 22 | /// 23 | /// It is marginally more vulnerable to denial of service attacks than 24 | /// default hashmaps, but it is also much faster. 25 | pub(crate) type HashMap = HbHashMap; 26 | 27 | pub(crate) trait ResultExt: Sized { 28 | fn trace(self, f: impl FnOnce(&T)) -> Self; 29 | 30 | fn acopied<'a, R: 'a + Copy>(self) -> Result> 31 | where 32 | T: Into>; 33 | } 34 | 35 | impl ResultExt for Result { 36 | fn trace(self, f: impl FnOnce(&T)) -> Self { 37 | if let Ok(ref t) = self { 38 | f(t); 39 | } 40 | 41 | self 42 | } 43 | 44 | fn acopied<'a, R: 'a + Copy>(self) -> Result> 45 | where 46 | T: Into>, 47 | { 48 | match self { 49 | Ok(t) => Ok(t.into().copied()), 50 | Err(e) => Err(e), 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /breadx/src/utils/test.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | #![cfg(test)] 7 | 8 | /// Set up a `tracing` subscriber for testing purposes. 9 | #[allow(dead_code)] 10 | pub(crate) fn setup_tracing() { 11 | tracing_subscriber::fmt::fmt() 12 | //.with_max_level(tracing::Level::TRACE) 13 | .try_init() 14 | .ok(); 15 | } 16 | -------------------------------------------------------------------------------- /breadx/src/utils/unblock.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! A structure for generating an iterator and then offloading it 7 | //! onto a thread pool. 8 | //! 9 | //! This is similar to the `Unblock` structure from the `blocking` crate, 10 | //! but it only works for iterators. I'm doing this for two reasons: 11 | //! 12 | //! 1). Doing DNS resolution is a cold path on an already cold path, so 13 | //! specializing and using another runtime's thread pool really 14 | //! isn't worth it. 15 | //! 16 | //! In 90 percent of cases, users connect over a Unix socket. In the 17 | //! remaining cases, 99 percent of those are over loopback or with a known 18 | //! IP address. It is very rare for DNS resolution to come into play. 19 | //! 20 | //! 2). Setting up infrastructure for the above is a lot of work. 21 | //! 3). It takes a generation function that returns a Result, specifically 22 | //! for `ToSocketAddrs`. 23 | 24 | #![cfg(feature = "async")] 25 | 26 | use core::{ 27 | mem, 28 | task::{Context, Poll, Waker}, 29 | }; 30 | use futures_util::Stream; 31 | use std::{ 32 | sync::mpsc::{self, TryRecvError}, 33 | thread, 34 | }; 35 | 36 | /// The entire point of this module. 37 | /// 38 | /// Takes a function that spawns an iterator, and runs it on another 39 | /// thread. 40 | pub(crate) enum Unblock { 41 | /// The iterator has not been spawned yet. 42 | Unspawned { 43 | /// The function to spawn the iterator with. 44 | spawn: Func, 45 | }, 46 | /// The iterator is spawned and running on another thread. 47 | Spawned { 48 | /// Channel to receive iterator items over. 49 | items: mpsc::Receiver>, 50 | /// Channel to send wakers over. 51 | wakers: mpsc::Sender, 52 | }, 53 | /// Temporary "hole" state for when we're in the process of spawning. 54 | Hole, 55 | } 56 | 57 | impl< 58 | Item: Send + 'static, 59 | Iter: Iterator, 60 | Err: Send + 'static, 61 | Func: FnOnce() -> Result + Send + 'static, 62 | > Unblock 63 | { 64 | /// Creates a new `Unblock`. 65 | pub(crate) fn new(spawn: Func) -> Self { 66 | Self::Unspawned { spawn } 67 | } 68 | 69 | /// Spawns the function on the thread pool, if we haven't already. 70 | fn spawn(&mut self) { 71 | // get the inner function 72 | let spawn = match mem::replace(self, Self::Hole) { 73 | Self::Unspawned { spawn } => spawn, 74 | _ => unreachable!("Unblock::spawn called twice"), 75 | }; 76 | 77 | // create the channels 78 | let (items_tx, items_rx) = mpsc::channel(); 79 | let (wakers_tx, wakers_rx) = mpsc::channel::(); 80 | 81 | // spawn the thread 82 | thread::Builder::new() 83 | .name("breadx-unblock".into()) 84 | .spawn(move || { 85 | // sub-function for waking all of our wakers 86 | let wake_all = move |wait_for_drop: bool| { 87 | if wait_for_drop { 88 | while let Ok(waker) = wakers_rx.recv() { 89 | waker.wake(); 90 | } 91 | } else { 92 | while let Ok(waker) = wakers_rx.try_recv() { 93 | waker.wake(); 94 | } 95 | } 96 | }; 97 | 98 | // run the function 99 | let iter = match spawn() { 100 | Ok(iter) => iter, 101 | Err(err) => { 102 | // send the error to the items channel 103 | items_tx.send(Err(err)).expect(CHANNEL_SEND_PANIC); 104 | wake_all(true); 105 | return; 106 | } 107 | }; 108 | 109 | // iterate over the items 110 | for item in iter { 111 | // send it over the channel 112 | items_tx.send(Ok(item)).expect(CHANNEL_SEND_PANIC); 113 | 114 | // wake any waiting wakers 115 | wake_all(false); 116 | } 117 | 118 | // we've sent all of our items, so drop the channel 119 | mem::drop(items_tx); 120 | 121 | // wake all of the wakers 122 | wake_all(true); 123 | }) 124 | .expect("failed to spawn unblock thread"); 125 | 126 | // set new state 127 | *self = Self::Spawned { 128 | items: items_rx, 129 | wakers: wakers_tx, 130 | }; 131 | } 132 | 133 | /// Poll for an item from this stream. 134 | fn poll_for_item(&mut self, ctx: &mut Context<'_>) -> Poll, Err>> { 135 | loop { 136 | // match on the current state 137 | match mem::replace(self, Self::Hole) { 138 | Self::Hole => { 139 | panic!("cannot poll an empty hole") 140 | } 141 | mut this @ Self::Unspawned { .. } => { 142 | // spawn the iterator 143 | this.spawn(); 144 | *self = this; 145 | } 146 | Self::Spawned { items, wakers } => { 147 | // try to receive an item 148 | match items.try_recv() { 149 | Ok(item) => { 150 | // if we got an item, return it 151 | *self = Self::Spawned { items, wakers }; 152 | return Poll::Ready(item.map(Some)); 153 | } 154 | Err(TryRecvError::Disconnected) => { 155 | // we're out of items, return None 156 | return Poll::Ready(Ok(None)); 157 | } 158 | Err(TryRecvError::Empty) => { 159 | // no items yet, so we need to wait 160 | // for one to come in 161 | 162 | // pusk waker to channel 163 | wakers.send(ctx.waker().clone()).ok(); 164 | *self = Self::Spawned { items, wakers }; 165 | return Poll::Pending; 166 | } 167 | } 168 | } 169 | } 170 | } 171 | } 172 | } 173 | 174 | impl< 175 | Item: Send + 'static, 176 | Iter: Iterator + Unpin, 177 | Err: Send + 'static, 178 | Func: FnOnce() -> Result + Send + Unpin + 'static, 179 | > Stream for Unblock 180 | { 181 | type Item = Result; 182 | 183 | fn poll_next( 184 | self: core::pin::Pin<&mut Self>, 185 | ctx: &mut Context<'_>, 186 | ) -> Poll> { 187 | match self.get_mut().poll_for_item(ctx) { 188 | Poll::Ready(item) => Poll::Ready(item.transpose()), 189 | Poll::Pending => Poll::Pending, 190 | } 191 | } 192 | } 193 | 194 | const CHANNEL_SEND_PANIC: &str = "failed to send channel item"; 195 | 196 | #[cfg(test)] 197 | mod test { 198 | use super::*; 199 | use core::convert::Infallible; 200 | use futures_util::{stream::iter, StreamExt}; 201 | use std::{thread::sleep, time::Duration}; 202 | 203 | #[test] 204 | fn unblock_works() { 205 | spin_on::spin_on(async { 206 | let unblock = Unblock::new(|| { 207 | let iter = (0..10).map(|i| { 208 | sleep(Duration::from_millis(1)); 209 | i 210 | }); 211 | Result::<_, Infallible>::Ok(iter) 212 | }); 213 | 214 | unblock 215 | .zip(iter(0..10)) 216 | .for_each(|(i, j)| async move { 217 | assert_eq!(i.unwrap(), j); 218 | }) 219 | .await; 220 | }); 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /breadx/src/void.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | /// A type that can be used in the "void" position. 7 | pub trait Void { 8 | /// The raw bytes of this item. 9 | fn bytes(&self) -> &[u8]; 10 | } 11 | 12 | impl Void for T { 13 | fn bytes(&self) -> &[u8] { 14 | bytemuck::bytes_of(self) 15 | } 16 | } 17 | 18 | impl Void for [T] { 19 | fn bytes(&self) -> &[u8] { 20 | bytemuck::cast_slice(self) 21 | } 22 | } 23 | 24 | impl Void for str { 25 | fn bytes(&self) -> &[u8] { 26 | self.as_bytes() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # This template contains all of the possible sections and their default values 2 | 3 | # Note that all fields that take a lint level have these possible values: 4 | # * deny - An error will be produced and the check will fail 5 | # * warn - A warning will be produced, but the check will not fail 6 | # * allow - No warning or error will be produced, though in some cases a note 7 | # will be 8 | 9 | # The values provided in this template are the default values that will be used 10 | # when any section or field is not specified in your own configuration 11 | 12 | # If 1 or more target triples (and optionally, target_features) are specified, 13 | # only the specified targets will be checked when running `cargo deny check`. 14 | # This means, if a particular package is only ever used as a target specific 15 | # dependency, such as, for example, the `nix` crate only being used via the 16 | # `target_family = "unix"` configuration, that only having windows targets in 17 | # this list would mean the nix crate, as well as any of its exclusive 18 | # dependencies not shared by any other crates, would be ignored, as the target 19 | # list here is effectively saying which targets you are building for. 20 | targets = [ 21 | # The triple can be any string, but only the target triples built in to 22 | # rustc (as of 1.40) can be checked against actual config expressions 23 | #{ triple = "x86_64-unknown-linux-musl" }, 24 | # You can also specify which target_features you promise are enabled for a 25 | # particular target. target_features are currently not validated against 26 | # the actual valid features supported by the target architecture. 27 | #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, 28 | ] 29 | 30 | # This section is considered when running `cargo deny check advisories` 31 | # More documentation for the advisories section can be found here: 32 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 33 | [advisories] 34 | # The path where the advisory database is cloned/fetched into 35 | db-path = "~/.cargo/advisory-db" 36 | # The url(s) of the advisory databases to use 37 | db-urls = ["https://github.com/rustsec/advisory-db"] 38 | # The lint level for security vulnerabilities 39 | vulnerability = "deny" 40 | # The lint level for unmaintained crates 41 | unmaintained = "warn" 42 | # The lint level for crates that have been yanked from their source registry 43 | yanked = "warn" 44 | # The lint level for crates with security notices. Note that as of 45 | # 2019-12-17 there are no security notice advisories in 46 | # https://github.com/rustsec/advisory-db 47 | notice = "warn" 48 | # A list of advisory IDs to ignore. Note that ignored advisories will still 49 | # output a note when they are encountered. 50 | ignore = [ 51 | #"RUSTSEC-0000-0000", 52 | ] 53 | # Threshold for security vulnerabilities, any vulnerability with a CVSS score 54 | # lower than the range specified will be ignored. Note that ignored advisories 55 | # will still output a note when they are encountered. 56 | # * None - CVSS Score 0.0 57 | # * Low - CVSS Score 0.1 - 3.9 58 | # * Medium - CVSS Score 4.0 - 6.9 59 | # * High - CVSS Score 7.0 - 8.9 60 | # * Critical - CVSS Score 9.0 - 10.0 61 | #severity-threshold = 62 | 63 | # If this is true, then cargo deny will use the git executable to fetch advisory database. 64 | # If this is false, then it uses a built-in git library. 65 | # Setting this to true can be helpful if you have special authentication requirements that cargo-deny does not support. 66 | # See Git Authentication for more information about setting up git authentication. 67 | #git-fetch-with-cli = true 68 | 69 | # This section is considered when running `cargo deny check licenses` 70 | # More documentation for the licenses section can be found here: 71 | # https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html 72 | [licenses] 73 | # The lint level for crates which do not have a detectable license 74 | unlicensed = "deny" 75 | # List of explicitly allowed licenses 76 | # See https://spdx.org/licenses/ for list of possible licenses 77 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. 78 | allow = [ 79 | "MIT", 80 | "Apache-2.0", 81 | "BSL-1.0", 82 | "BSD-3-Clause", 83 | "CC0-1.0", 84 | #"Apache-2.0 WITH LLVM-exception", 85 | ] 86 | # List of explicitly disallowed licenses 87 | # See https://spdx.org/licenses/ for list of possible licenses 88 | # [possible values: any SPDX 3.11 short identifier (+ optional exception)]. 89 | deny = [ 90 | #"Nokia", 91 | ] 92 | # Lint level for licenses considered copyleft 93 | copyleft = "deny" 94 | # Blanket approval or denial for OSI-approved or FSF Free/Libre licenses 95 | # * both - The license will be approved if it is both OSI-approved *AND* FSF 96 | # * either - The license will be approved if it is either OSI-approved *OR* FSF 97 | # * osi-only - The license will be approved if is OSI-approved *AND NOT* FSF 98 | # * fsf-only - The license will be approved if is FSF *AND NOT* OSI-approved 99 | # * neither - This predicate is ignored and the default lint level is used 100 | allow-osi-fsf-free = "neither" 101 | # Lint level used when no other predicates are matched 102 | # 1. License isn't in the allow or deny lists 103 | # 2. License isn't copyleft 104 | # 3. License isn't OSI/FSF, or allow-osi-fsf-free = "neither" 105 | default = "deny" 106 | # The confidence threshold for detecting a license from license text. 107 | # The higher the value, the more closely the license text must be to the 108 | # canonical license text of a valid SPDX license file. 109 | # [possible values: any between 0.0 and 1.0]. 110 | confidence-threshold = 0.8 111 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 112 | # aren't accepted for every possible crate as with the normal allow list 113 | exceptions = [ 114 | # Each entry is the crate and version constraint, and its specific allow 115 | # list 116 | #{ allow = ["Zlib"], name = "adler32", version = "*" }, 117 | ] 118 | 119 | # Some crates don't have (easily) machine readable licensing information, 120 | # adding a clarification entry for it allows you to manually specify the 121 | # licensing information 122 | #[[licenses.clarify]] 123 | # The name of the crate the clarification applies to 124 | #name = "ring" 125 | # The optional version constraint for the crate 126 | #version = "*" 127 | # The SPDX expression for the license requirements of the crate 128 | #expression = "MIT AND ISC AND OpenSSL" 129 | # One or more files in the crate's source used as the "source of truth" for 130 | # the license expression. If the contents match, the clarification will be used 131 | # when running the license check, otherwise the clarification will be ignored 132 | # and the crate will be checked normally, which may produce warnings or errors 133 | # depending on the rest of your configuration 134 | #license-files = [ 135 | # Each entry is a crate relative path, and the (opaque) hash of its contents 136 | #{ path = "LICENSE", hash = 0xbd0eed23 } 137 | #] 138 | 139 | [licenses.private] 140 | # If true, ignores workspace crates that aren't published, or are only 141 | # published to private registries. 142 | # To see how to mark a crate as unpublished (to the official registry), 143 | # visit https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field. 144 | ignore = false 145 | # One or more private registries that you might publish crates to, if a crate 146 | # is only published to private registries, and ignore is true, the crate will 147 | # not have its license(s) checked 148 | registries = [ 149 | #"https://sekretz.com/registry 150 | ] 151 | 152 | # This section is considered when running `cargo deny check bans`. 153 | # More documentation about the 'bans' section can be found here: 154 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 155 | [bans] 156 | # Lint level for when multiple versions of the same crate are detected 157 | multiple-versions = "warn" 158 | # Lint level for when a crate version requirement is `*` 159 | wildcards = "allow" 160 | # The graph highlighting used when creating dotgraphs for crates 161 | # with multiple versions 162 | # * lowest-version - The path to the lowest versioned duplicate is highlighted 163 | # * simplest-path - The path to the version with the fewest edges is highlighted 164 | # * all - Both lowest-version and simplest-path are used 165 | highlight = "all" 166 | # List of crates that are allowed. Use with care! 167 | allow = [ 168 | #{ name = "ansi_term", version = "=0.11.0" }, 169 | ] 170 | # List of crates to deny 171 | deny = [ 172 | # Each entry the name of a crate and a version range. If version is 173 | # not specified, all versions will be matched. 174 | #{ name = "ansi_term", version = "=0.11.0" }, 175 | # 176 | # Wrapper crates can optionally be specified to allow the crate when it 177 | # is a direct dependency of the otherwise banned crate 178 | #{ name = "ansi_term", version = "=0.11.0", wrappers = [] }, 179 | ] 180 | # Certain crates/versions that will be skipped when doing duplicate detection. 181 | skip = [ 182 | #{ name = "ansi_term", version = "=0.11.0" }, 183 | ] 184 | # Similarly to `skip` allows you to skip certain crates during duplicate 185 | # detection. Unlike skip, it also includes the entire tree of transitive 186 | # dependencies starting at the specified crate, up to a certain depth, which is 187 | # by default infinite 188 | skip-tree = [ 189 | #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, 190 | ] 191 | 192 | # This section is considered when running `cargo deny check sources`. 193 | # More documentation about the 'sources' section can be found here: 194 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 195 | [sources] 196 | # Lint level for what to happen when a crate from a crate registry that is not 197 | # in the allow list is encountered 198 | unknown-registry = "warn" 199 | # Lint level for what to happen when a crate from a git repository that is not 200 | # in the allow list is encountered 201 | unknown-git = "warn" 202 | # List of URLs for allowed crate registries. Defaults to the crates.io index 203 | # if not specified. If it is specified but empty, no registries are allowed. 204 | allow-registry = ["https://github.com/rust-lang/crates.io-index"] 205 | # List of URLs for allowed Git repositories 206 | allow-git = [] 207 | 208 | [sources.allow-org] 209 | # 1 or more github.com organizations to allow git sources for 210 | github = [] 211 | # 1 or more gitlab.com organizations to allow git sources for 212 | gitlab = [] 213 | # 1 or more bitbucket.org organizations to allow git sources for 214 | bitbucket = [] 215 | -------------------------------------------------------------------------------- /xcb_parser/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright John Nunley, 2022. 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # (See accompanying file LICENSE or copy at 4 | # https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | [package] 7 | name = "xcb_parser" 8 | version = "0.1.0" 9 | edition = "2018" 10 | license = "BSL-1.0" 11 | authors = ["notgull "] 12 | description = "A simple parser for XCB-XML files" 13 | keywords = ["xcb","parser"] 14 | 15 | [dependencies] 16 | anyhow = { version = "1.0.53", features = ["backtrace"] } 17 | quick-xml = "0.22.0" 18 | tracing = "0.1.29" 19 | -------------------------------------------------------------------------------- /xcb_parser/README.md: -------------------------------------------------------------------------------- 1 | # `xcb-parser` 2 | 3 | `xcb-parser` is a simple parser for the [XML-XCB](https://xcb.freedesktop.org/XmlXcb/) format. It is 4 | capable of taking in XML input from XML-XCB descriptions and outputting the constructs described in 5 | them. It is a wrapper on top of the [`quick-xml`](https://crates.io/crates/quick-xml) crate. 6 | 7 | ## License 8 | 9 | This package is distributed under the Boost Software License Version 1.0. 10 | Consult the [LICENSE](../LICENSE) file or consult the [web mirror] for 11 | more information. 12 | 13 | [web mirror]: https://www.boost.org/LICENSE_1_0.txt -------------------------------------------------------------------------------- /xcb_parser/src/docs.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use crate::read_text_or_cdata; 7 | use anyhow::{anyhow, Context, Result}; 8 | use quick_xml::{ 9 | events::{BytesStart, Event}, 10 | Reader, 11 | }; 12 | use std::io::BufRead; 13 | 14 | /// Documentation for an X11 item. 15 | #[derive(Debug, Clone, PartialEq, Eq)] 16 | pub struct Docs { 17 | /// A brief description of the item. 18 | pub brief: Option, 19 | /// A longer description of the item. 20 | pub description: Option, 21 | /// An example describing the item, in C code. 22 | pub example: Option, 23 | /// A list of fields in the docs. 24 | pub fields: Vec, 25 | /// A list of errors that can occur. 26 | pub errors: Vec, 27 | /// A list of references to other items. 28 | pub see_also: Vec, 29 | } 30 | 31 | impl Docs { 32 | /// Parse these docs. 33 | /// 34 | /// Thie assumes we are already in the docs block. 35 | #[inline] 36 | pub(crate) fn parse(reader: &mut Reader) -> Result { 37 | let mut buffer = vec![]; 38 | 39 | let mut brief = None; 40 | let mut description = None; 41 | let mut example = None; 42 | let mut fields = vec![]; 43 | let mut errors = vec![]; 44 | let mut see_also = vec![]; 45 | 46 | loop { 47 | match reader.read_event(&mut buffer)? { 48 | Event::Start(s) if s.name() == b"brief" => { 49 | brief = Some(read_text_or_cdata(reader)?); 50 | } 51 | Event::Start(s) if s.name() == b"description" => { 52 | description = Some(read_text_or_cdata(reader)?); 53 | } 54 | Event::Start(s) if s.name() == b"example" => { 55 | example = Some(read_text_or_cdata(reader)?); 56 | } 57 | Event::Start(s) if s.name() == b"field" => { 58 | fields.push(DocField::parse(s, reader)?); 59 | } 60 | Event::Start(s) if s.name() == b"error" => { 61 | errors.push(DocError::parse(s, reader)?); 62 | } 63 | Event::Empty(e) if e.name() == b"see" => { 64 | see_also.push(DocReference::parse(e, reader)?); 65 | } 66 | Event::End(e) if e.name() == b"doc" => { 67 | return Ok(Docs { 68 | brief, 69 | description, 70 | example, 71 | fields, 72 | errors, 73 | see_also, 74 | }); 75 | } 76 | Event::Eof => return Err(anyhow!("unexpected end of file")), 77 | _ => continue, 78 | } 79 | } 80 | } 81 | } 82 | 83 | /// A field in the documentation. 84 | #[derive(Debug, Clone, PartialEq, Eq)] 85 | pub struct DocField { 86 | /// The name of the field. 87 | pub name: String, 88 | /// A description of the field. 89 | pub description: String, 90 | } 91 | 92 | impl DocField { 93 | #[inline] 94 | fn parse(start_event: BytesStart<'_>, reader: &mut Reader) -> Result { 95 | let mut name = None; 96 | for attr in start_event.attributes() { 97 | let attr = attr?; 98 | match attr.key { 99 | b"name" => name = Some(attr.unescape_and_decode_value(reader)?), 100 | key => { 101 | return Err(anyhow!( 102 | "Unexpected attribute {}", 103 | String::from_utf8_lossy(key) 104 | )) 105 | .context("reading doc attrs") 106 | } 107 | } 108 | } 109 | 110 | let description = read_text_or_cdata(reader)?; 111 | 112 | Ok(DocField { 113 | name: name.ok_or_else(|| anyhow!("Missing name attribute"))?, 114 | description, 115 | }) 116 | } 117 | } 118 | 119 | /// An error that can occur. 120 | #[derive(Debug, Clone, PartialEq, Eq)] 121 | pub struct DocError { 122 | /// The type of the error. 123 | pub ty: String, 124 | /// A description of the error. 125 | pub description: String, 126 | } 127 | 128 | impl DocError { 129 | #[inline] 130 | fn parse(start_event: BytesStart<'_>, reader: &mut Reader) -> Result { 131 | let mut ty = None; 132 | 133 | for attr in start_event.attributes() { 134 | let attr = attr?; 135 | match attr.key { 136 | b"type" => ty = Some(attr.unescape_and_decode_value(reader)?), 137 | _ => { 138 | return Err(anyhow!( 139 | "unexpected attribute {}", 140 | String::from_utf8_lossy(attr.key) 141 | )) 142 | } 143 | } 144 | } 145 | 146 | let description = read_text_or_cdata(reader)?; 147 | 148 | Ok(DocError { 149 | ty: ty.ok_or_else(|| anyhow!("missing error type"))?, 150 | description, 151 | }) 152 | } 153 | } 154 | 155 | /// A reference to another item in the documentation. 156 | #[derive(Debug, Clone, PartialEq, Eq)] 157 | pub struct DocReference { 158 | /// The type of the reference. 159 | pub ty: String, 160 | /// The name of the reference. 161 | pub name: String, 162 | } 163 | 164 | impl DocReference { 165 | #[inline] 166 | fn parse(event: BytesStart<'_>, reader: &mut Reader) -> Result { 167 | let mut name = None; 168 | let mut ty = None; 169 | for attr in event.attributes() { 170 | let attr = attr?; 171 | match attr.key { 172 | b"name" => name = Some(attr.unescape_and_decode_value(reader)?), 173 | b"type" => ty = Some(attr.unescape_and_decode_value(reader)?), 174 | key => { 175 | return Err(anyhow!( 176 | "unknown attribute: {}", 177 | String::from_utf8_lossy(key) 178 | )) 179 | } 180 | } 181 | } 182 | Ok(DocReference { 183 | ty: ty.ok_or_else(|| anyhow!("missing type attribute"))?, 184 | name: name.ok_or_else(|| anyhow!("missing name attribute"))?, 185 | }) 186 | } 187 | } 188 | 189 | #[cfg(test)] 190 | mod tests { 191 | use super::*; 192 | use quick_xml::{events::Event, Reader}; 193 | 194 | #[test] 195 | fn test_parse_docs() { 196 | let mut reader = Reader::from_str( 197 | r#" 198 | 199 | This is a brief description. 200 | This is a longer description. 201 | This is an example. 202 | This is a field. 203 | 204 | 205 | "#, 206 | ); 207 | reader.trim_text(true); 208 | 209 | let docs = Docs::parse(&mut reader).unwrap(); 210 | 211 | assert_eq!(docs.brief, Some("This is a brief description.".into())); 212 | assert_eq!( 213 | docs.description, 214 | Some("This is a longer description.".into()) 215 | ); 216 | assert_eq!(docs.example, Some("This is an example.".into())); 217 | assert_eq!(docs.fields[0].name, "foo"); 218 | assert_eq!(docs.fields[0].description, "This is a field."); 219 | assert_eq!(docs.errors[0].ty, "bar"); 220 | assert_eq!(docs.errors[0].description, "This is an error."); 221 | assert_eq!(docs.see_also[0].ty, "baz"); 222 | assert_eq!(docs.see_also[0].name, "qux"); 223 | } 224 | 225 | #[test] 226 | fn test_parse_field() { 227 | let mut reader = Reader::from_str( 228 | r#" 229 | This is a field."#, 230 | ); 231 | 232 | let mut buf = vec![]; 233 | let start_elem = loop { 234 | match reader.read_event(&mut buf).unwrap() { 235 | Event::Start(e) => break e, 236 | _ => continue, 237 | } 238 | }; 239 | let field = DocField::parse(start_elem, &mut reader).unwrap(); 240 | 241 | assert_eq!(field.name, "foo"); 242 | assert_eq!(field.description, "This is a field."); 243 | } 244 | 245 | #[test] 246 | fn test_parse_error() { 247 | let mut reader = Reader::from_str( 248 | r#" 249 | This is an error."#, 250 | ); 251 | 252 | let mut buf = vec![]; 253 | let start_elem = loop { 254 | match reader.read_event(&mut buf).unwrap() { 255 | Event::Start(e) => break e, 256 | _ => continue, 257 | } 258 | }; 259 | let error = DocError::parse(start_elem, &mut reader).unwrap(); 260 | 261 | assert_eq!(error.ty, "bar"); 262 | assert_eq!(error.description, "This is an error."); 263 | } 264 | 265 | #[test] 266 | fn test_parse_reference() { 267 | let mut reader = Reader::from_str( 268 | r#" 269 | "#, 270 | ); 271 | 272 | let mut buf = vec![]; 273 | let start_elem = loop { 274 | match reader.read_event(&mut buf).unwrap() { 275 | Event::Empty(e) => break e, 276 | _ => continue, 277 | } 278 | }; 279 | let reference = DocReference::parse(start_elem, &mut reader).unwrap(); 280 | 281 | assert_eq!(reference.ty, "baz"); 282 | assert_eq!(reference.name, "qux"); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /xcb_parser/src/eventstruct.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | //! Event structures, which are basically a sum type for events. 7 | 8 | use crate::find_attribute; 9 | use anyhow::Result; 10 | use quick_xml::{ 11 | events::{BytesStart, Event}, 12 | Reader, 13 | }; 14 | use std::io::BufRead; 15 | 16 | /// An event struct sum type. 17 | #[derive(Debug, Clone, PartialEq, Eq)] 18 | pub struct EventStruct { 19 | /// The name of this eventstruct. 20 | pub name: String, 21 | /// The allowed selectors for this eventstruct. 22 | pub allowed: Vec, 23 | } 24 | 25 | impl EventStruct { 26 | #[inline] 27 | pub(crate) fn parse( 28 | start_elem: BytesStart<'_>, 29 | reader: &mut Reader, 30 | ) -> Result { 31 | let name = find_attribute(&start_elem, b"name", reader)?; 32 | let mut allowed = Vec::new(); 33 | let mut buffer = vec![]; 34 | 35 | loop { 36 | match reader.read_event(&mut buffer)? { 37 | Event::Start(start) | Event::Empty(start) => { 38 | if start.name() == b"allowed" { 39 | allowed.push(Allowed::parse(start, reader)?); 40 | } 41 | } 42 | Event::End(end) => { 43 | if end.name() == b"eventstruct" { 44 | break; 45 | } 46 | } 47 | _ => {} 48 | } 49 | } 50 | 51 | Ok(Self { name, allowed }) 52 | } 53 | } 54 | 55 | /// An allowed selector for an eventstruct. 56 | #[derive(Debug, Clone, PartialEq, Eq)] 57 | pub struct Allowed { 58 | /// The extension used to identify this selector. 59 | pub extension: String, 60 | /// Whether or not we consider XGE events. 61 | pub xge: bool, 62 | /// The min opcode for this selector. 63 | pub opcode_min: i64, 64 | /// The max opcode for this selector. 65 | pub opcode_max: i64, 66 | } 67 | 68 | impl Allowed { 69 | #[inline] 70 | fn parse(start_elem: BytesStart<'_>, reader: &mut Reader) -> Result { 71 | let extension = find_attribute(&start_elem, b"extension", reader)?; 72 | let xge = find_attribute(&start_elem, b"xge", reader)?; 73 | let opcode_min = find_attribute(&start_elem, b"opcode-min", reader)?; 74 | let opcode_max = find_attribute(&start_elem, b"opcode-max", reader)?; 75 | 76 | Ok(Self { 77 | extension, 78 | xge: xge.parse::()?, 79 | opcode_min: opcode_min.parse::()?, 80 | opcode_max: opcode_max.parse::()?, 81 | }) 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod tests { 87 | use super::*; 88 | use crate::read_start_event; 89 | use quick_xml::Reader; 90 | 91 | #[test] 92 | fn test_eventstruct_parse() -> Result<()> { 93 | let mut reader = Reader::from_str( 94 | r#" 95 | 96 | 97 | "#, 98 | ); 99 | 100 | let start_elem = read_start_event(&mut reader); 101 | let eventstruct = EventStruct::parse(start_elem, &mut reader)?; 102 | 103 | assert_eq!(eventstruct.name, "GetEvent"); 104 | assert_eq!(eventstruct.allowed.len(), 1); 105 | assert_eq!(eventstruct.allowed[0].extension, "XGE"); 106 | assert!(eventstruct.allowed[0].xge); 107 | assert_eq!(eventstruct.allowed[0].opcode_min, 0); 108 | assert_eq!(eventstruct.allowed[0].opcode_max, 20); 109 | 110 | Ok(()) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /xcb_parser/src/xenum.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use crate::{find_attribute, Docs, Expression}; 7 | use anyhow::{anyhow, Result}; 8 | use quick_xml::{ 9 | events::{BytesStart, Event}, 10 | Reader, 11 | }; 12 | use std::io::BufRead; 13 | 14 | /// An enumeration defined in the file. 15 | #[derive(Debug, Clone, PartialEq, Eq)] 16 | pub struct Enum { 17 | /// The name of the enum. 18 | pub name: String, 19 | /// The values in the enum. 20 | pub items: Vec, 21 | /// The documentation for the num. 22 | pub docs: Option, 23 | } 24 | 25 | impl Enum { 26 | #[inline] 27 | pub(crate) fn parse( 28 | start_elem: BytesStart<'_>, 29 | reader: &mut Reader, 30 | ) -> Result { 31 | let name = find_attribute(&start_elem, b"name", reader)?; 32 | let mut items = Vec::new(); 33 | let mut buffer = vec![]; 34 | let mut docs = None; 35 | 36 | loop { 37 | match reader.read_event(&mut buffer)? { 38 | Event::Start(start) => { 39 | if start.name() == b"item" { 40 | items.push(EnumItem::parse(start, reader)?); 41 | } else if start.name() == b"docs" { 42 | docs = Some(Docs::parse(reader)?); 43 | } 44 | } 45 | Event::End(end) => { 46 | if end.name() == b"enum" { 47 | break; 48 | } 49 | } 50 | Event::Eof => { 51 | return Err(anyhow!("parse enum: Unexpected EOF")); 52 | } 53 | _ => {} 54 | } 55 | } 56 | 57 | Ok(Self { name, items, docs }) 58 | } 59 | } 60 | 61 | /// An item in an enum. 62 | #[derive(Debug, Clone, PartialEq, Eq)] 63 | pub struct EnumItem { 64 | /// The name of the item. 65 | pub name: String, 66 | /// The value of the item. 67 | pub value: Option, 68 | } 69 | 70 | impl EnumItem { 71 | #[inline] 72 | fn parse(start_elem: BytesStart<'_>, reader: &mut Reader) -> Result { 73 | let name = find_attribute(&start_elem, b"name", reader)?; 74 | let value = Expression::parse(reader)?; 75 | 76 | // TODO: optional value... but I don't think it's ever 77 | // actually done in the protocol, so I don't think it 78 | // matters 79 | 80 | Ok(Self { 81 | name, 82 | value: Some(value), 83 | }) 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | use super::*; 90 | use crate::read_start_event; 91 | use quick_xml::Reader; 92 | 93 | #[test] 94 | fn test_parse_enum() { 95 | let mut reader = Reader::from_str( 96 | r#" 97 | 98 | 99 | 1 100 | 101 | 102 | 2 103 | 104 | 105 | "#, 106 | ); 107 | 108 | let start_elem = read_start_event(&mut reader); 109 | let enum_ = Enum::parse(start_elem, &mut reader).unwrap(); 110 | assert_eq!(enum_.name, "foo"); 111 | assert_eq!(enum_.items.len(), 2); 112 | assert_eq!(enum_.items[0].name, "bar"); 113 | assert_eq!(enum_.items[0].value, Some(Expression::Value(1))); 114 | assert_eq!(enum_.items[1].name, "baz"); 115 | assert_eq!(enum_.items[1].value, Some(Expression::Bit(2))); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /xcb_parser/tests/parse_xproto_test.rs: -------------------------------------------------------------------------------- 1 | // Copyright John Nunley, 2022. 2 | // Distributed under the Boost Software License, Version 1.0. 3 | // (See accompanying file LICENSE or copy at 4 | // https://www.boost.org/LICENSE_1_0.txt) 5 | 6 | use anyhow::Context; 7 | use std::{collections::HashMap, fs::read_dir, path::Path}; 8 | use xcb_parser::{read_xcb_from_file, Expression, ToplevelItem}; 9 | 10 | const MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR"); 11 | const XPROTO_ROOT: &str = "../xcbproto/src"; 12 | 13 | #[test] 14 | fn parse_all_protocol() { 15 | // iterate over the xproto directory, read in items and then parse them 16 | let protocol: HashMap<_, _> = read_dir(Path::new(MANIFEST_DIR).join(XPROTO_ROOT)) 17 | .expect("Failed to read dir") 18 | .map(|x| x.expect("Failed to read dir entry")) 19 | .filter(|x| x.path().extension().map_or(false, |ext| ext == "xml")) 20 | .map(|x| x.path()) 21 | .map(|x| (x.clone(), read_xcb_from_file(&x))) 22 | .map(|(path, iter)| { 23 | let name = path.file_stem().unwrap().to_str().unwrap().to_string(); 24 | let data = iter 25 | .expect("Failed to read xcb file") 26 | .collect::, _>>() 27 | .with_context(|| format!("Failed while reading {}", &name)) 28 | .expect("Failed to read entries"); 29 | (name, data) 30 | }) 31 | .collect(); 32 | 33 | // test some operations on the "xproto" module 34 | let xproto = protocol.get("xproto").unwrap(); 35 | assert!(xproto.iter().any(|x| match x { 36 | ToplevelItem::Struct(x) => x.name == "CHAR2B", 37 | _ => false, 38 | })); 39 | assert!(xproto.iter().any(|x| match x { 40 | ToplevelItem::Xidtype(x) => x == "WINDOW", 41 | _ => false, 42 | })); 43 | assert!(xproto.iter().any(|x| match x { 44 | ToplevelItem::Typedef { oldname, newname } => oldname == "CARD32" && newname == "VISUALID", 45 | _ => false, 46 | })); 47 | assert!(xproto.iter().any(|x| match x { 48 | ToplevelItem::Enum(x) => 49 | x.name == "VisualClass" && { 50 | x.items 51 | .iter() 52 | .any(|v| v.name == "TrueColor" && v.value == Some(Expression::Value(4))) 53 | }, 54 | _ => false, 55 | })); 56 | assert!(xproto.iter().any(|x| match x { 57 | ToplevelItem::Event(x) => { 58 | x.name == "ButtonPress" 59 | && x.number == 4 60 | && x.docs 61 | .as_ref() 62 | .unwrap() 63 | .fields 64 | .iter() 65 | .any(|d| d.description.contains("event was generated")) 66 | } 67 | _ => false, 68 | })); 69 | assert!(xproto.iter().any(|x| match x { 70 | ToplevelItem::EventCopy(mc) => 71 | mc.name == "KeyRelease" && mc.number == 3 && mc.ref_ == "KeyPress", 72 | _ => false, 73 | })); 74 | } 75 | --------------------------------------------------------------------------------