├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── publish.yml │ └── ci.yml ├── cast.apng ├── .gitignore ├── release.toml ├── Cargo.toml ├── LICENSE ├── CHANGELOG.md ├── src ├── wm_i3.rs ├── args.rs ├── main.rs └── utils.rs ├── README.md └── Cargo.lock /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: svenstaro 2 | -------------------------------------------------------------------------------- /cast.apng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svenstaro/wmfocus/HEAD/cast.apng -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | 8 | /target 9 | **/*.rs.bk 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | groups: 8 | all-dependencies: 9 | patterns: 10 | - "*" 11 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | sign-commit = true 2 | sign-tag = true 3 | pre-release-replacements = [ 4 | {file="README.md", search="wmfocus [0-9][a-z0-9\\.-]+", replace="wmfocus {{version}}"}, 5 | {file="CHANGELOG.md", search="Unreleased", replace="{{version}}"}, 6 | {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, 7 | {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}"}, 8 | {file="CHANGELOG.md", search="", replace="\n\n## [Unreleased] - ReleaseDate"}, 9 | {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/svenstaro/wmfocus/compare/{{tag_name}}...HEAD", exactly=1}, 10 | ] 11 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wmfocus" 3 | description = "Visually focus windows by label" 4 | version = "1.5.0" 5 | repository = "https://github.com/svenstaro/wmfocus" 6 | authors = ["Sven-Hendrik Haase "] 7 | license = "MIT" 8 | readme = "README.md" 9 | keywords = ["wm", "window", "manager", "i3"] 10 | categories = ["command-line-utilities", "command-line-interface"] 11 | edition = "2021" 12 | 13 | [profile.release] 14 | lto = true 15 | codegen-units = 1 16 | 17 | [features] 18 | i3 = ["i3ipc"] 19 | 20 | [dependencies] 21 | cairo-rs = { version = "0.20", features = ["xcb"] } 22 | css-color-parser = "0.1" 23 | font-loader = "0.11" 24 | i3ipc = { version = "0.10", optional = true } 25 | itertools = "0.13" 26 | log = "0.4" 27 | pretty_env_logger = "0.5" 28 | regex = "1.10" 29 | clap = { version = "4", features = ["derive", "cargo", "wrap_help", "deprecated"] } 30 | anyhow = "1" 31 | x11rb = { version = "0.13", features = ["allow-unsafe-code"] } 32 | xkeysym = "0.2.0" 33 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | publish: 10 | name: Publish 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | 16 | - name: Get tag name 17 | id: tag_name 18 | run: | 19 | echo ::set-output name=current_version::${GITHUB_REF#refs/tags/v} 20 | shell: bash 21 | 22 | - name: Get CHANGELOG.md entry 23 | id: changelog_reader 24 | uses: mindsers/changelog-reader-action@v1 25 | with: 26 | version: ${{ steps.tag.outputs.current_version }} 27 | path: ./CHANGELOG.md 28 | 29 | - name: Release 30 | uses: actions/create-release@v1 31 | env: 32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 33 | with: 34 | tag_name: ${{ github.ref }} 35 | release_name: ${{ github.ref }} 36 | body: 37 | ${{ steps.changelog_reader.outputs.log_entry }} 38 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | name: CI with ${{ matrix.rust }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | matrix: 11 | rust: [stable, nightly] 12 | 13 | steps: 14 | - run: sudo apt install libfontconfig1-dev libxkbcommon-dev libxcb-keysyms1-dev 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | 18 | - name: cargo build 19 | uses: actions-rs/cargo@v1 20 | with: 21 | command: build 22 | args: --all-features 23 | 24 | - name: cargo test 25 | uses: actions-rs/cargo@v1 26 | with: 27 | command: test 28 | args: --all-features 29 | 30 | - name: cargo fmt 31 | uses: actions-rs/cargo@v1 32 | with: 33 | command: fmt 34 | args: --all -- --check 35 | 36 | - name: cargo clippy 37 | uses: actions-rs/cargo@v1 38 | with: 39 | command: clippy 40 | args: --all-features -- -D warnings 41 | 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sven-Hendrik Haase 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/). 7 | 8 | 9 | 10 | ## [Unreleased] - ReleaseDate 11 | 12 | ## [1.5.0] - 2024-01-01 13 | - Add the `--swap` flag to swap the currently active window with the selected window [#325](https://github.com/svenstaro/wmfocus/pull/325) (thanks @Nukesor) 14 | 15 | ## [1.4.0] - 2023-01-22 16 | - Modernize all dependencies 17 | - Fix border offset issue resulting from newer versions of i3 18 | 19 | ## [1.3.0] - 2021-10-22 20 | - Highlight currently selected window (also adds `--textcolorcurrent`, `--textcolorcurrentalt`, `--bgcolorcurrent`) [#82](https://github.com/svenstaro/wmfocus/issues/82) 21 | 22 | ## [1.2.0] - 2021-07-11 23 | - Add -e/--exit-keys to choose specific keys to quit wmfocus instead of every key (thanks @Rephobia) 24 | 25 | ## [1.1.5] - 2020-10-26 26 | - Fix horizontal positions of labels for tabbed layout [#70](https://github.com/svenstaro/wmfocus/issues/70) 27 | 28 | ## [1.1.4] - 2020-08-07 29 | - Rewrite argument parsing to be more robust [#63](https://github.com/svenstaro/wmfocus/issues/63) 30 | - Add some debug information for font loading 31 | 32 | ## [1.1.3] - 2019-07-02 33 | - Add offset parameter [#17](https://github.com/svenstaro/wmfocus/pull/17) (thanks @jeffmhubbard) 34 | 35 | ## [1.1.2] - 2019-01-08 36 | - Bump some deps 37 | 38 | ## [1.1.1] - 2019-01-07 39 | - Print X window client id instead of internal i3 id [#10](https://github.com/svenstaro/wmfocus/issues/10) 40 | 41 | ## [1.1.0] - 2018-12-14 42 | - Make help colorful 43 | - Properly handle tabbed and stacked windows [#5](https://github.com/svenstaro/wmfocus/issues/5) 44 | - Make sure that labels don't overlap [#7](https://github.com/svenstaro/wmfocus/issues/7) 45 | - Update to Rust 2018 edition 46 | 47 | 48 | [Unreleased]: https://github.com/svenstaro/wmfocus/compare/v1.5.0...HEAD 49 | [1.5.0]: https://github.com/svenstaro/wmfocus/compare/v1.4.0...v1.5.0 50 | [1.4.0]: https://github.com/svenstaro/wmfocus/compare/v1.3.0...v1.4.0 51 | [1.3.0]: https://github.com/svenstaro/wmfocus/compare/v1.2.0...v1.3.0 52 | [1.2.0]: https://github.com/svenstaro/wmfocus/compare/v1.1.5...v1.2.0 53 | [1.1.5]: https://github.com/svenstaro/wmfocus/compare/v1.1.4...v1.1.5 54 | [1.1.4]: https://github.com/svenstaro/wmfocus/compare/1.1.3...v1.1.4 55 | [1.1.3]: https://github.com/svenstaro/wmfocus/compare/1.1.2...1.1.3 56 | [1.1.2]: https://github.com/svenstaro/wmfocus/compare/1.1.1...1.1.2 57 | [1.1.1]: https://github.com/svenstaro/wmfocus/compare/1.1.0...1.1.1 58 | [1.1.0]: https://github.com/svenstaro/wmfocus/compare/1.0.2...1.1.0 59 | -------------------------------------------------------------------------------- /src/wm_i3.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use i3ipc::reply::{Node, NodeLayout, NodeType, Workspace}; 3 | use i3ipc::I3Connection; 4 | use log::{debug, info}; 5 | 6 | use crate::DesktopWindow; 7 | 8 | /// Find first `Node` that fulfills a given criterion. 9 | fn find_first_node_with_attr(start_node: &Node, predicate: F) -> Option<&Node> 10 | where 11 | F: Fn(&Node) -> bool, 12 | { 13 | let mut nodes_to_explore: Vec<&Node> = start_node.nodes.iter().collect(); 14 | while !nodes_to_explore.is_empty() { 15 | let mut next_vec = vec![]; 16 | for node in &nodes_to_explore { 17 | if predicate(node) { 18 | return Some(node); 19 | } 20 | next_vec.extend(node.nodes.iter()); 21 | } 22 | nodes_to_explore = next_vec; 23 | } 24 | None 25 | } 26 | 27 | /// Find parent of `child`. 28 | fn find_parent_of<'a>(start_node: &'a Node, child: &'a Node) -> Option<&'a Node> { 29 | let mut nodes_to_explore: Vec<&Node> = start_node.nodes.iter().collect(); 30 | while !nodes_to_explore.is_empty() { 31 | let mut next_vec = vec![]; 32 | for node in &nodes_to_explore { 33 | if node.nodes.iter().any(|x| child.id == x.id) { 34 | return Some(node); 35 | } 36 | next_vec.extend(node.nodes.iter()); 37 | } 38 | nodes_to_explore = next_vec; 39 | } 40 | None 41 | } 42 | 43 | /// Return a list of all `DesktopWindow`s for the given `Workspace`. 44 | fn crawl_windows(root_node: &Node, workspace: &Workspace) -> Result> { 45 | let workspace_node = find_first_node_with_attr(root_node, |x| { 46 | x.name == Some(workspace.name.clone()) && x.nodetype == NodeType::Workspace 47 | }) 48 | .context("Couldn't find the Workspace node")?; 49 | 50 | let mut nodes_to_explore: Vec<&Node> = workspace_node.nodes.iter().collect(); 51 | nodes_to_explore.extend(workspace_node.floating_nodes.iter()); 52 | let mut windows = vec![]; 53 | while !nodes_to_explore.is_empty() { 54 | let mut next_vec = vec![]; 55 | for node in &nodes_to_explore { 56 | next_vec.extend(node.nodes.iter()); 57 | next_vec.extend(node.floating_nodes.iter()); 58 | if node.window.is_some() { 59 | let root_node = find_parent_of(root_node, node); 60 | 61 | let (pos_x, size_x) = if let Some(root_node) = root_node { 62 | if root_node.layout == NodeLayout::Tabbed { 63 | (node.rect.0 + node.deco_rect.0, node.deco_rect.2) 64 | } else { 65 | (node.rect.0, node.rect.2) 66 | } 67 | } else { 68 | (node.rect.0, node.rect.2) 69 | }; 70 | 71 | let pos_y = if let Some(root_node) = root_node { 72 | if root_node.layout == NodeLayout::Stacked { 73 | root_node.rect.1 + node.deco_rect.1 74 | } else { 75 | node.rect.1 + node.deco_rect.3 76 | } 77 | } else { 78 | node.rect.1 + node.deco_rect.3 79 | }; 80 | 81 | let window = DesktopWindow { 82 | id: node.id, 83 | x_window_id: node.window, 84 | pos: (pos_x, pos_y), 85 | size: (size_x, (node.rect.3 + node.deco_rect.3)), 86 | is_focused: node.focused, 87 | }; 88 | debug!("Found {:?}", window); 89 | windows.push(window); 90 | } 91 | } 92 | nodes_to_explore = next_vec; 93 | } 94 | Ok(windows) 95 | } 96 | 97 | /// Return a list of all windows. 98 | pub fn get_windows() -> Result> { 99 | // Establish a connection to i3 over a unix socket 100 | let mut connection = I3Connection::connect().context("Couldn't acquire i3 connection")?; 101 | let workspaces = connection 102 | .get_workspaces() 103 | .context("Problem communicating with i3")? 104 | .workspaces; 105 | let visible_workspaces = workspaces.iter().filter(|w| w.visible); 106 | let root_node = connection.get_tree()?; 107 | let mut windows = vec![]; 108 | for workspace in visible_workspaces { 109 | windows.extend(crawl_windows(&root_node, workspace)?); 110 | } 111 | Ok(windows) 112 | } 113 | 114 | /// Focus a specific `window`. 115 | pub fn focus_window(window: &DesktopWindow) -> Result<()> { 116 | let mut connection = I3Connection::connect().context("Couldn't acquire i3 connection")?; 117 | let command_str = format!("[con_id=\"{}\"] focus", window.id); 118 | let command = connection 119 | .run_command(&command_str) 120 | .context("Couldn't communicate with i3")?; 121 | info!("Sending to i3: {:?}", command); 122 | Ok(()) 123 | } 124 | 125 | /// Focus a specific `window`. 126 | pub fn swap_windows(active_window: &DesktopWindow, window: &DesktopWindow) -> Result<()> { 127 | let mut connection = I3Connection::connect().context("Couldn't acquire i3 connection")?; 128 | let command_str = format!( 129 | "[con_id=\"{}\"] swap with container con_id {}", 130 | active_window.id, window.id 131 | ); 132 | let command = connection 133 | .run_command(&command_str) 134 | .context("Couldn't communicate with i3")?; 135 | info!("Sending to i3: {:?}", command); 136 | Ok(()) 137 | } 138 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wmfocus - Visually focus windows by label 2 | 3 | [![CI](https://github.com/svenstaro/wmfocus/workflows/CI/badge.svg)](https://github.com/svenstaro/wmfocus/actions) 4 | [![Crates.io](https://img.shields.io/crates/v/wmfocus.svg)](https://crates.io/crates/wmfocus) 5 | [![license](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/svenstaro/wmfocus/blob/master/LICENSE) 6 | [![Stars](https://img.shields.io/github/stars/svenstaro/wmfocus.svg)](https://github.com/svenstaro/wmfocus/stargazers) 7 | [![Lines of Code](https://tokei.rs/b1/github/svenstaro/wmfocus)](https://github.com/svenstaro/wmfocus) 8 | 9 | This tool that allows you to rapidly choose a specific window directly without having to use the mouse or directional keyboard navigation. 10 | 11 | ![Screen cast](cast.apng) 12 | 13 | Thanks to cairo, it should work on all kinds of screens and automatically display at the correct size according to your DPI. 14 | 15 | 16 | ## Installation 17 | 18 | Packaging status 19 | 20 | **On Arch Linux**: `pacman -S wmfocus` 21 | 22 | **With Cargo**: `cargo install --features i3 wmfocus` 23 | 24 | ## Usage 25 | 26 | Draw labels on the upper-left corner of all windows: 27 | 28 | wmfocus 29 | 30 | Completely fill out windows and draw the label in the middle (try it with transparency!): 31 | 32 | wmfocus --fill 33 | 34 | Use a different font (as provided by fontconfig): 35 | 36 | wmfocus -f "Droid Sans":100 37 | 38 | Change up the default colors: 39 | 40 | wmfocus --textcolor red --textcoloralt "#eeeeee" --bgcolor "rgba(50, 50, 200, 0.5)" 41 | 42 | wmfocus will make use of a compositor to get real transparency. 43 | 44 | ## Full help 45 | ``` 46 | wmfocus 1.5.0 47 | 48 | Sven-Hendrik Haase 49 | 50 | Visually focus windows by label 51 | 52 | USAGE: 53 | wmfocus [OPTIONS] 54 | 55 | OPTIONS: 56 | --textcolor Text color (CSS notation) [default: #dddddd] 57 | --textcoloralt Text color alternate (CSS notation) [default: #666666] 58 | --bgcolor Background color (CSS notation) [default: "rgba(30, 30, 30, 0.9)"] 59 | --textcolorcurrent Text color current window (CSS notation) [default: #333333] 60 | --textcolorcurrentalt Text color current window alternate (CSS notation) [default: #999999] 61 | --bgcolorcurrent Background color current window (CSS notation) [default: "rgba(200, 200, 200, 0.9)"] 62 | --halign Horizontal alignment of the box inside the window [default: left] [possible values: left, center, right] 63 | --valign Vertical alignment of the box inside the window [default: top] [possible values: top, center, bottom] 64 | --fill Completely fill out windows 65 | -c, --chars Define a set of possbile values to use as hint characters [default: sadfjklewcmpgh] 66 | -e, --exit-keys ... List of keys to exit application, sequences separator is space, key separator is '+', eg Control_L+g 67 | Shift_L+f 68 | -f, --font Use a specific TrueType font with this format: family:size [default: Mono:72] 69 | -h, --help Print help information 70 | -m, --margin Add an additional margin around the text box (value is a factor of the box size) [default: 0.2] 71 | -o, --offset Offset box from edge of window relative to alignment (x,y) [default: 0,0] 72 | -p, --print-only Print the window id only but don't change focus 73 | -V, --version Print version information 74 | ``` 75 | 76 | ## Troubleshooting 77 | 78 | If there's some funky stuff, you can try to track it down by running `wmfocus` with `RUST_LOG=trace`: 79 | 80 | RUST_LOG=trace wmfocus 81 | 82 | This will print quite some useful debugging info. 83 | 84 | 85 | ## Compiling 86 | 87 | You need to have recent versions of `rust`, `cargo`, `xcb-util-keysyms`, `libxkbcommon-x11` and `cairo` installed. 88 | 89 | Then, just clone it like usual and `cargo run` to get output: 90 | 91 | git clone https://github.com/svenstaro/wmfocus.git 92 | cd wmfocus 93 | cargo run --features i3 94 | 95 | 96 | ## Window manager support 97 | 98 | While this tool is window manager-independent, an implementation for your favorite window manager might not yet be available. Current support: 99 | 100 | - i3 101 | - sway (partial, accepting PRs) 102 | 103 | If you want to implement support for more window managers, have a look at the [i3 implementation](https://github.com/svenstaro/wmfocus/blob/master/src/wm_i3.rs). 104 | 105 | This tool is heavily inspired by [i3-easyfocus](https://github.com/cornerman/i3-easyfocus). 106 | 107 | 108 | ## Releasing 109 | 110 | This is mostly a note for me on how to release this thing: 111 | 112 | - `cargo release` 113 | - `cargo release --execute` 114 | - Release will automatically be deployed by GitHub Actions. 115 | - Update Arch package. 116 | -------------------------------------------------------------------------------- /src/args.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Context, Result}; 2 | use clap::{Parser, ValueEnum}; 3 | use css_color_parser::Color as CssColor; 4 | use font_loader::system_fonts; 5 | use log::{info, warn}; 6 | 7 | use crate::utils; 8 | 9 | #[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Hash)] 10 | pub enum HorizontalAlign { 11 | Left, 12 | Center, 13 | Right, 14 | } 15 | 16 | #[derive(ValueEnum, Debug, Clone, Copy, PartialEq, Eq, Hash)] 17 | pub enum VerticalAlign { 18 | Top, 19 | Center, 20 | Bottom, 21 | } 22 | 23 | /// Load a system font. 24 | fn load_font(font_family: &str) -> Result> { 25 | let mut font_family_property = system_fonts::FontPropertyBuilder::new() 26 | .family(font_family) 27 | .build(); 28 | let info = system_fonts::query_specific(&mut font_family_property); 29 | info!("Returned effective font is: {:?}", info); 30 | let (loaded_font, _) = 31 | if let Some((loaded_font, index)) = system_fonts::get(&font_family_property) { 32 | (loaded_font, index) 33 | } else { 34 | warn!("Family not found, falling back to first Monospace font"); 35 | let mut font_monospace_property = 36 | system_fonts::FontPropertyBuilder::new().monospace().build(); 37 | let sysfonts = system_fonts::query_specific(&mut font_monospace_property); 38 | warn!("Falling back to font '{font}'", font = sysfonts[0]); 39 | let (loaded_font, index) = system_fonts::get(&font_monospace_property) 40 | .context("Couldn't find suitable font")?; 41 | (loaded_font, index) 42 | }; 43 | Ok(loaded_font) 44 | } 45 | 46 | /// Generate a valid `FontConfig` from `f`. 47 | /// `f` is expected to be in format `Mono:72`. 48 | fn parse_truetype_font(f: &str) -> Result { 49 | let mut v = f.split(':'); 50 | let (family, size) = ( 51 | v.next().context("Wrong font format")?, 52 | v.next().context("Wrong font format")?, 53 | ); 54 | 55 | let loaded_font = load_font(family).context("Couldn't load font")?; 56 | let font_config = FontConfig { 57 | font_family: family.to_string(), 58 | font_size: size.parse::().context("Couldn't parse font size")?, 59 | loaded_font, 60 | }; 61 | Ok(font_config) 62 | } 63 | 64 | /// Validate coordinates and parse offset. 65 | fn parse_offset(c: &str) -> Result { 66 | let mut v = c.split(','); 67 | let (x, y) = ( 68 | v.next() 69 | .ok_or("Wrong coordinate format, expected x,y coordinates")?, 70 | v.next() 71 | .ok_or("Wrong coordinate format, expected x,y coordinates")?, 72 | ); 73 | let offset = Offset { 74 | x: x.parse::() 75 | .map_err(|_| "Couldn't parse x coordinate")?, 76 | y: y.parse::() 77 | .map_err(|_| "Couldn't parse y coordinate")?, 78 | }; 79 | Ok(offset) 80 | } 81 | 82 | /// Parse a color into a tuple of floats. 83 | fn parse_color(color_str: &str) -> Result<(f64, f64, f64, f64), String> { 84 | let color = color_str 85 | .parse::() 86 | .map_err(|_| "Invalid color format")?; 87 | Ok(( 88 | f64::from(color.r) / 255.0, 89 | f64::from(color.g) / 255.0, 90 | f64::from(color.b) / 255.0, 91 | f64::from(color.a), 92 | )) 93 | } 94 | 95 | #[derive(Debug, Clone)] 96 | pub struct Offset { 97 | pub x: i32, 98 | pub y: i32, 99 | } 100 | 101 | #[derive(Debug, Clone)] 102 | pub struct FontConfig { 103 | pub font_family: String, 104 | pub font_size: f64, 105 | pub loaded_font: Vec, 106 | } 107 | 108 | fn parse_exit_keys(s: &str) -> Result { 109 | Ok(utils::Sequence::new(Some(s))) 110 | } 111 | 112 | #[derive(Parser, Debug)] 113 | #[command(name = "wmfocus", author, about, version)] 114 | pub struct AppConfig { 115 | /// Use a specific TrueType font with this format: family:size 116 | #[arg( 117 | short, 118 | long, 119 | default_value = "Mono:72", 120 | value_parser(parse_truetype_font) 121 | )] 122 | pub font: FontConfig, 123 | 124 | /// Define a set of possbile values to use as hint characters 125 | #[arg(short = 'c', long = "chars", default_value = "sadfjklewcmpgh")] 126 | pub hint_chars: String, 127 | 128 | /// Add an additional margin around the text box (value is a factor of the box size) 129 | #[arg(short, long, default_value = "0.2")] 130 | pub margin: f32, 131 | 132 | /// Text color (CSS notation) 133 | #[arg( 134 | long = "textcolor", 135 | display_order = 49, 136 | default_value = "#dddddd", 137 | value_parser(parse_color) 138 | )] 139 | pub text_color: (f64, f64, f64, f64), 140 | 141 | /// Text color alternate (CSS notation) 142 | #[arg( 143 | long = "textcoloralt", 144 | display_order = 50, 145 | default_value = "#666666", 146 | value_parser(parse_color) 147 | )] 148 | pub text_color_alt: (f64, f64, f64, f64), 149 | 150 | /// Background color (CSS notation) 151 | #[arg( 152 | long = "bgcolor", 153 | display_order = 51, 154 | default_value = "rgba(30, 30, 30, 0.9)", 155 | value_parser(parse_color) 156 | )] 157 | pub bg_color: (f64, f64, f64, f64), 158 | 159 | /// Text color current window (CSS notation) 160 | #[arg( 161 | long = "textcolorcurrent", 162 | display_order = 52, 163 | default_value = "#333333", 164 | value_parser(parse_color) 165 | )] 166 | pub text_color_current: (f64, f64, f64, f64), 167 | 168 | /// Text color current window alternate (CSS notation) 169 | #[arg( 170 | long = "textcolorcurrentalt", 171 | display_order = 53, 172 | default_value = "#999999", 173 | value_parser(parse_color) 174 | )] 175 | pub text_color_current_alt: (f64, f64, f64, f64), 176 | 177 | /// Background color current window (CSS notation) 178 | #[arg( 179 | long = "bgcolorcurrent", 180 | display_order = 54, 181 | default_value = "rgba(200, 200, 200, 0.9)", 182 | value_parser(parse_color) 183 | )] 184 | pub bg_color_current: (f64, f64, f64, f64), 185 | 186 | /// Horizontal alignment of the box inside the window 187 | #[arg( 188 | long = "halign", 189 | display_order = 100, 190 | default_value = "left", 191 | ignore_case = true 192 | )] 193 | pub horizontal_align: HorizontalAlign, 194 | 195 | /// Vertical alignment of the box inside the window 196 | #[arg( 197 | long = "valign", 198 | display_order = 101, 199 | default_value = "top", 200 | ignore_case = true 201 | )] 202 | pub vertical_align: VerticalAlign, 203 | 204 | /// Completely fill out windows 205 | #[arg(long, display_order = 102, conflicts_with_all(&["horizontal_align", "vertical_align", "margin", "offset"]))] 206 | pub fill: bool, 207 | 208 | /// Print the window id only but don't change focus 209 | #[arg(short, long)] 210 | pub print_only: bool, 211 | 212 | /// Offset box from edge of window relative to alignment (x,y) 213 | #[arg( 214 | short, 215 | long, 216 | allow_hyphen_values = true, 217 | default_value = "0,0", 218 | value_parser(parse_offset) 219 | )] 220 | pub offset: Offset, 221 | 222 | /// List of keys to exit application, sequences separator is space, key separator is '+', eg Control_L+g Shift_L+f 223 | #[arg(short, long, value_parser(parse_exit_keys))] 224 | pub exit_keys: Vec, 225 | 226 | /// If this flag is set, the currently active window will swap with the selected window. 227 | #[arg(short, long)] 228 | pub swap: bool, 229 | } 230 | 231 | pub fn parse_args() -> AppConfig { 232 | let mut config = AppConfig::parse(); 233 | if config.fill { 234 | config.horizontal_align = HorizontalAlign::Center; 235 | config.vertical_align = VerticalAlign::Center; 236 | } 237 | config 238 | } 239 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::time::Duration; 3 | 4 | use anyhow::{Context, Result}; 5 | use log::{debug, info, warn}; 6 | use x11rb::xcb_ffi::XCBConnection; 7 | 8 | use x11rb::{ 9 | connection::Connection, 10 | protocol::xproto::{self, ConnectionExt as _}, 11 | protocol::Event, 12 | wrapper::ConnectionExt, 13 | }; 14 | 15 | mod args; 16 | mod utils; 17 | 18 | #[cfg(feature = "i3")] 19 | extern crate i3ipc; 20 | 21 | #[cfg(feature = "i3")] 22 | mod wm_i3; 23 | 24 | #[cfg(feature = "i3")] 25 | use crate::wm_i3 as wm; 26 | 27 | #[derive(Debug)] 28 | pub struct DesktopWindow { 29 | id: i64, 30 | x_window_id: Option, 31 | pos: (i32, i32), 32 | size: (i32, i32), 33 | is_focused: bool, 34 | } 35 | 36 | #[derive(Debug)] 37 | pub struct RenderWindow<'a> { 38 | desktop_window: &'a DesktopWindow, 39 | cairo_context: cairo::Context, 40 | draw_pos: (f64, f64), 41 | rect: (i32, i32, i32, i32), 42 | } 43 | 44 | #[cfg(any(feature = "i3", feature = "add_some_other_wm_here"))] 45 | fn main() -> Result<()> { 46 | pretty_env_logger::init(); 47 | let app_config = args::parse_args(); 48 | 49 | // Get the windows from each specific window manager implementation. 50 | let desktop_windows_raw = wm::get_windows().context("Couldn't get desktop windows")?; 51 | 52 | // Sort by position to make hint position more deterministic. 53 | let desktop_windows = utils::sort_by_pos(desktop_windows_raw); 54 | 55 | let (conn, screen_num) = XCBConnection::connect(None).context("No Xorg connection")?; 56 | let screen = &conn.setup().roots[screen_num]; 57 | 58 | // Assemble RenderWindows from DesktopWindows. 59 | let mut render_windows = HashMap::new(); 60 | for desktop_window in &desktop_windows { 61 | // We need to estimate the font size before rendering because we want the window to only be 62 | // the size of the font. 63 | let hint = utils::get_next_hint( 64 | render_windows.keys().collect(), 65 | &app_config.hint_chars, 66 | desktop_windows.len(), 67 | ) 68 | .context("Couldn't get next hint")?; 69 | 70 | // Figure out how large the window actually needs to be. 71 | let text_extents = utils::extents_for_text( 72 | &hint, 73 | &app_config.font.font_family, 74 | app_config.font.font_size, 75 | ) 76 | .context("Couldn't create extents for text")?; 77 | let (width, height, margin_width, margin_height) = if app_config.fill { 78 | ( 79 | desktop_window.size.0 as u16, 80 | desktop_window.size.1 as u16, 81 | (f64::from(desktop_window.size.0) - text_extents.width()) / 2.0, 82 | (f64::from(desktop_window.size.1) - text_extents.height()) / 2.0, 83 | ) 84 | } else { 85 | let margin_factor = 1.0 + 0.2; 86 | ( 87 | (text_extents.width() * margin_factor).round() as u16, 88 | (text_extents.height() * margin_factor).round() as u16, 89 | ((text_extents.width() * margin_factor) - text_extents.width()) / 2.0, 90 | ((text_extents.height() * margin_factor) - text_extents.height()) / 2.0, 91 | ) 92 | }; 93 | 94 | // Due to the way cairo lays out text, we'll have to calculate the actual coordinates to 95 | // put the cursor. See: 96 | // https://www.cairographics.org/samples/text_align_center/ 97 | // https://www.cairographics.org/samples/text_extents/ 98 | // https://www.cairographics.org/tutorial/#L1understandingtext 99 | let draw_pos = ( 100 | margin_width - text_extents.x_bearing(), 101 | text_extents.height() + margin_height 102 | - (text_extents.height() + text_extents.y_bearing()), 103 | ); 104 | 105 | debug!( 106 | "Spawning RenderWindow for this DesktopWindow: {:?}", 107 | desktop_window 108 | ); 109 | 110 | let x_offset = app_config.offset.x; 111 | let mut x = match app_config.horizontal_align { 112 | args::HorizontalAlign::Left => (desktop_window.pos.0 + x_offset) as i16, 113 | args::HorizontalAlign::Center => { 114 | (desktop_window.pos.0 + desktop_window.size.0 / 2 - i32::from(width) / 2) as i16 115 | } 116 | args::HorizontalAlign::Right => { 117 | (desktop_window.pos.0 + desktop_window.size.0 - i32::from(width) - x_offset) as i16 118 | } 119 | }; 120 | 121 | let y_offset = app_config.offset.y; 122 | let y = match app_config.vertical_align { 123 | args::VerticalAlign::Top => (desktop_window.pos.1 + y_offset) as i16, 124 | args::VerticalAlign::Center => { 125 | (desktop_window.pos.1 + desktop_window.size.1 / 2 - i32::from(height) / 2) as i16 126 | } 127 | args::VerticalAlign::Bottom => { 128 | (desktop_window.pos.1 + desktop_window.size.1 - i32::from(height) - y_offset) as i16 129 | } 130 | }; 131 | 132 | // If this is overlapping then we'll nudge the new RenderWindow a little bit out of the 133 | // way. 134 | let mut overlaps = utils::find_overlaps( 135 | render_windows.values().collect(), 136 | (x.into(), y.into(), width.into(), height.into()), 137 | ); 138 | while !overlaps.is_empty() { 139 | x += overlaps.pop().unwrap().2 as i16; 140 | overlaps = utils::find_overlaps( 141 | render_windows.values().collect(), 142 | (x.into(), y.into(), width.into(), height.into()), 143 | ); 144 | } 145 | 146 | let xcb_window_id = conn.generate_id()?; 147 | 148 | let win_aux = xproto::CreateWindowAux::new() 149 | .event_mask( 150 | xproto::EventMask::EXPOSURE 151 | | xproto::EventMask::KEY_PRESS 152 | | xproto::EventMask::BUTTON_PRESS 153 | | xproto::EventMask::BUTTON_RELEASE, 154 | ) 155 | .backing_pixel(screen.black_pixel) 156 | .override_redirect(1); 157 | 158 | // Create the actual window. 159 | xproto::create_window( 160 | &conn, 161 | x11rb::COPY_FROM_PARENT as u8, 162 | xcb_window_id, 163 | screen.root, 164 | x, 165 | y, 166 | width, 167 | height, 168 | 0, 169 | xproto::WindowClass::INPUT_OUTPUT, 170 | screen.root_visual, 171 | &win_aux, 172 | )?; 173 | 174 | conn.map_window(xcb_window_id)?; 175 | 176 | // Set transparency. 177 | let opacity_atom = conn 178 | .intern_atom(false, b"_NET_WM_WINDOW_OPACITY")? 179 | .reply() 180 | .context("Couldn't create atom _NET_WM_WINDOW_OPACITY")? 181 | .atom; 182 | let opacity = (0xFFFFFFFFu64 as f64 * app_config.bg_color.3) as u64; 183 | conn.change_property32( 184 | xproto::PropMode::REPLACE, 185 | xcb_window_id, 186 | opacity_atom, 187 | xproto::AtomEnum::CARDINAL, 188 | &[opacity as u32], 189 | )?; 190 | 191 | conn.flush()?; 192 | 193 | let mut visual = utils::find_xcb_visualtype(&conn, screen.root_visual) 194 | .context("Couldn't find visual")?; 195 | let cairo_conn = 196 | unsafe { cairo::XCBConnection::from_raw_none(conn.get_raw_xcb_connection() as _) }; 197 | let cairo_visual = 198 | unsafe { cairo::XCBVisualType::from_raw_none(&mut visual as *mut _ as _) }; 199 | 200 | let surface = cairo::XCBSurface::create( 201 | &cairo_conn, 202 | &cairo::XCBDrawable(xcb_window_id), 203 | &cairo_visual, 204 | width.into(), 205 | height.into(), 206 | ) 207 | .context("Couldn't create Cairo Surface")?; 208 | let cairo_context = 209 | cairo::Context::new(&surface).context("Couldn't create Cairo Context")?; 210 | 211 | let render_window = RenderWindow { 212 | desktop_window, 213 | cairo_context, 214 | draw_pos, 215 | rect: (x.into(), y.into(), width.into(), height.into()), 216 | }; 217 | 218 | render_windows.insert(hint, render_window); 219 | } 220 | 221 | // Receive keyboard events. 222 | utils::snatch_keyboard(&conn, screen, Duration::from_secs(1))?; 223 | 224 | // Receive mouse events. 225 | utils::snatch_mouse(&conn, screen, Duration::from_secs(1))?; 226 | 227 | // Since we might have lots of windows on the desktop, it might be required 228 | // to enter a sequence in order to get to the correct window. 229 | // We'll have to track the keys pressed so far. 230 | let mut pressed_keys = String::default(); 231 | let mut sequence = utils::Sequence::new(None); 232 | 233 | let mut closed = false; 234 | while !closed { 235 | let event = conn.wait_for_event().context("No events")?; 236 | let event_option = Some(event); 237 | if let Some(e) = event_option { 238 | match e { 239 | Event::Expose(_) => { 240 | for (hint, rw) in &render_windows { 241 | utils::draw_hint_text(rw, &app_config, hint, &pressed_keys) 242 | .context("Couldn't draw hint text")?; 243 | conn.flush()?; 244 | } 245 | } 246 | Event::ButtonPress(_) => { 247 | closed = true; 248 | } 249 | Event::KeyRelease(_) => { 250 | let ksym = utils::get_pressed_symbol(&conn, e); 251 | let kstr = ksym 252 | .name() 253 | .context("Couldn't convert ksym to string")? 254 | .replace("XK_", ""); 255 | sequence.remove(&kstr); 256 | } 257 | Event::KeyPress(_) => { 258 | let ksym = utils::get_pressed_symbol(&conn, e); 259 | let kstr = ksym 260 | .name() 261 | .context("Couldn't convert ksym to string")? 262 | .replace("XK_", ""); 263 | 264 | sequence.push(kstr.to_owned()); 265 | 266 | if app_config.hint_chars.contains(&kstr) { 267 | info!("Adding '{}' to key sequence", kstr); 268 | pressed_keys.push_str(&kstr); 269 | } else { 270 | warn!("Pressed key '{}' is not a valid hint characters", kstr); 271 | } 272 | 273 | info!("Current key sequence: '{}'", pressed_keys); 274 | 275 | if ksym == xkeysym::key::Escape.into() 276 | || app_config.exit_keys.contains(&sequence) 277 | { 278 | info!("{:?} is exit sequence", sequence); 279 | closed = true; 280 | continue; 281 | } 282 | 283 | // Attempt to match the current sequence of keys as a string to the window 284 | // hints shown. 285 | // If there is an exact match, we're done. We'll then focus the window 286 | // and exit. However, we also want to check whether there is still any 287 | // chance to focus any windows from the current key sequence. If there 288 | // is not then we will also just exit and focus no new window. 289 | // If there still is a chance we might find a window then we'll just 290 | // keep going for now. 291 | if sequence.is_started() { 292 | utils::remove_last_key(&mut pressed_keys, &kstr); 293 | } else if let Some(rw) = &render_windows.get(&pressed_keys) { 294 | info!("Found matching window, focusing"); 295 | if app_config.print_only { 296 | println!("0x{:x}", rw.desktop_window.x_window_id.unwrap_or(0)); 297 | } else if app_config.swap { 298 | let Some(active_window) = 299 | desktop_windows.iter().find(|window| window.is_focused) 300 | else { 301 | warn!("There's no active window."); 302 | closed = true; 303 | continue; 304 | }; 305 | wm::swap_windows(active_window, rw.desktop_window) 306 | .context("Couldn't swap windows")?; 307 | } else { 308 | wm::focus_window(rw.desktop_window).context("Couldn't focus window")?; 309 | } 310 | closed = true; 311 | } else if !pressed_keys.is_empty() 312 | && render_windows.keys().any(|k| k.starts_with(&pressed_keys)) 313 | { 314 | for (hint, rw) in &render_windows { 315 | utils::draw_hint_text(rw, &app_config, hint, &pressed_keys) 316 | .context("Couldn't draw hint text")?; 317 | conn.flush()?; 318 | } 319 | continue; 320 | } else { 321 | warn!("No more matches possible with current key sequence"); 322 | closed = app_config.exit_keys.is_empty(); 323 | utils::remove_last_key(&mut pressed_keys, &kstr); 324 | } 325 | } 326 | _ => {} 327 | } 328 | } else { 329 | closed = true; 330 | } 331 | } 332 | 333 | Ok(()) 334 | } 335 | 336 | #[cfg(not(any(feature = "i3", feature = "add_some_other_wm_here")))] 337 | fn main() -> Result<()> { 338 | eprintln!( 339 | "You need to enable support for at least one window manager.\n 340 | Currently supported: 341 | --features i3" 342 | ); 343 | 344 | Ok(()) 345 | } 346 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::iter; 2 | use std::thread::sleep; 3 | use std::time::{Duration, Instant}; 4 | 5 | use anyhow::{bail, Context, Result}; 6 | use itertools::Itertools; 7 | use log::debug; 8 | use regex::Regex; 9 | use x11rb::connection::Connection; 10 | use x11rb::protocol::xproto::{ 11 | grab_keyboard, grab_pointer, ConnectionExt, EventMask, GrabMode, GrabStatus, Screen, Visualtype, 12 | }; 13 | use x11rb::protocol::Event; 14 | 15 | use crate::args::AppConfig; 16 | use crate::{DesktopWindow, RenderWindow}; 17 | 18 | /// Given a list of `current_hints` and a bunch of `hint_chars`, this finds a unique combination 19 | /// of characters that doesn't yet exist in `current_hints`. `max_count` is the maximum possible 20 | /// number of hints we need. 21 | pub fn get_next_hint( 22 | current_hints: Vec<&String>, 23 | hint_chars: &str, 24 | max_count: usize, 25 | ) -> Result { 26 | // Figure out which size we need. 27 | let mut size_required = 1; 28 | while hint_chars.len().pow(size_required) < max_count { 29 | size_required += 1; 30 | } 31 | let mut ret = hint_chars 32 | .chars() 33 | .next() 34 | .context("No hint_chars found")? 35 | .to_string(); 36 | let it = iter::repeat(hint_chars.chars().rev()) 37 | .take(size_required as usize) 38 | .multi_cartesian_product(); 39 | for c in it { 40 | let folded = c.into_iter().collect(); 41 | if !current_hints.contains(&&folded) { 42 | ret = folded; 43 | } 44 | } 45 | debug!("Returning next hint: {}", ret); 46 | Ok(ret) 47 | } 48 | 49 | /// A rust version of XCB's `xcb_visualtype_t` struct. This is used in a FFI-way. 50 | #[derive(Debug, Clone, Copy)] 51 | #[repr(C)] 52 | pub struct xcb_visualtype_t { 53 | pub visual_id: u32, 54 | pub class: u8, 55 | pub bits_per_rgb_value: u8, 56 | pub colormap_entries: u16, 57 | pub red_mask: u32, 58 | pub green_mask: u32, 59 | pub blue_mask: u32, 60 | pub pad0: [u8; 4], 61 | } 62 | 63 | impl From for xcb_visualtype_t { 64 | fn from(value: Visualtype) -> xcb_visualtype_t { 65 | xcb_visualtype_t { 66 | visual_id: value.visual_id, 67 | class: value.class.into(), 68 | bits_per_rgb_value: value.bits_per_rgb_value, 69 | colormap_entries: value.colormap_entries, 70 | red_mask: value.red_mask, 71 | green_mask: value.green_mask, 72 | blue_mask: value.blue_mask, 73 | pad0: [0; 4], 74 | } 75 | } 76 | } 77 | 78 | /// Find a `xcb_visualtype_t` based on its ID number 79 | pub fn find_xcb_visualtype(conn: &impl Connection, visual_id: u32) -> Option { 80 | for root in &conn.setup().roots { 81 | for depth in &root.allowed_depths { 82 | for visual in &depth.visuals { 83 | if visual.visual_id == visual_id { 84 | return Some((*visual).into()); 85 | } 86 | } 87 | } 88 | } 89 | None 90 | } 91 | 92 | pub fn extents_for_text(text: &str, family: &str, size: f64) -> Result { 93 | // Create a buffer image that should be large enough. 94 | // TODO: Figure out the maximum size from the largest window on the desktop. 95 | // For now we'll use made-up maximum values. 96 | let surface = cairo::ImageSurface::create(cairo::Format::ARgb32, 1024, 1024) 97 | .context("Couldn't create ImageSurface")?; 98 | let cr = cairo::Context::new(&surface).context("Couldn't create Cairo Surface")?; 99 | cr.select_font_face(family, cairo::FontSlant::Normal, cairo::FontWeight::Normal); 100 | cr.set_font_size(size); 101 | cr.text_extents(text).context("Couldn't create TextExtents") 102 | } 103 | 104 | /// Draw a `text` onto `rw`. In case any `current_hints` are already typed, it will draw those in a 105 | /// different color to show that they were in fact typed. 106 | pub fn draw_hint_text( 107 | rw: &RenderWindow, 108 | app_config: &AppConfig, 109 | text: &str, 110 | current_hints: &str, 111 | ) -> Result<()> { 112 | // Paint background. 113 | rw.cairo_context.set_operator(cairo::Operator::Source); 114 | 115 | if rw.desktop_window.is_focused { 116 | rw.cairo_context.set_source_rgb( 117 | app_config.bg_color_current.0, 118 | app_config.bg_color_current.1, 119 | app_config.bg_color_current.2, 120 | ); 121 | } else { 122 | rw.cairo_context.set_source_rgb( 123 | app_config.bg_color.0, 124 | app_config.bg_color.1, 125 | app_config.bg_color.2, 126 | ); 127 | } 128 | rw.cairo_context.paint().context("Error trying to draw")?; 129 | rw.cairo_context.set_operator(cairo::Operator::Over); 130 | 131 | rw.cairo_context.select_font_face( 132 | &app_config.font.font_family, 133 | cairo::FontSlant::Normal, 134 | cairo::FontWeight::Normal, 135 | ); 136 | rw.cairo_context.set_font_size(app_config.font.font_size); 137 | rw.cairo_context.move_to(rw.draw_pos.0, rw.draw_pos.1); 138 | if text.starts_with(current_hints) { 139 | // Paint already selected chars. 140 | if rw.desktop_window.is_focused { 141 | rw.cairo_context.set_source_rgba( 142 | app_config.text_color_current_alt.0, 143 | app_config.text_color_current_alt.1, 144 | app_config.text_color_current_alt.2, 145 | app_config.text_color_current_alt.3, 146 | ); 147 | } else { 148 | rw.cairo_context.set_source_rgba( 149 | app_config.text_color_alt.0, 150 | app_config.text_color_alt.1, 151 | app_config.text_color_alt.2, 152 | app_config.text_color_alt.3, 153 | ); 154 | } 155 | for c in current_hints.chars() { 156 | rw.cairo_context 157 | .show_text(&c.to_string()) 158 | .context("Couldn't display text")?; 159 | } 160 | } 161 | 162 | // Paint unselected chars. 163 | if rw.desktop_window.is_focused { 164 | rw.cairo_context.set_source_rgba( 165 | app_config.text_color_current.0, 166 | app_config.text_color_current.1, 167 | app_config.text_color_current.2, 168 | app_config.text_color_current.3, 169 | ); 170 | } else { 171 | rw.cairo_context.set_source_rgba( 172 | app_config.text_color.0, 173 | app_config.text_color.1, 174 | app_config.text_color.2, 175 | app_config.text_color.3, 176 | ); 177 | } 178 | let re = Regex::new(&format!("^{current_hints}")).unwrap(); 179 | for c in re.replace(text, "").chars() { 180 | rw.cairo_context 181 | .show_text(&c.to_string()) 182 | .context("Couldn't show text")?; 183 | } 184 | rw.cairo_context.target().flush(); 185 | 186 | Ok(()) 187 | } 188 | 189 | /// Try to grab the keyboard until `timeout` is reached. 190 | /// 191 | /// Generally with X, I found that you can't grab global keyboard input without it failing 192 | /// sometimes due to other clients grabbing it occasionally. Hence, we'll have to keep retrying 193 | /// until we eventually succeed. 194 | pub fn snatch_keyboard(conn: &impl Connection, screen: &Screen, timeout: Duration) -> Result<()> { 195 | let now = Instant::now(); 196 | loop { 197 | if now.elapsed() > timeout { 198 | bail!("Couldn't grab keyboard input within {:?}", now.elapsed()); 199 | } 200 | let grab_keyboard_cookie = grab_keyboard( 201 | conn, 202 | true, 203 | screen.root, 204 | x11rb::CURRENT_TIME, 205 | GrabMode::ASYNC, 206 | GrabMode::ASYNC, 207 | ); 208 | let grab_keyboard_reply = grab_keyboard_cookie? 209 | .reply() 210 | .context("Couldn't communicate with X")?; 211 | if grab_keyboard_reply.status == GrabStatus::SUCCESS { 212 | return Ok(()); 213 | } 214 | sleep(Duration::from_millis(1)); 215 | } 216 | } 217 | 218 | /// Try to grab the mouse until `timeout` is reached. 219 | /// 220 | /// Generally with X, I found that you can't grab global mouse input without it failing sometimes 221 | /// due to other clients grabbing it occasionally. Hence, we'll have to keep retrying until we 222 | /// eventually succeed. 223 | pub fn snatch_mouse(conn: &impl Connection, screen: &Screen, timeout: Duration) -> Result<()> { 224 | let now = Instant::now(); 225 | loop { 226 | if now.elapsed() > timeout { 227 | bail!("Couldn't grab keyboard input within {:?}", now.elapsed()); 228 | } 229 | let grab_pointer_cookie = grab_pointer( 230 | conn, 231 | true, 232 | screen.root, 233 | EventMask::BUTTON_PRESS, 234 | GrabMode::ASYNC, 235 | GrabMode::ASYNC, 236 | x11rb::NONE, 237 | x11rb::NONE, 238 | x11rb::CURRENT_TIME, 239 | ); 240 | let grab_pointer_reply = grab_pointer_cookie? 241 | .reply() 242 | .context("Couldn't communicate with X")?; 243 | if grab_pointer_reply.status == GrabStatus::SUCCESS { 244 | return Ok(()); 245 | } 246 | sleep(Duration::from_millis(1)); 247 | } 248 | } 249 | 250 | /// Sort list of `DesktopWindow`s by position. 251 | /// 252 | /// This sorts by column first and row second. 253 | pub fn sort_by_pos(mut dws: Vec) -> Vec { 254 | dws.sort_by_key(|w| w.pos.0); 255 | dws.sort_by_key(|w| w.pos.1); 256 | dws 257 | } 258 | 259 | /// Returns true if `r1` and `r2` overlap. 260 | fn intersects(r1: (i32, i32, i32, i32), r2: (i32, i32, i32, i32)) -> bool { 261 | let left_corner_inside = r1.0 < r2.0 + r2.2; 262 | let right_corner_inside = r1.0 + r1.2 > r2.0; 263 | let top_corner_inside = r1.1 < r2.1 + r2.3; 264 | let bottom_corner_inside = r1.1 + r1.3 > r2.1; 265 | left_corner_inside && right_corner_inside && top_corner_inside && bottom_corner_inside 266 | } 267 | 268 | /// Finds overlaps and returns a list of those rects in the format (x, y, w, h). 269 | pub fn find_overlaps( 270 | rws: Vec<&RenderWindow>, 271 | rect: (i32, i32, i32, i32), 272 | ) -> Vec<(i32, i32, i32, i32)> { 273 | let mut overlaps = vec![]; 274 | for rw in rws { 275 | if intersects(rw.rect, rect) { 276 | overlaps.push(rw.rect); 277 | } 278 | } 279 | overlaps 280 | } 281 | 282 | /// Remove last pressed key from pressed keys 283 | pub fn remove_last_key(pressed_keys: &mut String, kstr: &str) { 284 | if pressed_keys.contains(kstr) { 285 | pressed_keys.replace_range(pressed_keys.len() - kstr.len().., ""); 286 | } 287 | } 288 | 289 | pub fn get_pressed_symbol(conn: &impl Connection, event: Event) -> xkeysym::Keysym { 290 | let mapping = conn 291 | .get_keyboard_mapping( 292 | conn.setup().min_keycode, 293 | conn.setup().max_keycode - conn.setup().min_keycode + 1, 294 | ) 295 | .unwrap() 296 | .reply() 297 | .unwrap(); 298 | 299 | match event { 300 | Event::KeyPress(event) | Event::KeyRelease(event) => xkeysym::keysym( 301 | event.detail.into(), 302 | 0, 303 | conn.setup().min_keycode.into(), 304 | mapping.keysyms_per_keycode, 305 | mapping.keysyms.as_slice(), 306 | ) 307 | .unwrap(), 308 | _ => unreachable!(), 309 | } 310 | } 311 | 312 | /// Struct helps to write sequence and check if it is found in list of exit sequences 313 | #[derive(Debug, Clone, PartialEq, Eq)] 314 | pub struct Sequence { 315 | sequence: Vec, 316 | } 317 | 318 | impl Sequence { 319 | pub fn new(string: Option<&str>) -> Sequence { 320 | match string { 321 | Some(string) => { 322 | let mut vec: Vec = Sequence::explode(string, "+"); 323 | 324 | Sequence::sort(&mut vec); 325 | 326 | Sequence { sequence: vec } 327 | } 328 | None => Sequence { 329 | sequence: Vec::new(), 330 | }, 331 | } 332 | } 333 | 334 | fn explode(string: &str, separator: &str) -> Vec { 335 | string.split(separator).map(|s| s.to_string()).collect() 336 | } 337 | 338 | /// Sort vector alphabetically 339 | fn sort(vec: &mut [String]) { 340 | vec.sort_by_key(|a| a.to_lowercase()); 341 | } 342 | 343 | pub fn remove(&mut self, key: &str) { 344 | self.sequence.retain(|x| x != key); 345 | } 346 | 347 | pub fn push(&mut self, key: String) { 348 | self.sequence.push(key); 349 | Sequence::sort(&mut self.sequence); 350 | } 351 | 352 | /// Sequence is started if more than one key is pressed 353 | pub fn is_started(&self) -> bool { 354 | self.sequence.len() > 1 355 | } 356 | } 357 | 358 | #[cfg(test)] 359 | mod tests { 360 | use super::*; 361 | 362 | #[test] 363 | fn test_intersects() { 364 | assert!(intersects((1905, 705, 31, 82), (1905, 723, 38, 64))); 365 | } 366 | 367 | #[test] 368 | fn test_no_intersect() { 369 | assert!(!intersects((1905, 705, 31, 82), (2000, 723, 38, 64))); 370 | } 371 | 372 | #[test] 373 | fn test_sequences_equal() { 374 | let a = Sequence::new(Some("Control_L+Shift_L+a")); 375 | let b = Sequence::new(Some("Control_L+a+Shift_L")); 376 | 377 | assert_eq!(a, b); 378 | 379 | let mut c = Sequence::new(None); 380 | 381 | c.push("Shift_L".to_owned()); 382 | c.push("Control_L".to_owned()); 383 | c.push("a".to_owned()); 384 | 385 | assert_eq!(a, c); 386 | } 387 | 388 | #[test] 389 | fn test_sequences_not_equal() { 390 | let a = Sequence::new(Some("Control_L+Shift_L+a")); 391 | let b = Sequence::new(Some("Control_L+a")); 392 | 393 | assert_ne!(a, b); 394 | 395 | let mut c = Sequence::new(None); 396 | 397 | c.push("Shift_L".to_owned()); 398 | c.push("a".to_owned()); 399 | 400 | assert_ne!(a, c); 401 | } 402 | 403 | #[test] 404 | fn test_sequences_is_started() { 405 | let mut sequence = Sequence::new(None); 406 | assert!(!sequence.is_started()); 407 | 408 | sequence.push("Control_L".to_owned()); 409 | assert!(!sequence.is_started()); 410 | 411 | sequence.push("g".to_owned()); 412 | assert!(sequence.is_started()); 413 | 414 | sequence.remove("g"); 415 | 416 | assert!(!sequence.is_started()); 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.15" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.8" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.5" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" 49 | dependencies = [ 50 | "windows-sys 0.52.0", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.4" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys 0.52.0", 61 | ] 62 | 63 | [[package]] 64 | name = "anyhow" 65 | version = "1.0.89" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" 68 | 69 | [[package]] 70 | name = "as-raw-xcb-connection" 71 | version = "1.0.1" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" 74 | 75 | [[package]] 76 | name = "autocfg" 77 | version = "1.4.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 80 | 81 | [[package]] 82 | name = "bitflags" 83 | version = "1.3.2" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 86 | 87 | [[package]] 88 | name = "bitflags" 89 | version = "2.6.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 92 | 93 | [[package]] 94 | name = "byteorder" 95 | version = "1.5.0" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 98 | 99 | [[package]] 100 | name = "cairo-rs" 101 | version = "0.20.1" 102 | source = "registry+https://github.com/rust-lang/crates.io-index" 103 | checksum = "e8a0ea147c94108c9613235388f540e4d14c327f7081c9e471fc8ee8a2533e69" 104 | dependencies = [ 105 | "bitflags 2.6.0", 106 | "cairo-sys-rs", 107 | "glib", 108 | "libc", 109 | ] 110 | 111 | [[package]] 112 | name = "cairo-sys-rs" 113 | version = "0.20.0" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "428290f914b9b86089f60f5d8a9f6e440508e1bcff23b25afd51502b0a2da88f" 116 | dependencies = [ 117 | "glib-sys", 118 | "libc", 119 | "system-deps", 120 | ] 121 | 122 | [[package]] 123 | name = "cc" 124 | version = "1.1.24" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" 127 | dependencies = [ 128 | "shlex", 129 | ] 130 | 131 | [[package]] 132 | name = "cfg-expr" 133 | version = "0.17.0" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "d0890061c4d3223e7267f3bad2ec40b997d64faac1c2815a4a9d95018e2b9e9c" 136 | dependencies = [ 137 | "smallvec", 138 | "target-lexicon", 139 | ] 140 | 141 | [[package]] 142 | name = "clap" 143 | version = "4.5.19" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "7be5744db7978a28d9df86a214130d106a89ce49644cbc4e3f0c22c3fba30615" 146 | dependencies = [ 147 | "clap_builder", 148 | "clap_derive", 149 | ] 150 | 151 | [[package]] 152 | name = "clap_builder" 153 | version = "4.5.19" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | checksum = "a5fbc17d3ef8278f55b282b2a2e75ae6f6c7d4bb70ed3d0382375104bfafdb4b" 156 | dependencies = [ 157 | "anstream", 158 | "anstyle", 159 | "clap_lex", 160 | "strsim", 161 | "terminal_size", 162 | ] 163 | 164 | [[package]] 165 | name = "clap_derive" 166 | version = "4.5.18" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 169 | dependencies = [ 170 | "heck", 171 | "proc-macro2", 172 | "quote", 173 | "syn", 174 | ] 175 | 176 | [[package]] 177 | name = "clap_lex" 178 | version = "0.7.2" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" 181 | 182 | [[package]] 183 | name = "cmake" 184 | version = "0.1.51" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" 187 | dependencies = [ 188 | "cc", 189 | ] 190 | 191 | [[package]] 192 | name = "colorchoice" 193 | version = "1.0.2" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" 196 | 197 | [[package]] 198 | name = "core-foundation" 199 | version = "0.9.4" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 202 | dependencies = [ 203 | "core-foundation-sys", 204 | "libc", 205 | ] 206 | 207 | [[package]] 208 | name = "core-foundation-sys" 209 | version = "0.8.7" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 212 | 213 | [[package]] 214 | name = "core-graphics" 215 | version = "0.22.3" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" 218 | dependencies = [ 219 | "bitflags 1.3.2", 220 | "core-foundation", 221 | "core-graphics-types", 222 | "foreign-types", 223 | "libc", 224 | ] 225 | 226 | [[package]] 227 | name = "core-graphics-types" 228 | version = "0.1.3" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" 231 | dependencies = [ 232 | "bitflags 1.3.2", 233 | "core-foundation", 234 | "libc", 235 | ] 236 | 237 | [[package]] 238 | name = "core-text" 239 | version = "19.2.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" 242 | dependencies = [ 243 | "core-foundation", 244 | "core-graphics", 245 | "foreign-types", 246 | "libc", 247 | ] 248 | 249 | [[package]] 250 | name = "css-color-parser" 251 | version = "0.1.2" 252 | source = "registry+https://github.com/rust-lang/crates.io-index" 253 | checksum = "9ccb6ce7ef97e6dc6e575e51b596c9889a5cc88a307b5ef177d215c61fd7581d" 254 | dependencies = [ 255 | "lazy_static", 256 | ] 257 | 258 | [[package]] 259 | name = "either" 260 | version = "1.13.0" 261 | source = "registry+https://github.com/rust-lang/crates.io-index" 262 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 263 | 264 | [[package]] 265 | name = "env_logger" 266 | version = "0.10.2" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 269 | dependencies = [ 270 | "humantime", 271 | "is-terminal", 272 | "log", 273 | "regex", 274 | "termcolor", 275 | ] 276 | 277 | [[package]] 278 | name = "equivalent" 279 | version = "1.0.1" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 282 | 283 | [[package]] 284 | name = "errno" 285 | version = "0.3.9" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 288 | dependencies = [ 289 | "libc", 290 | "windows-sys 0.52.0", 291 | ] 292 | 293 | [[package]] 294 | name = "expat-sys" 295 | version = "2.1.6" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" 298 | dependencies = [ 299 | "cmake", 300 | "pkg-config", 301 | ] 302 | 303 | [[package]] 304 | name = "font-loader" 305 | version = "0.11.0" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "c49d6b4c11dca1a1dd931a34a9f397e2da91abe3de4110505f3530a80e560b52" 308 | dependencies = [ 309 | "core-foundation", 310 | "core-text", 311 | "libc", 312 | "servo-fontconfig", 313 | "winapi", 314 | ] 315 | 316 | [[package]] 317 | name = "foreign-types" 318 | version = "0.3.2" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 321 | dependencies = [ 322 | "foreign-types-shared", 323 | ] 324 | 325 | [[package]] 326 | name = "foreign-types-shared" 327 | version = "0.1.1" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 330 | 331 | [[package]] 332 | name = "freetype-sys" 333 | version = "0.13.1" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" 336 | dependencies = [ 337 | "cmake", 338 | "libc", 339 | "pkg-config", 340 | ] 341 | 342 | [[package]] 343 | name = "futures-channel" 344 | version = "0.3.30" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 347 | dependencies = [ 348 | "futures-core", 349 | ] 350 | 351 | [[package]] 352 | name = "futures-core" 353 | version = "0.3.30" 354 | source = "registry+https://github.com/rust-lang/crates.io-index" 355 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 356 | 357 | [[package]] 358 | name = "futures-executor" 359 | version = "0.3.30" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" 362 | dependencies = [ 363 | "futures-core", 364 | "futures-task", 365 | "futures-util", 366 | ] 367 | 368 | [[package]] 369 | name = "futures-macro" 370 | version = "0.3.30" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" 373 | dependencies = [ 374 | "proc-macro2", 375 | "quote", 376 | "syn", 377 | ] 378 | 379 | [[package]] 380 | name = "futures-task" 381 | version = "0.3.30" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 384 | 385 | [[package]] 386 | name = "futures-util" 387 | version = "0.3.30" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 390 | dependencies = [ 391 | "futures-core", 392 | "futures-macro", 393 | "futures-task", 394 | "pin-project-lite", 395 | "pin-utils", 396 | "slab", 397 | ] 398 | 399 | [[package]] 400 | name = "gethostname" 401 | version = "0.4.3" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" 404 | dependencies = [ 405 | "libc", 406 | "windows-targets 0.48.5", 407 | ] 408 | 409 | [[package]] 410 | name = "gio-sys" 411 | version = "0.20.4" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "4f7efc368de04755344f0084104835b6bb71df2c1d41e37d863947392a894779" 414 | dependencies = [ 415 | "glib-sys", 416 | "gobject-sys", 417 | "libc", 418 | "system-deps", 419 | "windows-sys 0.52.0", 420 | ] 421 | 422 | [[package]] 423 | name = "glib" 424 | version = "0.20.4" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "adcf1ec6d3650bf9fdbc6cee242d4fcebc6f6bfd9bea5b929b6a8b7344eb85ff" 427 | dependencies = [ 428 | "bitflags 2.6.0", 429 | "futures-channel", 430 | "futures-core", 431 | "futures-executor", 432 | "futures-task", 433 | "futures-util", 434 | "gio-sys", 435 | "glib-macros", 436 | "glib-sys", 437 | "gobject-sys", 438 | "libc", 439 | "memchr", 440 | "smallvec", 441 | ] 442 | 443 | [[package]] 444 | name = "glib-macros" 445 | version = "0.20.4" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "a6bf88f70cd5720a6197639dcabcb378dd528d0cb68cb1f45e3b358bcb841cd7" 448 | dependencies = [ 449 | "heck", 450 | "proc-macro-crate", 451 | "proc-macro2", 452 | "quote", 453 | "syn", 454 | ] 455 | 456 | [[package]] 457 | name = "glib-sys" 458 | version = "0.20.4" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "5f9eca5d88cfa6a453b00d203287c34a2b7cac3a7831779aa2bb0b3c7233752b" 461 | dependencies = [ 462 | "libc", 463 | "system-deps", 464 | ] 465 | 466 | [[package]] 467 | name = "gobject-sys" 468 | version = "0.20.4" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "a4c674d2ff8478cf0ec29d2be730ed779fef54415a2fb4b565c52def62696462" 471 | dependencies = [ 472 | "glib-sys", 473 | "libc", 474 | "system-deps", 475 | ] 476 | 477 | [[package]] 478 | name = "hashbrown" 479 | version = "0.15.0" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" 482 | 483 | [[package]] 484 | name = "heck" 485 | version = "0.5.0" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 488 | 489 | [[package]] 490 | name = "hermit-abi" 491 | version = "0.4.0" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 494 | 495 | [[package]] 496 | name = "humantime" 497 | version = "2.1.0" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 500 | 501 | [[package]] 502 | name = "i3ipc" 503 | version = "0.10.1" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "63f3dac00c473fae88cb3114f35312204469a32ffb20874264a5214d6c8c927e" 506 | dependencies = [ 507 | "byteorder", 508 | "log", 509 | "serde", 510 | "serde_json", 511 | ] 512 | 513 | [[package]] 514 | name = "indexmap" 515 | version = "2.6.0" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" 518 | dependencies = [ 519 | "equivalent", 520 | "hashbrown", 521 | ] 522 | 523 | [[package]] 524 | name = "is-terminal" 525 | version = "0.4.13" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" 528 | dependencies = [ 529 | "hermit-abi", 530 | "libc", 531 | "windows-sys 0.52.0", 532 | ] 533 | 534 | [[package]] 535 | name = "is_terminal_polyfill" 536 | version = "1.70.1" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 539 | 540 | [[package]] 541 | name = "itertools" 542 | version = "0.13.0" 543 | source = "registry+https://github.com/rust-lang/crates.io-index" 544 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 545 | dependencies = [ 546 | "either", 547 | ] 548 | 549 | [[package]] 550 | name = "itoa" 551 | version = "1.0.11" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 554 | 555 | [[package]] 556 | name = "lazy_static" 557 | version = "0.1.16" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "cf186d1a8aa5f5bee5fd662bc9c1b949e0259e1bcc379d1f006847b0080c7417" 560 | 561 | [[package]] 562 | name = "libc" 563 | version = "0.2.159" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" 566 | 567 | [[package]] 568 | name = "linux-raw-sys" 569 | version = "0.4.14" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 572 | 573 | [[package]] 574 | name = "log" 575 | version = "0.4.22" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 578 | 579 | [[package]] 580 | name = "memchr" 581 | version = "2.7.4" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 584 | 585 | [[package]] 586 | name = "pin-project-lite" 587 | version = "0.2.14" 588 | source = "registry+https://github.com/rust-lang/crates.io-index" 589 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 590 | 591 | [[package]] 592 | name = "pin-utils" 593 | version = "0.1.0" 594 | source = "registry+https://github.com/rust-lang/crates.io-index" 595 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 596 | 597 | [[package]] 598 | name = "pkg-config" 599 | version = "0.3.31" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" 602 | 603 | [[package]] 604 | name = "pretty_env_logger" 605 | version = "0.5.0" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" 608 | dependencies = [ 609 | "env_logger", 610 | "log", 611 | ] 612 | 613 | [[package]] 614 | name = "proc-macro-crate" 615 | version = "3.2.0" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" 618 | dependencies = [ 619 | "toml_edit", 620 | ] 621 | 622 | [[package]] 623 | name = "proc-macro2" 624 | version = "1.0.86" 625 | source = "registry+https://github.com/rust-lang/crates.io-index" 626 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" 627 | dependencies = [ 628 | "unicode-ident", 629 | ] 630 | 631 | [[package]] 632 | name = "quote" 633 | version = "1.0.37" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 636 | dependencies = [ 637 | "proc-macro2", 638 | ] 639 | 640 | [[package]] 641 | name = "regex" 642 | version = "1.11.0" 643 | source = "registry+https://github.com/rust-lang/crates.io-index" 644 | checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" 645 | dependencies = [ 646 | "aho-corasick", 647 | "memchr", 648 | "regex-automata", 649 | "regex-syntax", 650 | ] 651 | 652 | [[package]] 653 | name = "regex-automata" 654 | version = "0.4.8" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" 657 | dependencies = [ 658 | "aho-corasick", 659 | "memchr", 660 | "regex-syntax", 661 | ] 662 | 663 | [[package]] 664 | name = "regex-syntax" 665 | version = "0.8.5" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 668 | 669 | [[package]] 670 | name = "rustix" 671 | version = "0.38.37" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 674 | dependencies = [ 675 | "bitflags 2.6.0", 676 | "errno", 677 | "libc", 678 | "linux-raw-sys", 679 | "windows-sys 0.52.0", 680 | ] 681 | 682 | [[package]] 683 | name = "ryu" 684 | version = "1.0.18" 685 | source = "registry+https://github.com/rust-lang/crates.io-index" 686 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 687 | 688 | [[package]] 689 | name = "serde" 690 | version = "1.0.210" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 693 | dependencies = [ 694 | "serde_derive", 695 | ] 696 | 697 | [[package]] 698 | name = "serde_derive" 699 | version = "1.0.210" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 702 | dependencies = [ 703 | "proc-macro2", 704 | "quote", 705 | "syn", 706 | ] 707 | 708 | [[package]] 709 | name = "serde_json" 710 | version = "1.0.128" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 713 | dependencies = [ 714 | "itoa", 715 | "memchr", 716 | "ryu", 717 | "serde", 718 | ] 719 | 720 | [[package]] 721 | name = "serde_spanned" 722 | version = "0.6.8" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" 725 | dependencies = [ 726 | "serde", 727 | ] 728 | 729 | [[package]] 730 | name = "servo-fontconfig" 731 | version = "0.5.1" 732 | source = "registry+https://github.com/rust-lang/crates.io-index" 733 | checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" 734 | dependencies = [ 735 | "libc", 736 | "servo-fontconfig-sys", 737 | ] 738 | 739 | [[package]] 740 | name = "servo-fontconfig-sys" 741 | version = "5.1.0" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" 744 | dependencies = [ 745 | "expat-sys", 746 | "freetype-sys", 747 | "pkg-config", 748 | ] 749 | 750 | [[package]] 751 | name = "shlex" 752 | version = "1.3.0" 753 | source = "registry+https://github.com/rust-lang/crates.io-index" 754 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 755 | 756 | [[package]] 757 | name = "slab" 758 | version = "0.4.9" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 761 | dependencies = [ 762 | "autocfg", 763 | ] 764 | 765 | [[package]] 766 | name = "smallvec" 767 | version = "1.13.2" 768 | source = "registry+https://github.com/rust-lang/crates.io-index" 769 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 770 | 771 | [[package]] 772 | name = "strsim" 773 | version = "0.11.1" 774 | source = "registry+https://github.com/rust-lang/crates.io-index" 775 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 776 | 777 | [[package]] 778 | name = "syn" 779 | version = "2.0.79" 780 | source = "registry+https://github.com/rust-lang/crates.io-index" 781 | checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" 782 | dependencies = [ 783 | "proc-macro2", 784 | "quote", 785 | "unicode-ident", 786 | ] 787 | 788 | [[package]] 789 | name = "system-deps" 790 | version = "7.0.3" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "66d23aaf9f331227789a99e8de4c91bf46703add012bdfd45fdecdfb2975a005" 793 | dependencies = [ 794 | "cfg-expr", 795 | "heck", 796 | "pkg-config", 797 | "toml", 798 | "version-compare", 799 | ] 800 | 801 | [[package]] 802 | name = "target-lexicon" 803 | version = "0.12.16" 804 | source = "registry+https://github.com/rust-lang/crates.io-index" 805 | checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" 806 | 807 | [[package]] 808 | name = "termcolor" 809 | version = "1.4.1" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 812 | dependencies = [ 813 | "winapi-util", 814 | ] 815 | 816 | [[package]] 817 | name = "terminal_size" 818 | version = "0.4.0" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" 821 | dependencies = [ 822 | "rustix", 823 | "windows-sys 0.59.0", 824 | ] 825 | 826 | [[package]] 827 | name = "toml" 828 | version = "0.8.19" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" 831 | dependencies = [ 832 | "serde", 833 | "serde_spanned", 834 | "toml_datetime", 835 | "toml_edit", 836 | ] 837 | 838 | [[package]] 839 | name = "toml_datetime" 840 | version = "0.6.8" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" 843 | dependencies = [ 844 | "serde", 845 | ] 846 | 847 | [[package]] 848 | name = "toml_edit" 849 | version = "0.22.22" 850 | source = "registry+https://github.com/rust-lang/crates.io-index" 851 | checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" 852 | dependencies = [ 853 | "indexmap", 854 | "serde", 855 | "serde_spanned", 856 | "toml_datetime", 857 | "winnow", 858 | ] 859 | 860 | [[package]] 861 | name = "unicode-ident" 862 | version = "1.0.13" 863 | source = "registry+https://github.com/rust-lang/crates.io-index" 864 | checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" 865 | 866 | [[package]] 867 | name = "utf8parse" 868 | version = "0.2.2" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 871 | 872 | [[package]] 873 | name = "version-compare" 874 | version = "0.2.0" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" 877 | 878 | [[package]] 879 | name = "winapi" 880 | version = "0.3.9" 881 | source = "registry+https://github.com/rust-lang/crates.io-index" 882 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 883 | dependencies = [ 884 | "winapi-i686-pc-windows-gnu", 885 | "winapi-x86_64-pc-windows-gnu", 886 | ] 887 | 888 | [[package]] 889 | name = "winapi-i686-pc-windows-gnu" 890 | version = "0.4.0" 891 | source = "registry+https://github.com/rust-lang/crates.io-index" 892 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 893 | 894 | [[package]] 895 | name = "winapi-util" 896 | version = "0.1.9" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 899 | dependencies = [ 900 | "windows-sys 0.59.0", 901 | ] 902 | 903 | [[package]] 904 | name = "winapi-x86_64-pc-windows-gnu" 905 | version = "0.4.0" 906 | source = "registry+https://github.com/rust-lang/crates.io-index" 907 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 908 | 909 | [[package]] 910 | name = "windows-sys" 911 | version = "0.52.0" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 914 | dependencies = [ 915 | "windows-targets 0.52.6", 916 | ] 917 | 918 | [[package]] 919 | name = "windows-sys" 920 | version = "0.59.0" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 923 | dependencies = [ 924 | "windows-targets 0.52.6", 925 | ] 926 | 927 | [[package]] 928 | name = "windows-targets" 929 | version = "0.48.5" 930 | source = "registry+https://github.com/rust-lang/crates.io-index" 931 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 932 | dependencies = [ 933 | "windows_aarch64_gnullvm 0.48.5", 934 | "windows_aarch64_msvc 0.48.5", 935 | "windows_i686_gnu 0.48.5", 936 | "windows_i686_msvc 0.48.5", 937 | "windows_x86_64_gnu 0.48.5", 938 | "windows_x86_64_gnullvm 0.48.5", 939 | "windows_x86_64_msvc 0.48.5", 940 | ] 941 | 942 | [[package]] 943 | name = "windows-targets" 944 | version = "0.52.6" 945 | source = "registry+https://github.com/rust-lang/crates.io-index" 946 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 947 | dependencies = [ 948 | "windows_aarch64_gnullvm 0.52.6", 949 | "windows_aarch64_msvc 0.52.6", 950 | "windows_i686_gnu 0.52.6", 951 | "windows_i686_gnullvm", 952 | "windows_i686_msvc 0.52.6", 953 | "windows_x86_64_gnu 0.52.6", 954 | "windows_x86_64_gnullvm 0.52.6", 955 | "windows_x86_64_msvc 0.52.6", 956 | ] 957 | 958 | [[package]] 959 | name = "windows_aarch64_gnullvm" 960 | version = "0.48.5" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 963 | 964 | [[package]] 965 | name = "windows_aarch64_gnullvm" 966 | version = "0.52.6" 967 | source = "registry+https://github.com/rust-lang/crates.io-index" 968 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 969 | 970 | [[package]] 971 | name = "windows_aarch64_msvc" 972 | version = "0.48.5" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 975 | 976 | [[package]] 977 | name = "windows_aarch64_msvc" 978 | version = "0.52.6" 979 | source = "registry+https://github.com/rust-lang/crates.io-index" 980 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 981 | 982 | [[package]] 983 | name = "windows_i686_gnu" 984 | version = "0.48.5" 985 | source = "registry+https://github.com/rust-lang/crates.io-index" 986 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 987 | 988 | [[package]] 989 | name = "windows_i686_gnu" 990 | version = "0.52.6" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 993 | 994 | [[package]] 995 | name = "windows_i686_gnullvm" 996 | version = "0.52.6" 997 | source = "registry+https://github.com/rust-lang/crates.io-index" 998 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 999 | 1000 | [[package]] 1001 | name = "windows_i686_msvc" 1002 | version = "0.48.5" 1003 | source = "registry+https://github.com/rust-lang/crates.io-index" 1004 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1005 | 1006 | [[package]] 1007 | name = "windows_i686_msvc" 1008 | version = "0.52.6" 1009 | source = "registry+https://github.com/rust-lang/crates.io-index" 1010 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1011 | 1012 | [[package]] 1013 | name = "windows_x86_64_gnu" 1014 | version = "0.48.5" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1017 | 1018 | [[package]] 1019 | name = "windows_x86_64_gnu" 1020 | version = "0.52.6" 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" 1022 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1023 | 1024 | [[package]] 1025 | name = "windows_x86_64_gnullvm" 1026 | version = "0.48.5" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1029 | 1030 | [[package]] 1031 | name = "windows_x86_64_gnullvm" 1032 | version = "0.52.6" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1035 | 1036 | [[package]] 1037 | name = "windows_x86_64_msvc" 1038 | version = "0.48.5" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1041 | 1042 | [[package]] 1043 | name = "windows_x86_64_msvc" 1044 | version = "0.52.6" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1047 | 1048 | [[package]] 1049 | name = "winnow" 1050 | version = "0.6.20" 1051 | source = "registry+https://github.com/rust-lang/crates.io-index" 1052 | checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" 1053 | dependencies = [ 1054 | "memchr", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "wmfocus" 1059 | version = "1.5.0" 1060 | dependencies = [ 1061 | "anyhow", 1062 | "cairo-rs", 1063 | "clap", 1064 | "css-color-parser", 1065 | "font-loader", 1066 | "i3ipc", 1067 | "itertools", 1068 | "log", 1069 | "pretty_env_logger", 1070 | "regex", 1071 | "x11rb", 1072 | "xkeysym", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "x11rb" 1077 | version = "0.13.1" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "5d91ffca73ee7f68ce055750bf9f6eca0780b8c85eff9bc046a3b0da41755e12" 1080 | dependencies = [ 1081 | "as-raw-xcb-connection", 1082 | "gethostname", 1083 | "libc", 1084 | "rustix", 1085 | "x11rb-protocol", 1086 | ] 1087 | 1088 | [[package]] 1089 | name = "x11rb-protocol" 1090 | version = "0.13.1" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" 1093 | 1094 | [[package]] 1095 | name = "xkeysym" 1096 | version = "0.2.1" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" 1099 | --------------------------------------------------------------------------------