├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── contrib └── layout ├── doc └── kile.1.scd ├── protocol └── river-layout-v3.xml └── src ├── client.rs ├── layout.rs ├── lexer ├── lexer.rs └── mod.rs ├── main.rs └── wayland └── mod.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | /src/wayland/ 8 | /doc/kile.1.gz 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "bitflags" 5 | version = "1.2.1" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 8 | 9 | [[package]] 10 | name = "cc" 11 | version = "1.0.67" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" 14 | 15 | [[package]] 16 | name = "cfg-if" 17 | version = "1.0.0" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 20 | 21 | [[package]] 22 | name = "downcast-rs" 23 | version = "1.2.0" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" 26 | 27 | [[package]] 28 | name = "kile" 29 | version = "0.1.0" 30 | dependencies = [ 31 | "wayland-client", 32 | "wayland-commons", 33 | "wayland-scanner", 34 | ] 35 | 36 | [[package]] 37 | name = "libc" 38 | version = "0.2.88" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a" 41 | 42 | [[package]] 43 | name = "nix" 44 | version = "0.20.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a" 47 | dependencies = [ 48 | "bitflags", 49 | "cc", 50 | "cfg-if", 51 | "libc", 52 | ] 53 | 54 | [[package]] 55 | name = "once_cell" 56 | version = "1.7.2" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" 59 | 60 | [[package]] 61 | name = "pkg-config" 62 | version = "0.3.19" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c" 65 | 66 | [[package]] 67 | name = "proc-macro2" 68 | version = "1.0.24" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" 71 | dependencies = [ 72 | "unicode-xid", 73 | ] 74 | 75 | [[package]] 76 | name = "quote" 77 | version = "1.0.9" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 80 | dependencies = [ 81 | "proc-macro2", 82 | ] 83 | 84 | [[package]] 85 | name = "smallvec" 86 | version = "1.6.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" 89 | 90 | [[package]] 91 | name = "unicode-xid" 92 | version = "0.2.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 95 | 96 | [[package]] 97 | name = "wayland-client" 98 | version = "0.28.5" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "06ca44d86554b85cf449f1557edc6cc7da935cc748c8e4bf1c507cbd43bae02c" 101 | dependencies = [ 102 | "bitflags", 103 | "downcast-rs", 104 | "libc", 105 | "nix", 106 | "wayland-commons", 107 | "wayland-scanner", 108 | "wayland-sys", 109 | ] 110 | 111 | [[package]] 112 | name = "wayland-commons" 113 | version = "0.28.5" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "8bd75ae380325dbcff2707f0cd9869827ea1d2d6d534cff076858d3f0460fd5a" 116 | dependencies = [ 117 | "nix", 118 | "once_cell", 119 | "smallvec", 120 | "wayland-sys", 121 | ] 122 | 123 | [[package]] 124 | name = "wayland-scanner" 125 | version = "0.28.5" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "389d680d7bd67512dc9c37f39560224327038deb0f0e8d33f870900441b68720" 128 | dependencies = [ 129 | "proc-macro2", 130 | "quote", 131 | "xml-rs", 132 | ] 133 | 134 | [[package]] 135 | name = "wayland-sys" 136 | version = "0.28.5" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "2907bd297eef464a95ba9349ea771611771aa285b932526c633dc94d5400a8e2" 139 | dependencies = [ 140 | "pkg-config", 141 | ] 142 | 143 | [[package]] 144 | name = "xml-rs" 145 | version = "0.8.3" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" 148 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kile" 3 | version = "0.1.0" 4 | authors = ["bryan "] 5 | edition = "2018" 6 | build = "build.rs" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [build-dependencies] 11 | wayland-scanner = "0.28.5" 12 | 13 | [dependencies] 14 | wayland-commons = "0.28.5" 15 | wayland-client = "0.28.5" 16 | 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Bryan 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # A layout generator for [river](https://github.com/ifreund/river) 2 | 3 | kile is a layout client for river. 4 | 5 | Through a lisp like syntax, users can define new dynamic layouts from existing ones. 6 | 7 | Layout examples are provided in the [contrib](https://gitlab.com/snakedye/kile/-/blob/main/contrib/layout) file. 8 | The documentation is in the man page. 9 | 10 | ### Dependencies 11 | - rust 12 | - scdoc (optional) 13 | 14 | ## Building 15 | 16 | [![Packaging status](https://repology.org/badge/vertical-allrepos/kile-wl.svg)](https://repology.org/project/kile-wl/versions) 17 | ```shell 18 | git clone https://gitlab.com/snakedye/kile.git 19 | cd kile 20 | cargo build --release 21 | ``` 22 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate wayland_scanner; 2 | 3 | use std::fs::{File, OpenOptions}; 4 | use std::path::Path; 5 | use std::process::{Command, Stdio}; 6 | use wayland_scanner::{generate_code, Side}; 7 | 8 | pub fn main() { 9 | generate("river_layout_v3"); 10 | match Command::new("scdoc").spawn() { 11 | Ok(_) => { 12 | let input = File::open(Path::new("./doc/kile.1.scd")).unwrap(); 13 | let output = OpenOptions::new() 14 | .write(true) 15 | .create(true) 16 | .open(Path::new("./doc/kile.1.gz")) 17 | .unwrap(); 18 | Command::new("scdoc") 19 | .stdin(Stdio::from(input)) 20 | .stdout(output) 21 | .spawn() 22 | .expect("Failed to execute command"); 23 | } 24 | Err(_) => {} 25 | } 26 | } 27 | 28 | fn generate(protocol_name: &str) { 29 | let out_dir = Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/src/wayland/")); 30 | 31 | let mut protocol_dir = String::from(concat!(env!("CARGO_MANIFEST_DIR"), "/protocol/")); 32 | protocol_dir.push_str(protocol_name); 33 | protocol_dir.push_str(".xml"); 34 | protocol_dir = protocol_dir.replace("_", "-"); 35 | 36 | let protocol = Path::new(&protocol_dir); 37 | let mut protocol_file = protocol_name.to_string(); 38 | protocol_file.push_str(".rs"); 39 | 40 | generate_code(protocol, out_dir.join(protocol_file), Side::Client); 41 | } 42 | -------------------------------------------------------------------------------- /contrib/layout: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # These are fairly simple layout that serves as 4 | # both example of what can be done and also bulding blocks for your own layouts 5 | ROW="((h: h d) 2 0.65 0)" 6 | 7 | JUMP="(2 > 8 | $ROW 9 | ? (h 1 0.65 0) 10 | )" 11 | 12 | HOR="((h: 13 | v ( 14 | (v: f d) 1 0.5 0 15 | ) 16 | ) 1 0.63 0)" 17 | 18 | # If you wish to add more layout, please follow the indenting style. 19 | # Since the syntax isn't really a lisp, I took inspiration from infix-lsp 20 | # https://github.com/elkowar/infix-lisp 21 | # Feel free to add more below :) 22 | -------------------------------------------------------------------------------- /doc/kile.1.scd: -------------------------------------------------------------------------------- 1 | KILE(1) "https://gitlab.com/snakedye/kile" "General Commands Manual" 2 | 3 | # NAME 4 | 5 | kile - a tiling a layout generator for river 6 | 7 | # DESCRIPTION 8 | 9 | *kile* is a layout generator for *river*. 10 | 11 | Through a lisp like syntax, users can define new dynamic layouts from existing ones. 12 | 13 | Note: *kile* could work on any Wayland compositor 14 | that implements *river-layout-v3*. 15 | 16 | 17 | # ARGUMENTS 18 | 19 | *-h*, *--h*, *--help* 20 | Display a help message. 21 | 22 | *-n*, *--n*, *--namespace* 23 | The namespace of this layout generator. 24 | 25 | # CONFIGURATION 26 | 27 | On launch, the default layout is *Full*. 28 | 29 | To configure kile you need to modify or set theses layout values using *riverctl*. 30 | 31 | ## COMMANDS 32 | 33 | *(default_)(mod_)main_amount* _(int)_ 34 | An arbitrary positive integer indicating the amount of main views. 35 | 36 | *(default_)(mod_)main_ratio* _(float)_ 37 | A floating point numger indicating the relative size of the area reserved for main views. 38 | Note that layouts commonly expect values between 0.1 and 0.9. 39 | 40 | *(default_)(mod_)main_index* _(int)_ 41 | An arbitrary positive integer indicating the index of the main area in a layout. 42 | 43 | *(mod_)view_padding* _(int)_ 44 | The padding in pixels of the each view within the layout. 45 | 46 | *(mod_)outer_padding* _(int)_ 47 | The padding in pixels between the layout and the edges of the output. 48 | 49 | *xoffset* _(int)_ 50 | The horizontal offset in pixels from a lateral screen edge. 51 | Positive integers create an offset from 52 | the right of screen and negatives from the left. 53 | 54 | *yoffset* _(int)_ 55 | The vertical offset in pixels from the top or bottom screen edge. 56 | Positive integers create an offset from 57 | the top of screen and negatives from the bottom. 58 | 59 | *dimension* _(uint)_ _(uint)_ _(uint)_ _(uint)_ 60 | A custom dimension of the output. 61 | 62 | *resize* _(bool)_ 63 | Declare if the output's geometry relative to kile can or cannot be resized. 64 | 65 | *smart_padding* _(bool)_ 66 | Enables or disables smart padding. 67 | 68 | *order* _ascend/descend_ 69 | The order in which the tags are sorted. If it's *descend*ing, the highest tag will have priority when multiple are focused and vice versa. 70 | 71 | *default*, *focused*, *all* or *1..32* _(string)_ _(layout)_ 72 | The configuration of a tag. _0..32_ means all values between 1 and 32 inclusively. 73 | *focused* will set the _layout_ of the focused tag, *all*, all tags and for numbers 74 | between *1 and 32*. 75 | 76 | _string_ the name of the layout, seperated from the layout by a new line. This will be useful once river-toplevel-v1 is implementated. 77 | _layout_ the layout definition of the corresponding tag. See *LAYOUTS*. 78 | 79 | *clear* _(tag)_ 80 | Clear the configuration of the given tag(s) 81 | 82 | Possible values *all*, *default*, *focused* and an _uint_ between *1 and 32* inclusively. 83 | 84 | # LAYOUTS 85 | 86 | kile only ships with the following layouts. 87 | 88 | - "f" | "ful" | "full" 89 | 90 | - "d" | "dec" | "deck" 91 | 92 | - "v" | "ver" | "vertical" 93 | 94 | - "h" | "hor" | "horizontal" 95 | 96 | Instead of manually implementating all variations of each dynamic layout involving splits or stacking, 97 | kile let's the user declare his own layout from existing ones through an s-expression like syntax. 98 | 99 | Examples are provided in the layout file in contrib/. 100 | 101 | ## RECURSIVE 102 | 103 | This layout is the core of every nested layout. It essentially let's you define a layout of layouts 104 | or in other words the disposition of layouts within one. The _:_ symbol is the delimiter between a layout and its _sublayouts_. 105 | 106 | The layout generated follow these rules. 107 | 108 | - views at the top of the stack will be displayed in the main area. 109 | - views are spread evenly accross all other areas ie those that aren't main. 110 | - the areas generated by layout on the left side of _:_ are for the _sublayouts_ not views. 111 | - the area generated at _main_index_ will have _main_amount_ of views/areas unless _main_amount_ is 0. 112 | - this layout will *always* create as much areas as necessary to inhabit the amount of views assigned to its area. 113 | 114 | 115 | format: *( layout : layout layout ... )* 116 | 117 | *examples:* 118 | - (v: h h) 119 | - (d: h (v: full dec)) 120 | - ((ver: h h) : ver ful) 121 | 122 | ## PARAMETERS 123 | 124 | This layout provides custom layout parameters to a specific layout. If a parameter isn't specified, the layout will inherit the one from his parent. 125 | 126 | When this layout is not nested in another, it also serves to set the layout parameters of the tag. 127 | Note that not all parameters need to be defined, if you want to escape one, just insert an invalid character. 128 | 129 | format: *( layout main_amount main_ratio main_index )* 130 | 131 | 132 | *examples:* 133 | - (vertical 2) 134 | - ((v: h h) 1 0.6 0) 135 | 136 | ## CONDITIONAL 137 | 138 | Let's you determine which layout to generate in an area depending on the value of certain parameters. 139 | 140 | 141 | The accepted comparison symbols are 142 | - **>** (greater than) 143 | - **=** (equal to) 144 | - **<** (lower than) 145 | 146 | 147 | Symbols associated to parameters 148 | - **?** : the amount of views/areas in that area 149 | - **!** : the index of the main area in that layout 150 | - **%** : the ratio of the main area relative others 151 | 152 | 153 | format: *( view_amount *_<|>|=_* layout *_!|?|%_* layout )* 154 | 155 | The layout between the _operator_ and the _parameter_ symbol will be generated if the condition is true. 156 | In the case it's false the layout on the right side of the _parameter_ symbol will be generated. 157 | 158 | 159 | *examples* 160 | - (2 > h ? v) 161 | - (0.6 > (v: h h) % f) 162 | 163 | 164 | # SEE ALSO 165 | 166 | *riverctl*(1), *rivertile*(7) 167 | -------------------------------------------------------------------------------- /protocol/river-layout-v3.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright 2020-2021 The River Developers 5 | 6 | Permission to use, copy, modify, and/or distribute this software for any 7 | purpose with or without fee is hereby granted, provided that the above 8 | copyright notice and this permission notice appear in all copies. 9 | 10 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 | 18 | 19 | 20 | This protocol specifies a way for clients to propose arbitrary positions 21 | and dimensions for a set of views on a specific output of a compositor 22 | through the river_layout_v3 object. 23 | 24 | Layouts are a strictly linear list of views, the position and dimensions 25 | of which are supplied by the client. Any complex underlying data structure 26 | a client may use when generating the layout is lost in transmission. This 27 | is an intentional limitation. 28 | 29 | Additonally, this protocol allows the compositor to deliver arbitrary 30 | user-provided commands associated with a layout to clients. A client 31 | may use these commands to implement runtime configuration/control, or 32 | may ignore them entirely. How the user provides these commands to the 33 | compositor is not specified by this protocol and left to compositor policy. 34 | 35 | Warning! The protocol described in this file is currently in the 36 | testing phase. Backward compatible changes may be added together with 37 | the corresponding interface version bump. Backward incompatible changes 38 | can only be done by creating a new major version of the extension. 39 | 40 | 41 | 42 | 43 | A global factory for river_layout_v3 objects. 44 | 45 | 46 | 47 | 48 | This request indicates that the client will not use the 49 | river_layout_manager object any more. Objects that have been created 50 | through this instance are not affected. 51 | 52 | 53 | 54 | 55 | 56 | This creates a new river_layout_v3 object for the given wl_output. 57 | 58 | All layout related communication is done through this interface. 59 | 60 | The namespace is used by the compositor to decide which river_layout_v3 61 | object will receive layout demands for the output. 62 | 63 | The namespace is required to be be unique per-output. Furthermore, 64 | two separate clients may not share a namespace on separate outputs. If 65 | these conditions are not upheld, the the namespace_in_use event will 66 | be sent directly after creation of the river_layout_v3 object. 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | This interface allows clients to receive layout demands from the 77 | compositor for a specific output and subsequently propose positions and 78 | dimensions of individual views. 79 | 80 | 81 | 82 | 84 | 86 | 87 | 88 | 89 | 90 | This request indicates that the client will not use the river_layout_v3 91 | object any more. 92 | 93 | 94 | 95 | 96 | 97 | After this event is sent, all requests aside from the destroy event 98 | will be ignored by the server. If the client wishes to try again with 99 | a different namespace they must create a new river_layout_v3 object. 100 | 101 | 102 | 103 | 104 | 105 | The compositor sends this event to inform the client that it requires a 106 | layout for a set of views. 107 | 108 | The usable width and height height indicate the space in which the 109 | client can safely position views without interfering with desktop 110 | widgets such as panels. 111 | 112 | The serial of this event is used to identify subsequent requests as 113 | belonging to this layout demand. Beware that the client might need 114 | to handle multiple layout demands at the same time. 115 | 116 | The server will ignore responses to all but the most recent layout 117 | demand. Thus, clients are only required to respond to the most recent 118 | layout_demand received. If a newer layout_demand is received before 119 | the client has finished responding to an old demand, the client should 120 | abort work on the old demand as any further work would be wasted. 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | This request proposes a size and position for a view in the layout demand 132 | with matching serial. 133 | 134 | A client must send this request for every view that is part of the 135 | layout demand. The number of views in the layout is given by the 136 | view_count argument of the layout_demand event. Pushing too many or 137 | too few view dimensions is a protocol error. 138 | 139 | The x and y coordinates are relative to the usable area of the output, 140 | with (0,0) as the top left corner. 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | This request indicates that the client is done pushing dimensions 152 | and the compositor may apply the layout. This completes the layout 153 | demand with matching serial, any other requests sent with the serial 154 | are a protocol error. 155 | 156 | The layout_name argument is a user-facing name or short description 157 | of the layout that is being committed. The compositor may for example 158 | display this on a status bar, though what exactly is done with it is 159 | left to the compositor's discretion. 160 | 161 | The compositor is free to use this proposed layout however it chooses, 162 | including ignoring it. 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | This event informs the client of a command sent to it by the user. 171 | 172 | The semantic meaning of the command is left for the client to 173 | decide. It is also free to ignore it entirely if it so chooses. 174 | 175 | A layout_demand will be sent after this event if the compositor is 176 | currently using this layout object to arrange the output. 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /src/client.rs: -------------------------------------------------------------------------------- 1 | use super::layout::Layout; 2 | use super::lexer; 3 | use crate::wayland::{ 4 | river_layout_v3::river_layout_manager_v3::RiverLayoutManagerV3, 5 | river_layout_v3::river_layout_v3::Event, 6 | }; 7 | use wayland_client::protocol::wl_output::WlOutput; 8 | use wayland_client::Main; 9 | 10 | pub struct Globals { 11 | pub namespace: String, 12 | pub layout_manager: Option>, 13 | } 14 | 15 | // Parameters necessary to generate a layout 16 | #[derive(Copy, Clone)] 17 | pub struct Parameters { 18 | pub amount: u32, 19 | pub index: u32, 20 | pub ratio: f64, 21 | } 22 | 23 | #[derive(Copy, Clone)] 24 | pub enum Order { 25 | Ascend, 26 | Descend, 27 | } 28 | 29 | // The state of an Output 30 | pub struct Output { 31 | pub output: Main, 32 | // This is the index of the focused Tag 33 | pub focused: usize, 34 | // Defines if a layout should regenerated or not 35 | pub reload: bool, 36 | // Defines if a the layout area should reajusted to the output dimension or not 37 | pub resize: bool, 38 | // Order the tags are sorted 39 | pub order: Order, 40 | // Dimensions of the layout area 41 | pub dimension: Area, 42 | pub view_padding: i32, 43 | pub outer_padding: i32, 44 | pub smart_padding: bool, 45 | // The configuration of all Tags 46 | pub tags: [Option; 32], 47 | } 48 | 49 | // The configuration of a Tag 50 | pub struct Tag { 51 | pub name: String, 52 | pub layout: Layout, 53 | pub parameters: Parameters, 54 | } 55 | 56 | #[derive(Copy, Clone, Debug)] 57 | pub struct Area { 58 | pub x: u32, 59 | pub y: u32, 60 | pub w: u32, 61 | pub h: u32, 62 | } 63 | 64 | impl Globals { 65 | pub fn new(namespace: String) -> Globals { 66 | { 67 | Globals { 68 | namespace, 69 | layout_manager: None, 70 | } 71 | } 72 | } 73 | } 74 | 75 | impl Output { 76 | pub fn new(output: Main) -> Output { 77 | { 78 | Output { 79 | output, 80 | dimension: Area { 81 | x: 0, 82 | y: 0, 83 | w: 0, 84 | h: 0, 85 | }, 86 | focused: 0, 87 | reload: true, 88 | resize: false, 89 | view_padding: 0, 90 | outer_padding: 0, 91 | smart_padding: false, 92 | order: Order::Ascend, 93 | tags: Default::default(), 94 | } 95 | } 96 | } 97 | pub fn layout_filter( 98 | mut self, 99 | layout_manager: Option<&Main>, 100 | namespace: String, 101 | ) { 102 | // A generic default configuration used when a Tag isn't defined 103 | let mut default: Tag = { 104 | Tag { 105 | name: "kile".to_owned(), 106 | parameters: { 107 | Parameters { 108 | index: 0, 109 | amount: 1, 110 | ratio: 0.6, 111 | } 112 | }, 113 | layout: Layout::Full, 114 | } 115 | }; 116 | let layout = layout_manager 117 | .expect("Compositor doesn't implement river_layout_v3") 118 | .get_layout(&self.output, namespace.clone()); 119 | let mut view_padding = 0; 120 | // A vector holding the geometry of all the views from the most recent layout demand 121 | let mut views: Vec = Vec::new(); 122 | layout.quick_assign(move |layout, event, _| match event { 123 | Event::LayoutDemand { 124 | view_count, 125 | usable_width, 126 | usable_height, 127 | serial, 128 | tags, 129 | } => { 130 | let layout_name = if self.reload { 131 | if !self.resize { 132 | self.dimension = { 133 | Area { 134 | x: 0, 135 | y: 0, 136 | w: usable_width, 137 | h: usable_height, 138 | } 139 | }; 140 | if !self.smart_padding || view_count > 1 { 141 | self.dimension.apply_padding(self.outer_padding); 142 | } 143 | } 144 | self.focused = tag(tags, &self.order) as usize; 145 | match self.tags[self.focused].as_ref() { 146 | Some(tag) => { 147 | view_padding = self.view_padding; 148 | tag.update(&mut views, view_count, self.dimension); 149 | tag.name.as_str() 150 | } 151 | None => { 152 | default.update(&mut views, view_count, self.dimension); 153 | default.name.as_str() 154 | } 155 | } 156 | } else { 157 | "reload" 158 | }; 159 | self.reload = true; 160 | for area in &mut views { 161 | if !self.smart_padding || view_count > 1 { 162 | area.apply_padding(view_padding); 163 | } 164 | layout.push_view_dimensions( 165 | area.x as i32, 166 | area.y as i32, 167 | area.w, 168 | area.h, 169 | serial, 170 | ) 171 | } 172 | layout.commit(layout_name.to_owned(), serial); 173 | } 174 | Event::NamespaceInUse => { 175 | println!("Namespace already in use."); 176 | layout.destroy(); 177 | } 178 | // All String events are delegated to the lexer 179 | Event::UserCommand { command } => { 180 | if let Some((command, value)) = command.split_once(' ') { 181 | match command { 182 | "padding" => { 183 | if let Ok(value) = value.parse::() { 184 | self.outer_padding = value; 185 | view_padding = value - view_padding; 186 | self.view_padding = value; 187 | } 188 | } 189 | "mod_padding" => { 190 | if let Ok(delta) = value.parse::() { 191 | self.outer_padding += delta; 192 | if (self.view_padding as i32) + delta >= 0 { 193 | self.view_padding += delta; 194 | view_padding = delta; 195 | } 196 | } 197 | } 198 | "outer_padding" => { 199 | if let Ok(value) = value.parse::() { 200 | self.outer_padding = value; 201 | } 202 | } 203 | "view_padding" => { 204 | if let Ok(value) = value.parse::() { 205 | view_padding = value - view_padding; 206 | self.view_padding = value; 207 | if !views.is_empty() { 208 | self.reload = false; 209 | } 210 | } 211 | } 212 | "mod_outer_padding" => { 213 | if let Ok(delta) = value.parse::() { 214 | self.outer_padding += delta; 215 | } 216 | } 217 | "mod_view_padding" => { 218 | if let Ok(delta) = value.parse::() { 219 | if (self.view_padding as i32) + delta >= 0 { 220 | self.view_padding += delta; 221 | view_padding = delta; 222 | if !views.is_empty() { 223 | self.reload = false; 224 | } 225 | } 226 | } 227 | } 228 | "main_ratio" => { 229 | if let Some(tag) = self.tags[self.focused].as_mut() { 230 | if let Ok(value) = value.parse::() { 231 | tag.parameters.ratio = value.clamp(0.0, 1.0); 232 | } 233 | } 234 | } 235 | "mod_main_ratio" => { 236 | if let Some(tag) = self.tags[self.focused].as_mut() { 237 | if let Ok(delta) = value.parse::() { 238 | if delta <= tag.parameters.ratio { 239 | tag.parameters.ratio += delta; 240 | } 241 | } 242 | } 243 | } 244 | "default_main_ratio" => { 245 | if let Ok(value) = value.parse::() { 246 | default.parameters.ratio = value.clamp(0.0, 1.0); 247 | } 248 | } 249 | "mod_default__main_ratio" => { 250 | if let Ok(delta) = value.parse::() { 251 | if delta <= default.parameters.ratio { 252 | default.parameters.ratio += delta; 253 | } 254 | } 255 | } 256 | "main_amount" => { 257 | if let Some(tag) = self.tags[self.focused].as_mut() { 258 | if let Ok(value) = value.parse::() { 259 | tag.parameters.amount = value 260 | } 261 | } 262 | } 263 | "mod_main_amount" => { 264 | if let Some(tag) = self.tags[self.focused].as_mut() { 265 | if let Ok(delta) = value.parse::() { 266 | if (tag.parameters.amount as i32) + delta >= 0 { 267 | tag.parameters.amount = 268 | ((tag.parameters.amount as i32) + delta) as u32 269 | } 270 | } 271 | } 272 | } 273 | "default_main_amount" => { 274 | if let Ok(value) = value.parse::() { 275 | default.parameters.amount = value; 276 | } 277 | } 278 | "mod_default_main_amount" => { 279 | if let Ok(delta) = value.parse::() { 280 | if (default.parameters.amount as i32) + delta >= 0 { 281 | default.parameters.amount = 282 | ((default.parameters.amount as i32) + delta) as u32 283 | } 284 | } 285 | } 286 | "main_index" => { 287 | if let Some(tag) = self.tags[self.focused].as_mut() { 288 | if let Ok(value) = value.parse::() { 289 | tag.parameters.index = value; 290 | } 291 | } 292 | } 293 | "mod_main_index" => { 294 | if let Some(tag) = self.tags[self.focused].as_mut() { 295 | if let Ok(delta) = value.parse::() { 296 | if (tag.parameters.index as i32) + delta >= 0 { 297 | tag.parameters.index = 298 | ((tag.parameters.index as i32) + delta) as u32 299 | } 300 | } 301 | } 302 | } 303 | "default_main_index" => { 304 | if let Ok(value) = value.parse::() { 305 | default.parameters.index = value; 306 | } 307 | } 308 | "mod_default_main_index" => { 309 | if let Ok(delta) = value.parse::() { 310 | if (default.parameters.index as i32) + delta >= 0 { 311 | default.parameters.index = 312 | ((default.parameters.index as i32) + delta) as u32 313 | } 314 | } 315 | } 316 | "xoffset" => { 317 | if let Ok(delta) = value.parse::() { 318 | if delta < 0 { 319 | self.dimension.x = 0; 320 | } else { 321 | self.dimension.x = delta.abs() as u32; 322 | } 323 | self.dimension.w -= delta.abs() as u32; 324 | self.resize = true; 325 | } 326 | } 327 | "yoffset" => { 328 | if let Ok(delta) = value.parse::() { 329 | if delta < 0 { 330 | self.dimension.y = 0; 331 | } else { 332 | self.dimension.y = delta.abs() as u32; 333 | } 334 | self.dimension.h -= delta.abs() as u32; 335 | self.resize = true; 336 | } 337 | } 338 | "dimension" => { 339 | let mut fields = value.split_whitespace(); 340 | self.dimension = { 341 | self.resize = true; 342 | Area { 343 | x: fields 344 | .next() 345 | .unwrap_or_default() 346 | .parse::() 347 | .unwrap_or(self.dimension.x), 348 | y: fields 349 | .next() 350 | .unwrap_or_default() 351 | .parse::() 352 | .unwrap_or(self.dimension.y), 353 | w: fields 354 | .next() 355 | .unwrap_or_default() 356 | .parse::() 357 | .unwrap_or(self.dimension.w), 358 | h: fields 359 | .next() 360 | .unwrap_or_default() 361 | .parse::() 362 | .unwrap_or(self.dimension.h), 363 | } 364 | } 365 | } 366 | "resize" => { 367 | if let Ok(ans) = value.parse::() { 368 | self.resize = ans; 369 | } 370 | } 371 | "smart_padding" => { 372 | if let Ok(ans) = value.parse::() { 373 | self.smart_padding = ans; 374 | } 375 | } 376 | "order" => match value { 377 | "ascend" => self.order = Order::Ascend, 378 | "descend" => self.order = Order::Descend, 379 | _ => {} 380 | }, 381 | "default" => { 382 | let (name, layout) = if let Some(data) = value.split_once('\n') { 383 | data 384 | } else if let Some(data) = lexer::split_ounce(value, ' ') { 385 | data 386 | } else { 387 | ("kile", value) 388 | }; 389 | default.name = name.to_owned(); 390 | default.layout = lexer::parse(layout); 391 | } 392 | "clear" => match value { 393 | "all" => self.tags = Default::default(), 394 | "default" => default.layout = Layout::Full, 395 | "focused" => self.tags[self.focused] = None, 396 | _ => match value.parse::() { 397 | Ok(int) => { 398 | if int > 0 && int < 33 { 399 | self.tags[int - 1] = None 400 | } 401 | } 402 | Err(_) => {} 403 | }, 404 | }, 405 | _ => lexer::main(&mut self, command, value), 406 | } 407 | } 408 | } 409 | }); 410 | } 411 | } 412 | 413 | impl Tag { 414 | fn update(&self, views: &mut Vec, view_amount: u32, area: Area) { 415 | views.clear(); 416 | area.generate(views, &self.layout, &self.parameters, view_amount, true); 417 | } 418 | } 419 | 420 | fn tag(tagmask: u32, order: &Order) -> u32 { 421 | let mut int = 0; 422 | let mut current: u32; 423 | while { 424 | current = 1 << int; 425 | current < tagmask 426 | } { 427 | if current != tagmask && (tagmask / current) % 2 != 0 { 428 | if let Order::Descend = order { 429 | int = tag(tagmask - current, order); 430 | } 431 | break; 432 | } 433 | int += 1; 434 | } 435 | int 436 | } 437 | -------------------------------------------------------------------------------- /src/layout.rs: -------------------------------------------------------------------------------- 1 | use super::client::*; 2 | 3 | #[derive(Copy, Clone, Debug)] 4 | pub enum Condition { 5 | Equal, 6 | Greater, 7 | Less, 8 | } 9 | 10 | #[derive(Copy, Clone, Debug)] 11 | pub enum Variant { 12 | Amount(u32), 13 | Index(u32), 14 | Ratio(f64), 15 | } 16 | 17 | impl Condition { 18 | fn is_true(&self, variant: &Variant, parameters: Parameters) -> bool { 19 | match self { 20 | Condition::Equal => match *variant { 21 | Variant::Amount(uint) => parameters.amount == uint, 22 | Variant::Index(uint) => parameters.index == uint, 23 | Variant::Ratio(float) => parameters.ratio == float, 24 | }, 25 | Condition::Greater => match *variant { 26 | Variant::Amount(uint) => parameters.amount > uint, 27 | Variant::Index(uint) => parameters.index > uint, 28 | Variant::Ratio(float) => parameters.ratio > float, 29 | }, 30 | Condition::Less => match *variant { 31 | Variant::Amount(uint) => parameters.amount < uint, 32 | Variant::Index(uint) => parameters.index < uint, 33 | Variant::Ratio(float) => parameters.ratio < float, 34 | }, 35 | } 36 | } 37 | } 38 | 39 | #[derive(Clone, Debug)] 40 | pub enum Layout { 41 | Full, 42 | Deck, 43 | Vertical, 44 | Horizontal, 45 | Recursive { 46 | outer: Box, 47 | inner: Vec, 48 | }, 49 | Conditional { 50 | a: Box, 51 | b: Box, 52 | variant: Variant, 53 | condition: Condition, 54 | }, 55 | Parameters { 56 | index: Option, 57 | ratio: Option, 58 | amount: Option, 59 | layout: Box, 60 | }, 61 | } 62 | 63 | impl Area { 64 | pub fn apply_padding(&mut self, padding: i32) { 65 | if 2 * padding < self.h as i32 && 2 * padding < self.w as i32 { 66 | self.x = ((self.x as i32) + padding) as u32; 67 | self.y = ((self.y as i32) + padding) as u32; 68 | self.w = ((self.w as i32) - 2 * padding) as u32; 69 | self.h = ((self.h as i32) - 2 * padding) as u32; 70 | } 71 | } 72 | pub fn generate( 73 | self, 74 | views: &mut Vec, 75 | layout: &Layout, 76 | parameters: &Parameters, 77 | mut view_amount: u32, 78 | ratio: bool, 79 | ) { 80 | let mut area = self; 81 | let master = ratio && view_amount > 1 && parameters.index < view_amount; 82 | 83 | match layout { 84 | Layout::Full => { 85 | for _ in 0..view_amount { 86 | views.push(area); 87 | } 88 | } 89 | Layout::Deck => { 90 | let yoffset = ((self.h as f64 * 0.1) / (view_amount as f64 - 1.0)).floor() as u32; 91 | let xoffset = ((self.w as f64 * 0.1) / (view_amount as f64 - 1.0)).floor() as u32; 92 | for _ in 0..view_amount { 93 | area.w = self.w - (xoffset * (view_amount - 1)); 94 | area.h = self.h - (yoffset * (view_amount - 1)); 95 | views.push(area); 96 | area.x += xoffset; 97 | area.y += yoffset; 98 | } 99 | } 100 | Layout::Horizontal => { 101 | let reste = area.h % view_amount; 102 | let mut slave_height = area.h; 103 | let main_height = if master { 104 | ((area.h as f64) * parameters.ratio) as u32 105 | } else { 106 | 0 107 | }; 108 | slave_height -= main_height; 109 | for i in 0..view_amount { 110 | area.h = if master && i == parameters.index { 111 | main_height 112 | } else if master { 113 | slave_height / (view_amount - 1) 114 | } else { 115 | slave_height / view_amount 116 | }; 117 | if i == 0 { 118 | area.h += reste; 119 | } 120 | 121 | views.push(area); 122 | area.y += area.h; 123 | } 124 | } 125 | Layout::Vertical => { 126 | let reste = area.w % view_amount; 127 | let mut slave_width = area.w; 128 | let main_width = if master { 129 | ((area.w as f64) * parameters.ratio) as u32 130 | } else { 131 | 0 132 | }; 133 | slave_width -= main_width; 134 | for i in 0..view_amount { 135 | area.w = if master && i == parameters.index { 136 | main_width 137 | } else if master { 138 | slave_width / (view_amount - 1) 139 | } else { 140 | slave_width / view_amount 141 | }; 142 | if i == 0 { 143 | area.w += reste; 144 | } 145 | 146 | views.push(area); 147 | area.x += area.w; 148 | } 149 | } 150 | Layout::Recursive { outer, inner } => { 151 | let mut frame = Vec::new(); 152 | let frames_available = inner.len() as u32; 153 | let mut frame_amount = { 154 | let main = parameters.amount >= 1 155 | && frames_available > 1 156 | && parameters.index < frames_available 157 | && view_amount > parameters.amount; 158 | if parameters.amount >= view_amount { 159 | 1 160 | } else if main && view_amount - parameters.amount < frames_available { 161 | 1 + view_amount - parameters.amount 162 | } else if view_amount > frames_available || main { 163 | frames_available 164 | } else { 165 | view_amount 166 | } 167 | }; 168 | area.generate(&mut frame, &*outer, parameters, frame_amount, ratio); 169 | if parameters.amount > 0 170 | && parameters.amount <= view_amount 171 | && parameters.index < frame_amount 172 | { 173 | frame_amount -= 1; 174 | frame.remove(parameters.index as usize).generate( 175 | views, 176 | &inner[parameters.index as usize], 177 | parameters, 178 | if frame_amount == 0 { 179 | view_amount 180 | } else { parameters.amount }, 181 | false, 182 | ); 183 | view_amount -= parameters.amount; 184 | } 185 | for (mut i, rect) in frame.iter_mut().enumerate() { 186 | let mut count = view_amount / frame_amount; 187 | if view_amount % frame_amount != 0 && i as u32 != frame_amount { 188 | view_amount -= 1; 189 | count += 1; 190 | } 191 | if frame_amount as usize != inner.len() && i >= parameters.index as usize { 192 | i += 1 193 | } 194 | rect.generate(views, &inner[i], parameters, count, false); 195 | } 196 | } 197 | Layout::Parameters { 198 | index, 199 | ratio, 200 | amount, 201 | layout, 202 | } => { 203 | let parameters = { 204 | Parameters { 205 | index: index.unwrap_or(parameters.index), 206 | ratio: ratio.unwrap_or(parameters.ratio), 207 | amount: amount.unwrap_or(parameters.amount), 208 | } 209 | }; 210 | area.generate(views, &*layout, ¶meters, view_amount, true); 211 | } 212 | Layout::Conditional { 213 | a, 214 | b, 215 | variant, 216 | condition, 217 | } => { 218 | if condition.is_true( 219 | variant, 220 | Parameters { 221 | amount: view_amount, 222 | ratio: parameters.ratio, 223 | index: parameters.index, 224 | }, 225 | ) { 226 | area.generate(views, &*a, ¶meters, view_amount, ratio); 227 | } else { 228 | area.generate(views, &*b, ¶meters, view_amount, ratio); 229 | } 230 | } 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /src/lexer/lexer.rs: -------------------------------------------------------------------------------- 1 | use crate::layout::*; 2 | 3 | pub fn split_ounce<'s>(exp: &'s str, pattern: char) -> Option<(&'s str, &'s str)> { 4 | let mut paren = 0; 5 | for (i, c) in exp.to_string().chars().enumerate() { 6 | match c { 7 | '(' => paren += 1, 8 | ')' => paren -= 1, 9 | _ => {} 10 | } 11 | if c == pattern && paren == 0 { 12 | if !&exp[i + 1..].is_empty() { 13 | return Some((exp[0..i].trim(), exp[i + 1..].trim())); 14 | } 15 | } 16 | } 17 | None 18 | } 19 | 20 | // Captures a string slice contained within specific patterns 21 | fn clamp<'s>(exp: &'s str) -> Option<&'s str> { 22 | if let Some(start) = exp.find('(') { 23 | if let Some(end) = exp.rfind(')') { 24 | return Some(&exp[start + 1..end].trim()); 25 | } 26 | } 27 | None 28 | } 29 | // Iterates over all expressions delimited by a character 30 | // and excutes a function on each one of them 31 | fn filter<'s>(exp: &'s str, pattern: char, mut f: impl FnMut(&'s str) -> Result<(), String>) { 32 | if let Some((head, tail)) = split_ounce(exp, pattern) { 33 | match f(head) { 34 | Ok(_) => { 35 | filter(tail, pattern, f); 36 | } 37 | Err(m) => { 38 | if !m.is_empty() { 39 | eprintln!("{}", m) 40 | } 41 | } 42 | } 43 | } else { 44 | match f(exp) { 45 | Err(m) => { 46 | if !m.is_empty() { 47 | eprintln!("{}", m) 48 | } 49 | } 50 | _ => {} 51 | } 52 | } 53 | } 54 | 55 | pub fn parse<'s>(name: &str) -> Layout { 56 | match name { 57 | "f" | "ful" | "full" => Layout::Full, 58 | "d" | "dec" | "deck" => Layout::Deck, 59 | "v" | "ver" | "vertical" => Layout::Vertical, 60 | "h" | "hor" | "horizontal" => Layout::Horizontal, 61 | _ => { 62 | let mut condition = None; 63 | let (mut value, mut layout) = (None, None); 64 | if let Some(exp) = clamp(name) { 65 | if let Some((outer, inner)) = split_ounce(exp, ':') { 66 | Layout::Recursive { 67 | outer: { Box::new(parse(outer)) }, 68 | inner: if !inner.is_empty() { 69 | let mut vec = Vec::new(); 70 | filter(inner, ' ', |s| { 71 | vec.push(parse(s)); 72 | Ok(()) 73 | }); 74 | vec 75 | } else { 76 | eprintln!("Unsufficient amount of sublayouts: {}", exp); 77 | vec![Layout::Full] 78 | }, 79 | } 80 | } else if { 81 | if let Some((v, l)) = split_ounce(exp, '>') { 82 | value = Some(v); 83 | layout = Some(l); 84 | condition = Some(Condition::Greater); 85 | } else if let Some((v, l)) = split_ounce(exp, '<') { 86 | value = Some(v); 87 | layout = Some(l); 88 | condition = Some(Condition::Less); 89 | } else if let Some((v, l)) = split_ounce(exp, '=') { 90 | value = Some(v); 91 | layout = Some(l); 92 | condition = Some(Condition::Equal); 93 | } 94 | condition.is_some() 95 | } { 96 | if let Ok(uint) = value.unwrap().parse::() { 97 | if let Some((a, b)) = split_ounce(layout.unwrap(), '?') { 98 | Layout::Conditional { 99 | variant: Variant::Amount(uint), 100 | condition: condition.unwrap(), 101 | a: Box::new(parse(a)), 102 | b: Box::new(parse(b)), 103 | } 104 | } else if let Some((a, b)) = split_ounce(layout.unwrap(), '!') { 105 | Layout::Conditional { 106 | variant: Variant::Index(uint), 107 | condition: condition.unwrap(), 108 | a: Box::new(parse(a)), 109 | b: Box::new(parse(b)), 110 | } 111 | } else { 112 | Layout::Full 113 | } 114 | } else if let Ok(float) = value.unwrap().parse::() { 115 | if let Some((a, b)) = split_ounce(layout.unwrap(), '%') { 116 | Layout::Conditional { 117 | variant: Variant::Ratio(float), 118 | condition: condition.unwrap(), 119 | a: Box::new(parse(a)), 120 | b: Box::new(parse(b)), 121 | } 122 | } else { 123 | Layout::Full 124 | } 125 | } else { 126 | Layout::Full 127 | } 128 | } else { 129 | if let Some((layout, parameters)) = split_ounce(exp, ' ') { 130 | let mut var = parameters.split_whitespace(); 131 | Layout::Parameters { 132 | layout: Box::new(parse(layout)), 133 | amount: if let Ok(uint) = var.next().unwrap_or_default().parse::() 134 | { 135 | Some(uint) 136 | } else { 137 | None 138 | }, 139 | ratio: if let Ok(float) = var.next().unwrap_or_default().parse::() 140 | { 141 | Some(float) 142 | } else { 143 | None 144 | }, 145 | index: if let Ok(uint) = var.next().unwrap_or_default().parse::() { 146 | Some(uint) 147 | } else { 148 | None 149 | }, 150 | } 151 | } else { 152 | Layout::Full 153 | } 154 | } 155 | } else { 156 | Layout::Full 157 | } 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/lexer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod lexer; 2 | 3 | use crate::client::*; 4 | use crate::layout::*; 5 | pub use lexer::*; 6 | 7 | pub fn main<'s>(output_handle: &mut Output, name: &'s str, value: &'s str) { 8 | let tags: Result, ()> = match name.as_ref() { 9 | "focused" => Ok(output_handle.focused..output_handle.focused + 1), 10 | "all" => Ok(0..32), 11 | _ => match name.parse::() { 12 | Ok(int) => Ok(int - 1..int), 13 | Err(e) => { 14 | eprintln!("{} : {}", e, name); 15 | Err(()) 16 | } 17 | }, 18 | }; 19 | let (name, layout) = if let Some(data) = value.split_once('\n') { 20 | data 21 | } else if let Some(data) = lexer::split_ounce(value, ' ') { 22 | data 23 | } else { 24 | ("kile", value) 25 | }; 26 | let layout = lexer::parse(&layout.replace("\t", " ")); 27 | if let Ok(tags) = tags { 28 | if let Layout::Parameters { 29 | ratio, 30 | index, 31 | amount, 32 | layout, 33 | } = layout 34 | { 35 | for i in tags { 36 | let tag = output_handle.tags[i].as_mut(); 37 | match tag { 38 | Some(tag) => { 39 | tag.name = name.to_owned(); 40 | tag.parameters.index = index.unwrap_or(0); 41 | tag.parameters.amount = amount.unwrap_or(1); 42 | tag.parameters.ratio = ratio.unwrap_or(0.6); 43 | tag.layout = layout.as_ref().clone(); 44 | } 45 | None => { 46 | output_handle.tags[i] = Some({ 47 | Tag { 48 | name: name.to_owned(), 49 | layout: layout.as_ref().clone(), 50 | parameters: { 51 | Parameters { 52 | index: index.unwrap_or(0), 53 | amount: amount.unwrap_or(1), 54 | ratio: ratio.unwrap_or(0.6), 55 | } 56 | }, 57 | } 58 | }) 59 | } 60 | } 61 | } 62 | } else { 63 | for i in tags { 64 | let tag = output_handle.tags[i].as_mut(); 65 | match tag { 66 | Some(tag) => { 67 | tag.layout = layout.clone(); 68 | tag.name = name.to_owned(); 69 | } 70 | None => { 71 | output_handle.tags[i] = Some({ 72 | Tag { 73 | name: name.to_owned(), 74 | layout: layout.clone(), 75 | parameters: { 76 | Parameters { 77 | index: 0, 78 | amount: 1, 79 | ratio: 0.6, 80 | } 81 | }, 82 | } 83 | }) 84 | } 85 | } 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod client; 2 | mod layout; 3 | mod lexer; 4 | mod wayland; 5 | 6 | use crate::wayland::river_layout_v3::river_layout_manager_v3::RiverLayoutManagerV3; 7 | use client::{Globals, Output}; 8 | use std::env; 9 | use wayland_client::protocol::{wl_output, wl_output::WlOutput}; 10 | use wayland_client::{Display, GlobalManager, Main}; 11 | 12 | fn main() { 13 | let mut args = env::args(); 14 | let mut namespace = String::from("kile"); 15 | args.next(); 16 | loop { 17 | match args.next() { 18 | Some(flag) => match flag.as_str() { 19 | "--namespace" | "--n" | "-n" => { 20 | if let Some(name) = args.next() { 21 | namespace = name; 22 | } 23 | } 24 | "--help" | "-h" | "--h" => { 25 | print!("Usage: kile [option]\n\n"); 26 | print!(" -n, --n, --namespace : the namespace of kile.\n"); 27 | std::process::exit(0); 28 | } 29 | _ => break, 30 | }, 31 | None => break, 32 | } 33 | } 34 | 35 | let mut globals = Globals::new(namespace); 36 | let display = Display::connect_to_env().unwrap(); 37 | let mut event_queue = display.create_event_queue(); 38 | let attached_display = (*display).clone().attach(event_queue.token()); 39 | 40 | GlobalManager::new_with_cb( 41 | &attached_display, 42 | wayland_client::global_filter!( 43 | [ 44 | RiverLayoutManagerV3, 45 | 1, 46 | |layout_manager: Main, mut globals: DispatchData| { 47 | globals.get::().unwrap().layout_manager = Some(layout_manager); 48 | } 49 | ], 50 | [ 51 | WlOutput, 52 | 3, 53 | |output: Main, _globals: DispatchData| { 54 | output.quick_assign(move |output, event, mut globals| match event { 55 | wl_output::Event::Done => { 56 | let output = Output::new(output); 57 | if let Some(globals) = globals.get::() { 58 | output.layout_filter( 59 | globals.layout_manager.as_ref(), 60 | globals.namespace.clone(), 61 | ); 62 | } 63 | } 64 | _ => {} 65 | }); 66 | } 67 | ] 68 | ), 69 | ); 70 | 71 | loop { 72 | event_queue 73 | .dispatch(&mut globals, |event, object, _| { 74 | panic!( 75 | "[callop] Encountered an orphan event: {}@{}: {}", 76 | event.interface, 77 | object.as_ref().id(), 78 | event.name 79 | ); 80 | }) 81 | .unwrap(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/wayland/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate wayland_client; 2 | extern crate wayland_commons; 3 | 4 | // Re-export only the actual code, and then only use this re-export 5 | // The `generated` module below is just some boilerplate to properly isolate stuff 6 | // and avoid exposing internal details. 7 | // 8 | // You can use all the types from my_protocol as if they went from `wayland_client::protocol`. 9 | pub use wayland::client as river_layout_v3; 10 | 11 | pub mod wayland { 12 | // The generated code tends to trigger a lot of warnings 13 | // so we isolate it into a very permissive module 14 | #![allow(dead_code, non_camel_case_types, unused_unsafe, unused_variables)] 15 | #![allow(non_upper_case_globals, non_snake_case, unused_imports)] 16 | 17 | pub mod client { 18 | // These imports are used by the generated code 19 | pub(crate) use wayland_client::protocol::wl_output; 20 | pub(crate) use wayland_client::{protocol, sys}; 21 | pub(crate) use wayland_client::{ 22 | AnonymousObject, Attached, Display, GlobalManager, Main, Proxy, ProxyMap, 23 | }; 24 | pub(crate) use wayland_commons::map::{Object, ObjectMetadata}; 25 | pub(crate) use wayland_commons::smallvec; 26 | pub(crate) use wayland_commons::wire::{Argument, ArgumentType, Message, MessageDesc}; 27 | pub(crate) use wayland_commons::{Interface, MessageGroup}; 28 | // If you protocol interacts with objects from other protocols, you'll need to import 29 | // their modules, like so: 30 | pub(crate) use wayland_client::protocol::{wl_region, wl_seat, wl_surface}; 31 | include!(concat!( 32 | env!("CARGO_MANIFEST_DIR"), 33 | "/src/wayland/river_layout_v3.rs" 34 | )); 35 | } 36 | } 37 | --------------------------------------------------------------------------------