Compare commits

...

73 commits

Author SHA1 Message Date
Janis 1d15ef6336 fullscreen fix? 2022-05-17 16:46:40 +02:00
Janis ae89404de3 added cursor types 2022-05-08 18:59:42 +02:00
Janis fdf81d8d6a update dependencies, bump version to 0.3 2022-05-08 13:33:58 +02:00
janis 1aab741b49 Merge pull request 'feature_ewmh' (#4) from feature_ewmh into main
Reviewed-on: https://desktop-host/git/janis/wm/pulls/4
2022-05-08 13:27:50 +02:00
Janis c826556e83 partial EWMH support now, window type, wmname, clientlist,wmcheck 2022-05-08 13:27:50 +02:00
Janis 30867df46c added update_window_type function to clients which updates floating for dialog
style windows
2022-05-08 13:27:50 +02:00
Janis 364d621b72 added set_tiled function to change window from floating to tiled 2022-05-08 13:27:50 +02:00
Janis bae880c5e1 refactor Client to better reflect different window types 2022-05-08 13:27:50 +02:00
Janis 449b4cccd8 added clamp function to Size<T> 2022-05-08 13:27:50 +02:00
Janis 6c4f0d54bd added wm_transient_for atom 2022-05-08 13:27:50 +02:00
Janis 4eb1cb4555 added WindowType enum 2022-05-08 13:27:50 +02:00
Janis f9afdc990d setting supported ewmh atoms fixes fullscreen 2022-05-08 13:27:50 +02:00
Janis ac433847c5 added window name event 2022-05-08 13:27:50 +02:00
Janis 0dd42a7039 refactored atoms 2022-05-08 13:27:50 +02:00
Janis 85d3c3ce79 about to breka everything 2022-05-08 13:27:50 +02:00
Janis fb011ea23f added XLibConnection, EWMHAtoms type that stores atoms 2022-05-08 13:27:50 +02:00
Janis 2f805dab21 added test to EWMH atoms 2022-05-08 13:27:50 +02:00
Janis 56fff2698b added all EWMH atoms as enum 2022-05-08 13:27:50 +02:00
Janis db6ffb9416 added nirgendwm.toml config file example 2022-05-08 13:25:14 +02:00
Janis ba047217a6 changed name to nirgendwm, bumped version 2022-05-08 13:16:05 +02:00
Janis bc13bf43d6 changed default terminal to xterm 2022-05-08 13:15:52 +02:00
janis 590af3a06c Merge pull request 'feature_window-border' (#3) from feature_window-border into main
Reviewed-on: https://desktop-host/git/janis/wm/pulls/3
2022-05-08 13:12:56 +02:00
Janis 702004d2d2 remove border for fullscreen clients 2022-05-08 13:06:08 +02:00
Janis daf9f72a89 added border size to config 2022-05-08 12:56:43 +02:00
janis 71ddeb6af1 Merge pull request 'get_atom_property_leak_fix' (#2) from get_atom_property_leak_fix into main
Reviewed-on: https://desktop-host/git/janis/wm/pulls/2
2022-05-07 16:39:24 +02:00
Janis b1895bdd07 fixed get_atom_property() 2022-05-07 13:11:06 +02:00
Janis e49fdfa5be adds new XPointer<T> type 2022-05-07 00:32:37 +02:00
Janis b3f586ea6a removed old xlib.rs file 2022-05-07 00:29:28 +02:00
Janis 2c6d4fd465 fullscreen windows can no longer be resized or moved 2021-12-02 22:16:57 +01:00
Janis 192f865fec Merge branch 'feature-fullscreen' 2021-12-02 21:42:57 +01:00
Janis 25c0d94217 Merge branch 'refactor_point-size' into feature-fullscreen 2021-12-02 21:42:10 +01:00
Janis b49bfed1f0 removed debug logging 2021-12-02 21:36:51 +01:00
Janis c3f3ad7203 rebase/corrected all cases of Point<I> that were sizes 2021-12-02 21:32:56 +01:00
Janis 5dbfa6fbcf added fullscreen mechanics 2021-12-02 20:22:21 +01:00
Janis c9b926f5ba moved Point<I> to module utils
and also added `Size<I>`
both types depend on `num-traits`
2021-12-02 20:21:21 +01:00
Janis f6a871d1e7 changed FullscreenEvent to be On, Off, Toggle instead of a simple bool 2021-12-02 18:15:45 +01:00
Janis 7961c97d2f fix: transient windows appear as 1x1 sized 2021-12-02 18:13:00 +01:00
Janis 81a49e8290 seperated crate into lib and binary 2021-11-30 15:47:42 +01:00
Janis f26ca7948b added enwline to end of Cargo.toml 2021-11-29 20:49:50 +01:00
Janis 4810d88dc1 Merge branch 'refactor-2' 2021-11-29 20:47:57 +01:00
Janis f57a5f8033 updated readme 2021-11-29 20:39:00 +01:00
Janis 4d49ae52fd removed pictures 2021-11-29 01:06:06 +01:00
Janis 8f5f60455c made terminal command configurable 2021-11-29 00:41:15 +01:00
Janis c72356a087 disabled screenshot keybind since it doesnt work and also somehow grabs up 2021-11-29 00:07:56 +01:00
Janis 6404888941 cleanup, warnings, dead code, et cetera 2021-11-28 22:54:09 +01:00
Janis ece0eb7903 made border colors configurable in the config file 2021-11-28 22:45:02 +01:00
Janis 3a56102ec2 added config file and deserialization 2021-11-28 22:00:08 +01:00
Janis d3b4fcbf18 added a way to add already existing windows to wm 2021-11-28 21:15:51 +01:00
Janis df3c2e33ce config value for killing clients on exit 2021-11-28 21:02:38 +01:00
Janis 053afa576e removed Makefile and xinitrc
both files are now no longer needed since i added the runner command
2021-11-28 21:01:50 +01:00
Janis 2e589bf94b custom runner using Xephyr 2021-11-28 20:54:38 +01:00
Janis 91b5c91bd5 fixed window borders 2021-11-28 19:32:05 +01:00
Janis b47f245250 seems to work now :^) 2021-11-28 19:11:16 +01:00
Janis 2b4ddc8b5a fixed keysym_to_virtual_keycode and virtual_keycode_to_keysym 2021-11-28 19:10:36 +01:00
Janis d3f630549e fixed keybinds in backend trait/impl 2021-11-28 16:44:33 +01:00
Janis 964d6fe748 window server backend cursor api 2021-11-28 15:46:54 +01:00
Janis 72129ba61e keybind grabbing in xlib backend 2021-11-27 22:14:09 +01:00
Janis 696559d0af keybinds are not stored in a Rc<RefCell<>> to prevent cloning
this could be blocking if i ever add dynamic keybinds
2021-11-27 22:12:48 +01:00
Janis aafbcf2314 changed multiple (i32, i32) tuples to Point<i32> 2021-11-26 22:52:27 +01:00
Janis af21769d52 implemented most backend trait functions for XLib 2021-11-26 22:51:39 +01:00
Janis d3afc30ceb moved backend trait to own file 2021-11-26 22:51:08 +01:00
Janis a85d8d0df5 changed to using Point<I> instead of tuples or slices 2021-11-25 11:27:41 +01:00
Janis db17c9dbfe more work on XLib backend 2021-11-24 22:57:17 +01:00
Janis 57863e2eb7 changed ModifierState to use bitflags crate 2021-11-24 22:56:53 +01:00
Janis bea2ad6688 cargo check throws no error 2021-11-24 18:40:55 +01:00
No One 6c3999caab test 2021-11-22 20:17:43 +01:00
user ee1aa9cfae fixes and suppressing useless warnings 2021-11-21 20:41:25 +01:00
user ff27ec18d9 VirtualKeyCode conversion traits 2021-11-21 18:07:04 +01:00
user a175362a32 merged keysym.rs from branch refactor 2021-11-21 17:05:27 +01:00
user 1bc0c98156 idk 2021-11-21 17:05:07 +01:00
user 94c5cd9111 starting to abstract away xlib backend 2021-11-21 15:43:17 +01:00
user 8bd8894736 added filed from refactor branch 2021-11-20 23:51:23 +01:00
user 9f77d5f570 disabled mod clients2, fixed xinitrc 2021-11-19 04:30:36 +01:00
22 changed files with 5813 additions and 1216 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
/Cargo.lock /Cargo.lock
/.cargo/ /.cargo/
/wmlog /wmlog
/xinitrc.tmp

View file

@ -1,15 +1,27 @@
[package] [package]
name = "wm" name = "wm"
version = "0.2.0" version = "0.3.0"
authors = ["noonebtw <noonebtw@gmail.com>"] authors = ["noonebtw <noonebtw@gmail.com>"]
edition = "2018" edition = "2018"
[[bin]]
name = "nirgendwm"
path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
x11 = {version = "2.18.2", features = ["xlib"] } x11 = {version = "2.19", features = ["xlib", "xft"] }
log = "0.4.13" log = "0.4"
simple_logger = "1.11.0" simple_logger = "2.0"
dirs = "3.0.2" dirs = "3.0.0"
log4rs = "1.0.0" log4rs = "1.0"
indexmap = "1.6.2" indexmap = "1.0"
thiserror = "1.0"
bitflags = "1.3"
derivative = "2.2.0"
serde = { version = "1.0", features = ["derive"] }
toml = "0.5"
num-traits = "0.2"
strum = {version = "0.24.0", features = ["derive"]}
bytemuck = "1.0"

View file

@ -1,4 +1,5 @@
# Unnamed WM # No WM
## formerly Unnamed
This Project is a x11 tiling window manager written in Rust and losely based on / inspired by suckless' [dwm](https://dwm.suckless.org/). This Project is a x11 tiling window manager written in Rust and losely based on / inspired by suckless' [dwm](https://dwm.suckless.org/).
@ -11,5 +12,5 @@ Both `M-S-T` and `M-S-RET` will spawn an instance of `xterm`, `M-q` will kill th
One big difference from dwm is the way I handle virtual screens, although this is mostly a placeholder mechanic that I will most likely change in the future. Currently I have 3 (or more) virtual screens in a list that can be rotated with `M-left` and `M-right`. One big difference from dwm is the way I handle virtual screens, although this is mostly a placeholder mechanic that I will most likely change in the future. Currently I have 3 (or more) virtual screens in a list that can be rotated with `M-left` and `M-right`.
Unnamed WM also has optional gaps :^) No WM also has optional gaps :^)
![Unnamed WM in a VM](/vm-ss.png) ![No WM in a VM](/vm-ss.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 853 KiB

8
nirgendwm.toml Normal file
View file

@ -0,0 +1,8 @@
num_virtualscreens = 10
mod_key = "Super"
gap = 10
border_width = 5
active_window_border_color = "#6E0AC4"
inactive_window_border_color = "#CE9CFA"
kill_clients_on_exit = false
terminal_command = ["alacritty", []]

4
nowm.toml Normal file
View file

@ -0,0 +1,4 @@
num_virtualscreens = 10
mod_key = "Super"
gap = 5
kill_clients_on_exit = false

12
runner.sh Executable file
View file

@ -0,0 +1,12 @@
#!/bin/sh
# binary supplied by cargo
bin=$1
# write temporary xinitrc
printf "exec $bin\n" > xinitrc.tmp
# call xinit
XEPHYR_BIN=$(which Xephyr)
[ -z "$XEPHYR_BIN" ] || exec xinit ./xinitrc.tmp -- $XEPHYR_BIN :100 -ac -screen 800x600

211
src/backends/keycodes.rs Normal file
View file

@ -0,0 +1,211 @@
#![allow(dead_code)]
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
pub enum KeyOrButton {
Key(VirtualKeyCode),
Button(MouseButton),
}
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
pub enum MouseButton {
Left,
Middle,
Right,
ScrollUp,
ScrollDown,
ScrollLeft,
ScrollRight,
Forward,
Backward,
}
/// from winit
#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)]
#[repr(u32)]
pub enum VirtualKeyCode {
One,
Two,
Three,
Four,
Five,
Six,
Seven,
Eight,
Nine,
Zero,
A,
B,
C,
D,
E,
F,
G,
H,
I,
J,
K,
L,
M,
N,
O,
P,
Q,
R,
S,
T,
U,
V,
W,
X,
Y,
Z,
/// The Escape key, next to F1.
Escape,
F1,
F2,
F3,
F4,
F5,
F6,
F7,
F8,
F9,
F10,
F11,
F12,
F13,
F14,
F15,
F16,
F17,
F18,
F19,
F20,
F21,
F22,
F23,
F24,
/// Print Screen/SysRq.
Print,
/// Scroll Lock.
Scroll,
/// Pause/Break key, next to Scroll lock.
Pause,
/// `Insert`, next to Backspace.
Insert,
Home,
Delete,
End,
PageDown,
PageUp,
Left,
Up,
Right,
Down,
/// The Backspace key, right over Enter.
// TODO: rename
Back,
/// The Enter key.
Return,
/// The space bar.
Space,
/// The "Compose" key on Linux.
Compose,
Caret,
Numlock,
Numpad0,
Numpad1,
Numpad2,
Numpad3,
Numpad4,
Numpad5,
Numpad6,
Numpad7,
Numpad8,
Numpad9,
NumpadAdd,
NumpadDivide,
NumpadDecimal,
NumpadComma,
NumpadEnter,
NumpadEquals,
NumpadMultiply,
NumpadSubtract,
AbntC1,
AbntC2,
Apostrophe,
Apps,
Asterisk,
At,
Ax,
Backslash,
Calculator,
Capital,
Colon,
Comma,
Convert,
Equals,
Grave,
Kana,
Kanji,
LAlt,
LBracket,
LControl,
LShift,
LWin,
Mail,
MediaSelect,
MediaStop,
Minus,
Mute,
MyComputer,
// also called "Next"
NavigateForward,
// also called "Prior"
NavigateBackward,
NextTrack,
NoConvert,
OEM102,
Period,
PlayPause,
Plus,
Power,
PrevTrack,
RAlt,
RBracket,
RControl,
RShift,
RWin,
Semicolon,
Slash,
Sleep,
Stop,
Sysrq,
Tab,
Underline,
Unlabeled,
VolumeDown,
VolumeUp,
Wake,
WebBack,
WebFavorites,
WebForward,
WebHome,
WebRefresh,
WebSearch,
WebStop,
Yen,
Copy,
Paste,
Cut,
}

28
src/backends/mod.rs Normal file
View file

@ -0,0 +1,28 @@
pub mod keycodes;
pub mod traits;
pub mod window_event;
pub mod xlib;
pub use traits::*;
pub mod structs {
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub enum WindowType {
Splash,
Dialog,
Normal,
Utility,
Menu,
Toolbar,
Dock,
Desktop,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
pub enum Cursor {
Normal,
Resize,
Move,
}
}

61
src/backends/traits.rs Normal file
View file

@ -0,0 +1,61 @@
use super::{
structs::WindowType,
window_event::{self, KeyOrMouseBind},
};
use crate::util::{Point, Size};
pub trait WindowServerBackend {
type Window;
//type WindowEvent = super::window_event::WindowEvent<Self::Window>;
fn build() -> Self;
fn next_event(&mut self) -> window_event::WindowEvent<Self::Window>;
fn handle_event(&mut self, event: window_event::WindowEvent<Self::Window>);
/// adds a keybind to the specified `window`, or globally if `window` is `None`.
/// add global keybind
fn add_keybind(&mut self, keybind: KeyOrMouseBind);
fn remove_keybind(&mut self, keybind: &KeyOrMouseBind);
fn focus_window(&self, window: Self::Window);
fn unfocus_window(&self, window: Self::Window);
fn raise_window(&self, window: Self::Window);
fn set_window_fullscreen_state(
&self,
window: Self::Window,
fullscreen: bool,
);
fn hide_window(&self, window: Self::Window);
fn kill_window(&self, window: Self::Window);
fn get_parent_window(&self, window: Self::Window) -> Option<Self::Window>;
fn configure_window(
&self,
window: Self::Window,
new_size: Option<Size<i32>>,
new_pos: Option<Point<i32>>,
new_border: Option<i32>,
);
fn screen_size(&self) -> Size<i32>;
fn get_window_size(&self, window: Self::Window) -> Option<Size<i32>>;
fn get_window_name(&self, window: Self::Window) -> Option<String>;
fn get_window_type(&self, window: Self::Window) -> WindowType;
fn grab_cursor(&self);
fn ungrab_cursor(&self);
fn move_cursor(&self, window: Option<Self::Window>, position: Point<i32>);
fn all_windows(&self) -> Option<Vec<Self::Window>>;
fn set_active_window_border_color(&mut self, color_name: &str);
fn set_inactive_window_border_color(&mut self, color_name: &str);
fn resize_window(&self, window: Self::Window, new_size: Size<i32>) {
self.configure_window(window, Some(new_size), None, None);
}
fn move_window(&self, window: Self::Window, new_pos: Point<i32>) {
self.configure_window(window, None, Some(new_pos), None);
}
}

View file

@ -0,0 +1,386 @@
#![allow(dead_code)]
use super::{
keycodes::{KeyOrButton, MouseButton, VirtualKeyCode},
structs::WindowType,
};
use crate::util::{Point, Size};
use bitflags::bitflags;
#[derive(Debug, Clone)]
pub enum WindowEvent<Window> {
KeyEvent(KeyEvent<Window>),
ButtonEvent(ButtonEvent<Window>),
MotionEvent(MotionEvent<Window>),
MapRequestEvent(MapEvent<Window>),
MapEvent(MapEvent<Window>),
UnmapEvent(UnmapEvent<Window>),
CreateEvent(CreateEvent<Window>),
DestroyEvent(DestroyEvent<Window>),
EnterEvent(EnterEvent<Window>),
ConfigureEvent(ConfigureEvent<Window>),
FullscreenEvent(FullscreenEvent<Window>), //1 { window: Window, event: 1 },
WindowNameEvent(WindowNameEvent<Window>),
WindowTypeChangedEvent(WindowTypeChangedEvent<Window>),
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub enum KeyState {
Pressed,
Released,
}
#[derive(
Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, serde::Deserialize,
)]
#[repr(u8)]
pub enum ModifierKey {
Shift,
ShiftLock,
Control,
Alt,
AltGr,
/// Windows key on most keyboards
Super,
NumLock,
}
bitflags! {
pub struct ModifierState: u32 {
const SHIFT = 0x01;
const SHIFT_LOCK = 0x010;
const CONTROL = 0x0100;
const ALT = 0x01000;
const ALT_GR = 0x010000;
const SUPER = 0x0100000;
const NUM_LOCK = 0x01000000;
const IGNORE_LOCK = Self::CONTROL.bits | Self::ALT.bits |
Self::ALT_GR.bits | Self::SUPER.bits| Self::SHIFT.bits;
}
}
impl<const N: usize> From<[ModifierKey; N]> for ModifierState {
fn from(slice: [ModifierKey; N]) -> Self {
let mut state = ModifierState::empty();
for ele in slice {
state.insert_mod(ele);
}
state
}
}
impl ModifierState {
pub fn eq_ignore_lock(&self, rhs: &Self) -> bool {
let mask = Self::IGNORE_LOCK;
*self & mask == *rhs & mask
}
pub fn with_mod(mut self, modifier: ModifierKey) -> Self {
self.insert_mod(modifier);
self
}
pub fn unset_mod(&mut self, modifier: ModifierKey) {
self.set_mod(modifier, false);
}
pub fn set_mod(&mut self, modifier: ModifierKey, state: bool) {
self.set(
match modifier {
ModifierKey::Shift => Self::SHIFT,
ModifierKey::ShiftLock => Self::SHIFT_LOCK,
ModifierKey::Control => Self::CONTROL,
ModifierKey::Alt => Self::ALT,
ModifierKey::AltGr => Self::ALT_GR,
ModifierKey::Super => Self::SUPER,
ModifierKey::NumLock => Self::NUM_LOCK,
},
state,
);
}
pub fn insert_mod(&mut self, modifier: ModifierKey) {
self.set_mod(modifier, true);
}
}
impl Into<u8> for ModifierKey {
fn into(self) -> u8 {
self as u8
}
}
#[derive(Debug, Clone)]
pub struct KeyEvent<Window> {
pub window: Window,
pub state: KeyState,
pub keycode: VirtualKeyCode,
pub modifierstate: ModifierState,
}
impl<Window> KeyEvent<Window> {
pub fn new(
window: Window,
state: KeyState,
keycode: VirtualKeyCode,
modifierstate: ModifierState,
) -> Self {
Self {
window,
state,
keycode,
modifierstate,
}
}
}
#[derive(Debug, Clone)]
pub struct ButtonEvent<Window> {
pub window: Window,
pub state: KeyState,
pub keycode: MouseButton,
pub cursor_position: Point<i32>,
pub modifierstate: ModifierState,
}
impl<Window> ButtonEvent<Window> {
pub fn new(
window: Window,
state: KeyState,
keycode: MouseButton,
cursor_position: Point<i32>,
modifierstate: ModifierState,
) -> Self {
Self {
window,
state,
keycode,
cursor_position,
modifierstate,
}
}
}
#[derive(Debug, Clone)]
pub struct MotionEvent<Window> {
pub position: Point<i32>,
pub window: Window,
}
impl<Window> MotionEvent<Window> {
pub fn new(position: Point<i32>, window: Window) -> Self {
Self { position, window }
}
}
#[derive(Debug, Clone)]
pub struct MapEvent<Window> {
pub window: Window,
}
#[derive(Debug, Clone)]
pub struct UnmapEvent<Window> {
pub window: Window,
}
#[derive(Debug, Clone)]
pub struct EnterEvent<Window> {
pub window: Window,
}
#[derive(Debug, Clone)]
pub struct DestroyEvent<Window> {
pub window: Window,
}
impl<Window> DestroyEvent<Window> {
pub fn new(window: Window) -> Self {
Self { window }
}
}
#[derive(Debug, Clone)]
pub struct CreateEvent<Window> {
pub window: Window,
pub position: Point<i32>,
pub size: Size<i32>,
}
impl<Window> CreateEvent<Window> {
pub fn new(window: Window, position: Point<i32>, size: Size<i32>) -> Self {
Self {
window,
position,
size,
}
}
}
#[derive(Debug, Clone)]
pub struct ConfigureEvent<Window> {
pub window: Window,
pub position: Point<i32>,
pub size: Size<i32>,
}
impl<Window> ConfigureEvent<Window> {
pub fn new(window: Window, position: Point<i32>, size: Size<i32>) -> Self {
Self {
window,
position,
size,
}
}
}
#[derive(Debug, Clone)]
pub enum FullscreenState {
On,
Off,
Toggle,
}
impl From<bool> for FullscreenState {
fn from(value: bool) -> Self {
match value {
true => Self::On,
false => Self::Off,
}
}
}
#[derive(Debug, Clone)]
pub struct FullscreenEvent<Window> {
pub window: Window,
pub state: FullscreenState,
}
impl<Window> FullscreenEvent<Window> {
pub fn new(window: Window, state: FullscreenState) -> Self {
Self { window, state }
}
}
#[derive(Debug, Clone)]
pub struct WindowNameEvent<Window> {
pub window: Window,
pub name: String,
}
impl<Window> WindowNameEvent<Window> {
pub fn new(window: Window, name: String) -> Self {
Self { window, name }
}
}
#[derive(Debug, Clone)]
pub struct WindowTypeChangedEvent<Window> {
pub window: Window,
pub window_type: WindowType,
}
impl<Window> WindowTypeChangedEvent<Window> {
pub fn new(window: Window, window_type: WindowType) -> Self {
Self {
window,
window_type,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct KeyBind {
pub key: VirtualKeyCode,
pub modifiers: ModifierState,
}
impl KeyBind {
pub fn new(key: VirtualKeyCode) -> Self {
Self {
key,
modifiers: ModifierState::empty(),
}
}
pub fn with_mod(mut self, modifier_key: ModifierKey) -> Self {
self.modifiers.insert_mod(modifier_key);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct MouseBind {
pub button: MouseButton,
pub modifiers: ModifierState,
}
impl MouseBind {
pub fn new(button: MouseButton) -> Self {
Self {
button,
modifiers: ModifierState::empty(),
}
}
pub fn with_mod(mut self, modifier_key: ModifierKey) -> Self {
self.modifiers.insert_mod(modifier_key);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct KeyOrMouseBind {
pub key: KeyOrButton,
pub modifiers: ModifierState,
}
impl KeyOrMouseBind {
pub fn new(key: KeyOrButton) -> Self {
Self {
key,
modifiers: ModifierState::empty(),
}
}
pub fn with_mod(mut self, modifier_key: ModifierKey) -> Self {
self.modifiers.insert_mod(modifier_key);
self
}
}
impl From<&KeyBind> for KeyOrMouseBind {
fn from(keybind: &KeyBind) -> Self {
Self {
key: KeyOrButton::Key(keybind.key),
modifiers: keybind.modifiers,
}
}
}
impl From<KeyBind> for KeyOrMouseBind {
fn from(keybind: KeyBind) -> Self {
Self {
key: KeyOrButton::Key(keybind.key),
modifiers: keybind.modifiers,
}
}
}
impl From<&MouseBind> for KeyOrMouseBind {
fn from(mousebind: &MouseBind) -> Self {
Self {
key: KeyOrButton::Button(mousebind.button),
modifiers: mousebind.modifiers,
}
}
}
impl From<MouseBind> for KeyOrMouseBind {
fn from(mousebind: MouseBind) -> Self {
Self {
key: KeyOrButton::Button(mousebind.button),
modifiers: mousebind.modifiers,
}
}
}

View file

@ -0,0 +1,46 @@
use std::mem::MaybeUninit;
use x11::{xft, xlib};
use super::Display;
pub struct XftColor {
inner: xft::XftColor,
}
impl XftColor {
pub fn pixel(&self) -> u64 {
self.inner.pixel
}
#[allow(dead_code)]
pub fn color(&self) -> x11::xrender::XRenderColor {
self.inner.color
}
pub fn new(
dpy: Display,
screen: i32,
mut color_name: String,
) -> Result<Self, std::io::Error> {
color_name.push('\0');
let mut color = MaybeUninit::<xft::XftColor>::zeroed();
unsafe {
xft::XftColorAllocName(
dpy.get(),
xlib::XDefaultVisual(dpy.get(), screen),
xlib::XDefaultColormap(dpy.get(), screen),
color_name.as_ptr() as *mut _,
color.as_mut_ptr(),
) != 0
}
.then(|| Self {
inner: unsafe { color.assume_init() },
})
.ok_or(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Unable to allocate color.",
))
}
}

2108
src/backends/xlib/keysym.rs Normal file

File diff suppressed because it is too large Load diff

1877
src/backends/xlib/mod.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,31 +1,41 @@
use std::num::NonZeroI32;
use std::{ops::Rem, usize}; use std::{ops::Rem, usize};
use indexmap::IndexMap; use indexmap::IndexMap;
use log::{error, info}; use log::error;
use num_traits::Zero;
use crate::backends::structs::WindowType;
use crate::util::BuildIdentityHasher; use crate::util::BuildIdentityHasher;
use crate::util::{Point, Size};
mod client { mod client {
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use crate::{
backends::structs::WindowType,
util::{Point, Size},
};
use x11::xlib::Window; use x11::xlib::Window;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Client { pub struct Client {
pub(crate) window: Window, pub(crate) window: Window,
pub(crate) size: (i32, i32), pub(crate) size: Size<i32>,
pub(crate) position: (i32, i32), pub(crate) position: Point<i32>,
pub(crate) transient_for: Option<Window>, pub(crate) parent_window: Option<Window>,
pub(crate) window_type: WindowType,
pub(crate) fullscreen: bool,
} }
impl Default for Client { impl Default for Client {
fn default() -> Self { fn default() -> Self {
Self { Self {
window: 0, window: 0,
size: (100, 100), size: (100, 100).into(),
position: (0, 0), position: (0, 0).into(),
transient_for: None, parent_window: None,
fullscreen: false,
window_type: WindowType::Normal,
} }
} }
} }
@ -34,26 +44,26 @@ mod client {
#[allow(dead_code)] #[allow(dead_code)]
pub fn new( pub fn new(
window: Window, window: Window,
size: (i32, i32), size: Size<i32>,
position: (i32, i32), position: Point<i32>,
) -> Self { ) -> Self {
Self { Self {
window, window,
size, size,
position, position,
transient_for: None, ..Self::default()
} }
} }
pub fn new_transient( pub fn new_dialog(
window: Window, window: Window,
size: (i32, i32), size: Size<i32>,
transient: Window, transient: Window,
) -> Self { ) -> Self {
Self { Self {
window, window,
size, size,
transient_for: Some(transient), parent_window: Some(transient),
..Default::default() ..Default::default()
} }
} }
@ -65,8 +75,47 @@ mod client {
} }
} }
pub fn is_transient(&self) -> bool { pub fn with_window_type(self, window_type: WindowType) -> Self {
self.transient_for.is_some() Self {
window_type,
..self
}
}
pub fn with_parent_window(self, parent_window: Option<Window>) -> Self {
Self {
parent_window,
..self
}
}
pub fn with_size(self, size: Size<i32>) -> Self {
Self { size, ..self }
}
/// toggles the clients fullscreen flag.
/// returns `true` if the client is now fullscreen.
pub fn toggle_fullscreen(&mut self) -> bool {
self.fullscreen = !self.fullscreen;
self.is_fullscreen()
}
pub fn set_fullscreen(&mut self, fullscreen: bool) -> bool {
if self.fullscreen == fullscreen {
false
} else {
self.fullscreen = fullscreen;
true
}
}
pub fn is_fullscreen(&self) -> bool {
self.fullscreen
}
pub fn has_parent_window(&self) -> bool {
self.parent_window.is_some()
} }
} }
@ -140,7 +189,7 @@ pub struct ClientState {
pub(self) virtual_screens: VirtualScreenStore, pub(self) virtual_screens: VirtualScreenStore,
pub(self) gap: i32, pub(self) gap: i32,
pub(self) screen_size: (i32, i32), pub(self) screen_size: Size<i32>,
pub(self) master_size: f32, pub(self) master_size: f32,
border_size: i32, border_size: i32,
} }
@ -166,7 +215,7 @@ impl Default for ClientState {
focused: None, focused: None,
virtual_screens: VirtualScreenStore::new(1), virtual_screens: VirtualScreenStore::new(1),
gap: 0, gap: 0,
screen_size: (1, 1), screen_size: (1, 1).into(),
master_size: 1.0, master_size: 1.0,
border_size: 0, border_size: 0,
} }
@ -189,7 +238,7 @@ impl ClientState {
} }
} }
pub fn with_screen_size(self, screen_size: (i32, i32)) -> Self { pub fn with_screen_size(self, screen_size: Size<i32>) -> Self {
Self { Self {
screen_size, screen_size,
..self ..self
@ -207,6 +256,7 @@ impl ClientState {
self.border_size self.border_size
} }
#[allow(dead_code)]
pub fn set_border_mut(&mut self, new: i32) { pub fn set_border_mut(&mut self, new: i32) {
self.border_size = new; self.border_size = new;
} }
@ -214,26 +264,42 @@ impl ClientState {
pub fn insert(&mut self, mut client: Client) -> Option<&Client> { pub fn insert(&mut self, mut client: Client) -> Option<&Client> {
let key = client.key(); let key = client.key();
if client.is_transient() match client.window_type {
&& self.contains(&client.transient_for.unwrap()) // idk how to handle docks and desktops, for now they float innit
WindowType::Splash
| WindowType::Dialog
| WindowType::Utility
| WindowType::Menu
| WindowType::Toolbar
| WindowType::Dock
| WindowType::Desktop => {
if let Some(parent) = client
.parent_window
.and_then(|window| self.get(&window).into_option())
{ {
let transient = self.get(&client.transient_for.unwrap()).unwrap();
client.position = { client.position = {
( (
transient.position.0 parent.position.x
+ (transient.size.0 - client.size.0) / 2, + (parent.size.width - client.size.width) / 2,
transient.position.1 parent.position.y
+ (transient.size.1 - client.size.1) / 2, + (parent.size.height - client.size.height) / 2,
) )
.into()
}; };
}
client.size = client.size.clamp(
self.screen_size
- Size::new(self.border_size * 2, self.border_size * 2),
);
self.floating_clients.insert(key, client); self.floating_clients.insert(key, client);
} else { }
WindowType::Normal => {
self.clients.insert(key, client); self.clients.insert(key, client);
self.virtual_screens.get_mut_current().insert(&key); self.virtual_screens.get_mut_current().insert(&key);
} }
}
// adding a client changes the liling layout, rearrange // adding a client changes the liling layout, rearrange
self.arrange_virtual_screen(); self.arrange_virtual_screen();
@ -283,7 +349,7 @@ impl ClientState {
.filter(move |&(k, _)| self.is_client_visible(k)) .filter(move |&(k, _)| self.is_client_visible(k))
} }
fn iter_all_clients(&self) -> impl Iterator<Item = (&u64, &Client)> { pub fn iter_all_clients(&self) -> impl Iterator<Item = (&u64, &Client)> {
self.floating_clients.iter().chain(self.clients.iter()) self.floating_clients.iter().chain(self.clients.iter())
} }
@ -293,7 +359,15 @@ impl ClientState {
} }
pub fn iter_transient(&self) -> impl Iterator<Item = (&u64, &Client)> { pub fn iter_transient(&self) -> impl Iterator<Item = (&u64, &Client)> {
self.iter_floating().filter(|&(_, c)| c.is_transient()) self.iter_floating().filter(|&(_, c)| c.has_parent_window())
}
pub fn iter_by_window_type(
&self,
window_type: WindowType,
) -> impl Iterator<Item = (&u64, &Client)> {
self.iter_floating()
.filter(move |&(_, c)| c.window_type == window_type)
} }
pub fn iter_visible(&self) -> impl Iterator<Item = (&u64, &Client)> { pub fn iter_visible(&self) -> impl Iterator<Item = (&u64, &Client)> {
@ -330,7 +404,7 @@ impl ClientState {
{ {
match self.get(key) { match self.get(key) {
ClientEntry::Floating(c) => { ClientEntry::Floating(c) => {
if let Some(transient_for) = c.transient_for { if let Some(transient_for) = c.parent_window {
self.is_client_visible(&transient_for) self.is_client_visible(&transient_for)
} else { } else {
true true
@ -403,6 +477,49 @@ impl ClientState {
self.arrange_virtual_screen(); self.arrange_virtual_screen();
} }
pub fn set_fullscreen<K>(&mut self, key: &K, fullscreen: bool) -> bool
where
K: ClientKey,
{
self.get(key)
.into_option()
.map(|client| client.is_fullscreen() != fullscreen)
.unwrap_or(false)
.then(|| self.toggle_fullscreen(key))
.unwrap_or(false)
}
/// returns `true` if window layout changed
pub fn toggle_fullscreen<K>(&mut self, key: &K) -> bool
where
K: ClientKey,
{
if let Some(_new_fullscreen_state) = self.inner_toggle_fullscreen(key) {
self.arrange_virtual_screen();
true
} else {
false
}
}
fn inner_toggle_fullscreen<K>(&mut self, key: &K) -> Option<bool>
where
K: ClientKey,
{
let fullscreen_size = self.screen_size;
self.get_mut(key).into_option().map(|client| {
if client.toggle_fullscreen() {
client.size = fullscreen_size;
client.position = Point::zero();
true
} else {
false
}
})
}
/** /**
Sets a tiled client to floating and returns true, does nothing for a floating client and Sets a tiled client to floating and returns true, does nothing for a floating client and
returns false. If this function returns `true` you have to call `arrange_clients` after. returns false. If this function returns `true` you have to call `arrange_clients` after.
@ -420,6 +537,23 @@ impl ClientState {
} }
} }
/**
Sets a floating client to tiled and returns true, does nothing for a tiled client and
returns false. If this function returns `true` you have to call `arrange_clients` after.
*/
pub fn set_tiled<K>(&mut self, key: &K) -> bool
where
K: ClientKey,
{
if self.get(key).is_floating() {
self.toggle_floating(key);
true
} else {
false
}
}
/** /**
This function invalidates the tiling, call `arrange_clients` to fix it again (it doesn't do it This function invalidates the tiling, call `arrange_clients` to fix it again (it doesn't do it
automatically since xlib has to move and resize all windows anyways). automatically since xlib has to move and resize all windows anyways).
@ -427,6 +561,16 @@ impl ClientState {
pub fn toggle_floating<K>(&mut self, key: &K) pub fn toggle_floating<K>(&mut self, key: &K)
where where
K: ClientKey, K: ClientKey,
{
// do nothing if either no client matches the key or the client is fullscreen.
// FIXME: this should probably disable fullscreen mode (but that has to
// be handled in the wm state so that the backend can notify the client
// that it is no longer fullscreen)
if !self
.get(key)
.into_option()
.map(|c| c.is_fullscreen())
.unwrap_or(true)
{ {
let key = key.key(); let key = key.key();
let client = self.clients.remove(&key); let client = self.clients.remove(&key);
@ -439,25 +583,42 @@ impl ClientState {
} }
(None, Some(floating_client)) => { (None, Some(floating_client)) => {
// transient clients cannot be tiled // transient clients cannot be tiled
match floating_client.is_transient() { // only normal windows can be tiled
true => { match floating_client.window_type {
self.floating_clients.insert(key, floating_client); WindowType::Normal => {
}
false => {
self.clients.insert(key, floating_client); self.clients.insert(key, floating_client);
self.virtual_screens.get_mut_current().insert(&key); self.virtual_screens.get_mut_current().insert(&key);
} }
_ => {
self.floating_clients.insert(key, floating_client);
}
} }
} }
_ => { _ => {
error!("wtf? Client was present in tiled and floating list.") error!(
"wtf? Client was present in tiled and floating list."
)
} }
}; };
// we added or removed a client from the tiling so the layout changed, rearrange // we added or removed a client from the tiling so the layout changed, rearrange
self.arrange_virtual_screen(); self.arrange_virtual_screen();
} }
}
pub fn update_window_type<K>(&mut self, key: &K, window_type: WindowType)
where
K: ClientKey,
{
if let Some(client) = self.get_mut(key).into_option() {
client.window_type = window_type;
match window_type {
WindowType::Normal => self.set_floating(key),
_ => self.set_tiled(key),
};
}
}
fn remove_from_virtual_screens<K>(&mut self, key: &K) fn remove_from_virtual_screens<K>(&mut self, key: &K)
where where
@ -532,7 +693,9 @@ impl ClientState {
{ {
let (new, old) = self.focus_client_inner(key); let (new, old) = self.focus_client_inner(key);
info!("Swapping focus: new({:?}) old({:?})", new, old); if !(new.is_vacant() && old.is_vacant()) {
//info!("Swapping focus: new({:?}) old({:?})", new, old);
}
(new, old) (new, old)
} }
@ -622,54 +785,89 @@ impl ClientState {
*/ */
pub fn arrange_virtual_screen(&mut self) { pub fn arrange_virtual_screen(&mut self) {
let gap = self.gap; let gap = self.gap;
let (width, height) = self.screen_size; let (width, height) = self.screen_size.as_tuple();
// should be fine to unwrap since we will always have at least 1 virtual screen // should be fine to unwrap since we will always have at least 1 virtual screen
let vs = self.virtual_screens.get_mut_current(); let vs = self.virtual_screens.get_mut_current();
// if aux is empty -> width : width / 2 // if aux is empty -> width : width / 2
let (master_width, aux_width) = { let vs_width = width - gap * 2;
let effective_width = width - gap * 2;
let master_size = if vs.aux.is_empty() { let master_position = Point::new(0, 0);
let master_window_size = {
let factor = if vs.aux.is_empty() {
1.0 1.0
} else { } else {
self.master_size / 2.0 self.master_size / 2.0
}; };
let master_width = (effective_width as f32 * master_size) as i32; let width = (vs_width as f32 * factor) as i32;
let aux_width = effective_width - master_width;
(master_width, aux_width)
};
// make sure we dont devide by 0 // make sure we dont devide by 0
// height is max height / number of clients in the stack // height is max height / number of clients in the stack
let master_height = (height - gap * 2) let height = match vs.master.len() as i32 {
/ match NonZeroI32::new(vs.master.len() as i32) { 0 => 1,
Some(i) => i.get(), n => (height - gap * 2) / n,
None => 1,
}; };
// height is max height / number of clients in the stack Size::new(width, height)
let aux_height = (height - gap * 2)
/ match NonZeroI32::new(vs.aux.len() as i32) {
Some(i) => i.get(),
None => 1,
}; };
let aux_position = Point::new(master_window_size.width, 0);
let aux_window_size = {
let width = vs_width - master_window_size.width;
// make sure we dont devide by 0
// height is max height / number of clients in the stack
let height = match vs.aux.len() as i32 {
0 => 1,
n => (height - gap * 2) / n,
};
Size::new(width, height)
};
fn calculate_window_dimensions(
screen_size: Size<i32>,
stack_size: Size<i32>,
stack_position: Point<i32>,
fullscreen: bool,
nth: i32,
gap: i32,
border: i32,
) -> (Size<i32>, Point<i32>) {
if fullscreen {
let size = Size::new(screen_size.width, screen_size.height);
let pos = Point::new(0, 0);
(size, pos)
} else {
let size = Size::new(
stack_size.width - gap * 2 - border * 2,
stack_size.height - gap * 2 - border * 2,
);
let pos = Point::new(
stack_position.x + gap * 2,
stack_position.y + stack_size.height * nth + gap * 2,
);
(size, pos)
}
}
// Master // Master
for (i, key) in vs.master.iter().enumerate() { for (i, key) in vs.master.iter().enumerate() {
let size = ( if let Some(client) = self.clients.get_mut(key) {
master_width - gap * 2 - self.border_size * 2, let (size, position) = calculate_window_dimensions(
master_height - gap * 2 - self.border_size * 2, self.screen_size.into(),
master_window_size,
master_position,
client.is_fullscreen(),
i as i32,
gap,
self.border_size,
); );
let position = (gap * 2, master_height * i as i32 + gap * 2);
if let Some(client) = self.clients.get_mut(key) {
*client = Client { *client = Client {
size, size: size.into(),
position, position,
..*client ..*client
}; };
@ -678,17 +876,19 @@ impl ClientState {
// Aux // Aux
for (i, key) in vs.aux.iter().enumerate() { for (i, key) in vs.aux.iter().enumerate() {
let size = ( if let Some(client) = self.clients.get_mut(key) {
aux_width - gap * 2 - self.border_size * 2, let (size, position) = calculate_window_dimensions(
aux_height - gap * 2 - self.border_size * 2, self.screen_size.into(),
aux_window_size,
aux_position,
client.is_fullscreen(),
i as i32,
gap,
self.border_size,
); );
let position =
(master_width + gap * 2, aux_height * i as i32 + gap * 2);
if let Some(client) = self.clients.get_mut(key) {
*client = Client { *client = Client {
size, size: size.into(),
position, position,
..*client ..*client
}; };
@ -851,7 +1051,7 @@ impl VirtualScreenStore {
fn go_to_nth(&mut self, n: usize) -> usize { fn go_to_nth(&mut self, n: usize) -> usize {
self.last_idx = Some(self.current_idx); self.last_idx = Some(self.current_idx);
self.current_idx = n % self.screens.len(); self.current_idx = n.min(self.screens.len() - 1);
self.current_idx self.current_idx
} }
@ -902,3 +1102,25 @@ impl<T> ClientEntry<T> {
!self.is_vacant() !self.is_vacant()
} }
} }
impl ClientEntry<&client::Client> {
pub fn is_fullscreen(&self) -> bool {
match self {
ClientEntry::Tiled(c) | ClientEntry::Floating(c) => {
c.is_fullscreen()
}
ClientEntry::Vacant => false,
}
}
}
impl ClientEntry<&mut client::Client> {
pub fn is_fullscreen(&self) -> bool {
match self {
ClientEntry::Tiled(c) | ClientEntry::Floating(c) => {
c.is_fullscreen()
}
ClientEntry::Vacant => false,
}
}
}

26
src/lib.rs Normal file
View file

@ -0,0 +1,26 @@
#![deny(unsafe_op_in_unsafe_fn)]
pub mod backends;
pub mod clients;
pub mod state;
pub mod util;
pub mod error {
use thiserror::Error;
#[derive(Debug, Error)]
pub enum Error {
#[error("placeholder error for Result<T> as Option<T>")]
NonError,
#[error("Unknown Event")]
UnknownEvent,
#[error("Unhandled VirtualKeyCode")]
UnhandledVirtualKeyCode,
#[error(transparent)]
IoError(#[from] std::io::Error),
#[error(transparent)]
FmtError(#[from] std::fmt::Error),
#[error(transparent)]
XlibError(#[from] crate::backends::xlib::XlibError),
}
}

View file

@ -1,3 +1,5 @@
use std::io::Read;
use log::{debug, error, info, trace, warn}; use log::{debug, error, info, trace, warn};
use log4rs::{ use log4rs::{
append::{console::ConsoleAppender, file::FileAppender}, append::{console::ConsoleAppender, file::FileAppender},
@ -5,13 +7,8 @@ use log4rs::{
encode::pattern::PatternEncoder, encode::pattern::PatternEncoder,
Config, Config,
}; };
use state::WMConfig;
mod clients; use wm::state::WMConfig;
mod clients2;
mod state;
mod util;
mod xlib;
fn init_logger() { fn init_logger() {
let encoder = Box::new(PatternEncoder::new( let encoder = Box::new(PatternEncoder::new(
@ -24,7 +21,7 @@ fn init_logger() {
let _logfile = FileAppender::builder() let _logfile = FileAppender::builder()
.encoder(encoder) .encoder(encoder)
.build(home.join(".local/portlights.log")) .build(home.join(".local/nirgendwm.log"))
.unwrap(); .unwrap();
let config = Config::builder() let config = Config::builder()
@ -34,7 +31,7 @@ fn init_logger() {
Root::builder() Root::builder()
.appender("stdout") .appender("stdout")
//.appender("logfile") //.appender("logfile")
.build(log::LevelFilter::Info), .build(log::LevelFilter::Debug),
) )
.unwrap(); .unwrap();
@ -46,7 +43,23 @@ fn main() {
log_prologue(); log_prologue();
state::WindowManager::new(WMConfig::default()).run(); let mut config_path = std::path::PathBuf::from(env!("HOME"));
config_path.push(".config/nirgendwm.toml");
let config = std::fs::File::open(config_path)
.and_then(|mut file| {
let mut content = String::new();
file.read_to_string(&mut content)?;
Ok(content)
})
.and_then(|content| Ok(toml::from_str::<WMConfig>(&content)?))
.unwrap_or_else(|e| {
warn!("error parsing config file: {}", e);
info!("falling back to default config.");
WMConfig::default()
});
wm::state::WindowManager::<wm::backends::xlib::XLib>::new(config).run();
} }
fn log_prologue() { fn log_prologue() {
@ -65,7 +78,6 @@ fn log_prologue() {
#[test] #[test]
fn test_logger() { fn test_logger() {
init_logger(); init_logger();
// asdf
log_prologue(); log_prologue();
} }

File diff suppressed because it is too large Load diff

View file

@ -22,3 +22,225 @@ impl Hasher for IdentityHasher {
} }
pub type BuildIdentityHasher = BuildHasherDefault<IdentityHasher>; pub type BuildIdentityHasher = BuildHasherDefault<IdentityHasher>;
pub use point::Point;
pub use size::Size;
mod size {
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct Size<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
pub width: I,
pub height: I,
}
impl<I> std::ops::Add for Size<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
width: self.width + rhs.width,
height: self.height + rhs.height,
}
}
}
impl<I> std::ops::Sub for Size<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
width: self.width - rhs.width,
height: self.height - rhs.height,
}
}
}
impl<I> num_traits::Zero for Size<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
fn zero() -> Self {
Self::default()
}
fn is_zero(&self) -> bool {
self.width == I::zero() && self.height == I::zero()
}
}
impl<I> Default for Size<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
fn default() -> Self {
Self {
width: I::zero(),
height: I::zero(),
}
}
}
impl<I> From<(I, I)> for Size<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
fn from(value: (I, I)) -> Self {
Self::from_tuple(value)
}
}
impl<I> From<super::point::Point<I>> for Size<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
fn from(value: super::point::Point<I>) -> Self {
Self::new(value.x, value.y)
}
}
impl<I> Size<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
pub fn new(width: I, height: I) -> Self {
Self { width, height }
}
pub fn from_tuple(tuple: (I, I)) -> Self {
Self::new(tuple.0, tuple.1)
}
pub fn as_tuple(&self) -> (I, I) {
(self.width, self.height)
}
pub fn clamp(self, other: Self) -> Self {
Self::new(
self.width.min(other.width),
self.height.min(other.height),
)
}
pub fn map<F>(self, f: F) -> Self
where
F: FnOnce(I, I) -> Self,
{
f(self.width, self.height)
}
}
}
mod point {
#[derive(Debug, Copy, Clone, PartialEq, PartialOrd)]
pub struct Point<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
pub x: I,
pub y: I,
}
impl<I> std::ops::Add for Point<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl<I> std::ops::Sub for Point<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl<I> num_traits::Zero for Point<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
fn zero() -> Self {
Self::default()
}
fn is_zero(&self) -> bool {
self.x == I::zero() && self.y == I::zero()
}
}
impl<I> Default for Point<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
fn default() -> Self {
Self {
x: I::zero(),
y: I::zero(),
}
}
}
impl<I> From<(I, I)> for Point<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
fn from(value: (I, I)) -> Self {
Self::from_tuple(value)
}
}
impl<I> From<super::size::Size<I>> for Point<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
fn from(value: super::size::Size<I>) -> Self {
Self::new(value.width, value.height)
}
}
impl<I> Point<I>
where
I: num_traits::PrimInt + num_traits::Zero,
{
pub fn new(x: I, y: I) -> Self {
Self { x, y }
}
pub fn from_tuple(tuple: (I, I)) -> Self {
Self::new(tuple.0, tuple.1)
}
pub fn as_tuple(&self) -> (I, I) {
(self.x, self.y)
}
pub fn map<F, T>(self, f: F) -> T
where
F: FnOnce(I, I) -> T,
{
f(self.x, self.y)
}
}
}

View file

@ -1,765 +0,0 @@
use log::info;
use std::ptr::{null, null_mut};
use std::{ffi::CString, rc::Rc};
use x11::xlib::{
self, AnyButton, AnyKey, AnyModifier, Atom, ButtonPressMask,
ButtonReleaseMask, CWEventMask, ControlMask, CurrentTime, EnterWindowMask,
FocusChangeMask, LockMask, Mod1Mask, Mod2Mask, Mod3Mask, Mod4Mask,
Mod5Mask, PointerMotionMask, PropertyChangeMask, ShiftMask,
StructureNotifyMask, SubstructureNotifyMask, SubstructureRedirectMask,
Window, XCloseDisplay, XConfigureRequestEvent, XDefaultScreen, XEvent,
XGetTransientForHint, XGrabPointer, XInternAtom, XKillClient, XMapWindow,
XOpenDisplay, XRaiseWindow, XRootWindow, XSetErrorHandler, XSync,
XUngrabButton, XUngrabKey, XUngrabPointer, XWarpPointer, XWindowAttributes,
};
use xlib::GrabModeAsync;
use log::error;
use crate::clients::Client;
pub struct XLib {
display: Display,
root: Window,
_screen: i32,
atoms: Atoms,
global_keybinds: Vec<KeyOrButton>,
}
struct Atoms {
protocols: Atom,
delete_window: Atom,
active_window: Atom,
take_focus: Atom,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KeyOrButton {
Key(i32, u32),
Button(u32, u32, u64),
}
impl KeyOrButton {
#[allow(dead_code)]
pub fn key(keycode: i32, modmask: u32) -> Self {
Self::Key(keycode, modmask)
}
pub fn button(button: u32, modmask: u32, buttonmask: i64) -> Self {
Self::Button(button, modmask, buttonmask as u64)
}
}
#[derive(Clone)]
pub struct Display(Rc<*mut xlib::Display>);
impl XLib {
pub fn new() -> Self {
let (display, _screen, root) = unsafe {
let display = XOpenDisplay(null());
assert_ne!(display, null_mut());
let display = Display::new(display);
let screen = XDefaultScreen(display.get());
let root = XRootWindow(display.get(), screen);
(display, screen, root)
};
Self {
atoms: Atoms::init(display.clone()),
global_keybinds: Vec::new(),
root,
_screen,
display,
}
}
pub fn init(&mut self) {
unsafe {
let mut window_attributes =
std::mem::MaybeUninit::<xlib::XSetWindowAttributes>::zeroed()
.assume_init();
window_attributes.event_mask = SubstructureRedirectMask
| StructureNotifyMask
| SubstructureNotifyMask
| EnterWindowMask
| PointerMotionMask
| ButtonPressMask;
xlib::XChangeWindowAttributes(
self.dpy(),
self.root,
CWEventMask,
&mut window_attributes,
);
xlib::XSelectInput(
self.dpy(),
self.root,
window_attributes.event_mask,
);
XSetErrorHandler(Some(xlib_error_handler));
XSync(self.dpy(), 0);
}
self.grab_global_keybinds(self.root);
}
pub fn add_global_keybind(&mut self, key: KeyOrButton) {
self.global_keybinds.push(key);
}
#[allow(dead_code)]
fn ungrab_global_keybings(&self, window: Window) {
unsafe {
XUngrabButton(self.dpy(), AnyButton as u32, AnyModifier, window);
XUngrabKey(self.dpy(), AnyKey, AnyModifier, window);
}
}
fn grab_global_keybinds(&self, window: Window) {
for kb in self.global_keybinds.iter() {
self.grab_key_or_button(window, kb);
}
}
#[allow(dead_code)]
pub fn remove_global_keybind(&mut self, key: &KeyOrButton) {
if self.global_keybinds.contains(key) {
self.global_keybinds.retain(|kb| kb != key);
}
}
fn dpy(&self) -> *mut xlib::Display {
self.display.get()
}
#[allow(dead_code)]
pub fn squash_event(&self, event_type: i32) -> XEvent {
unsafe {
let mut event =
std::mem::MaybeUninit::<xlib::XEvent>::zeroed().assume_init();
while xlib::XCheckTypedEvent(self.dpy(), event_type, &mut event)
!= 0
{}
event
}
}
pub fn next_event(&self) -> XEvent {
unsafe {
let mut event =
std::mem::MaybeUninit::<xlib::XEvent>::zeroed().assume_init();
xlib::XNextEvent(self.dpy(), &mut event);
event
}
}
pub fn grab_key_or_button(&self, window: Window, key: &KeyOrButton) {
let numlock_mask = self.get_numlock_mask();
let modifiers =
vec![0, LockMask, numlock_mask, LockMask | numlock_mask];
for modifier in modifiers.iter() {
match key {
&KeyOrButton::Key(keycode, modmask) => {
unsafe {
xlib::XGrabKey(
self.dpy(),
keycode,
modmask | modifier,
window,
1, /* true */
GrabModeAsync,
GrabModeAsync,
);
}
}
&KeyOrButton::Button(button, modmask, buttonmask) => {
unsafe {
xlib::XGrabButton(
self.dpy(),
button,
modmask | modifier,
window,
1, /*true */
buttonmask as u32,
GrabModeAsync,
GrabModeAsync,
0,
0,
);
}
}
}
}
}
pub fn focus_client(&self, client: &Client) {
unsafe {
xlib::XSetInputFocus(
self.dpy(),
client.window,
xlib::RevertToPointerRoot,
xlib::CurrentTime,
);
let screen = xlib::XDefaultScreenOfDisplay(self.dpy()).as_ref();
if let Some(screen) = screen {
xlib::XSetWindowBorder(
self.dpy(),
client.window,
screen.white_pixel,
);
}
xlib::XChangeProperty(
self.dpy(),
self.root,
self.atoms.active_window,
xlib::XA_WINDOW,
32,
xlib::PropModeReplace,
&client.window as *const u64 as *const _,
1,
);
}
self.send_protocol(client, self.atoms.take_focus);
}
pub fn unfocus_client(&self, client: &Client) {
//info!("unfocusing client: {:?}", client);
unsafe {
xlib::XSetInputFocus(
self.dpy(),
self.root,
xlib::RevertToPointerRoot,
xlib::CurrentTime,
);
let screen = xlib::XDefaultScreenOfDisplay(self.dpy()).as_ref();
if let Some(screen) = screen {
xlib::XSetWindowBorder(
self.dpy(),
client.window,
screen.black_pixel,
);
}
// xlib::XDeleteProperty(
// self.dpy(),
// self.root,
// self.atoms.active_window,
// );
}
}
pub fn move_resize_client(&self, client: &Client) {
let mut windowchanges = xlib::XWindowChanges {
x: client.position.0,
y: client.position.1,
width: client.size.0,
height: client.size.1,
border_width: 0,
sibling: 0,
stack_mode: 0,
};
if client.size.0 < 1 || client.size.1 < 1 {
error!("client {:?} size is less than 1 pixel!", client);
} else {
unsafe {
xlib::XConfigureWindow(
self.dpy(),
client.window,
(xlib::CWY | xlib::CWX | xlib::CWHeight | xlib::CWWidth)
as u32,
&mut windowchanges,
);
// I don't think I have to call this ~
//self.configure_client(client);
}
}
}
pub fn move_client(&self, client: &Client) {
let mut wc = xlib::XWindowChanges {
x: client.position.0,
y: client.position.1,
width: client.size.0,
height: client.size.1,
border_width: 0,
sibling: 0,
stack_mode: 0,
};
if client.size.0 < 1 || client.size.1 < 1 {
error!("client {:?} size is less than 1 pixel!", client);
} else {
unsafe {
xlib::XConfigureWindow(
self.dpy(),
client.window,
(xlib::CWX | xlib::CWY) as u32,
&mut wc,
);
}
}
}
pub fn resize_client(&self, client: &Client) {
let mut wc = xlib::XWindowChanges {
x: client.position.0,
y: client.position.1,
width: client.size.0,
height: client.size.1,
border_width: 0,
sibling: 0,
stack_mode: 0,
};
if client.size.0 < 1 || client.size.1 < 1 {
error!("client {:?} size is less than 1 pixel!", client);
} else {
unsafe {
xlib::XConfigureWindow(
self.dpy(),
client.window,
(xlib::CWWidth | xlib::CWHeight) as u32,
&mut wc,
);
}
}
}
pub fn hide_client(&self, client: &Client) {
let mut wc = xlib::XWindowChanges {
x: client.size.0 * -2,
y: client.position.1,
width: client.size.0,
height: client.size.1,
border_width: 0,
sibling: 0,
stack_mode: 0,
};
if client.size.0 < 1 || client.size.1 < 1 {
error!("client {:?} size is less than 1 pixel!", client);
} else {
unsafe {
xlib::XConfigureWindow(
self.dpy(),
client.window,
(xlib::CWX | xlib::CWY) as u32,
&mut wc,
);
}
}
}
pub fn raise_client(&self, client: &Client) {
unsafe {
XRaiseWindow(self.dpy(), client.window);
}
}
pub fn get_window_size(&self, window: Window) -> Option<(i32, i32)> {
let mut wa = unsafe {
std::mem::MaybeUninit::<xlib::XWindowAttributes>::zeroed()
.assume_init()
};
if unsafe {
xlib::XGetWindowAttributes(self.dpy(), window, &mut wa) != 0
} {
Some((wa.width, wa.height))
} else {
None
}
}
fn get_window_attributes(
&self,
window: Window,
) -> Option<XWindowAttributes> {
let mut wa = unsafe {
std::mem::MaybeUninit::<xlib::XWindowAttributes>::zeroed()
.assume_init()
};
if unsafe {
xlib::XGetWindowAttributes(self.dpy(), window, &mut wa) != 0
} {
Some(wa)
} else {
None
}
}
pub fn get_transient_for_window(&self, window: Window) -> Option<Window> {
let mut transient_for: Window = 0;
if unsafe {
XGetTransientForHint(self.dpy(), window, &mut transient_for) != 0
} {
Some(transient_for)
} else {
None
}
}
pub fn expose_client(&self, client: &Client) {
self.expose_window(client.window);
}
fn expose_window(&self, window: Window) {
if let Some(wa) = self.get_window_attributes(window) {
unsafe {
xlib::XClearArea(
self.dpy(),
window,
0,
0,
wa.width as u32,
wa.height as u32,
1,
);
}
}
}
pub fn configure_window(&self, event: &XConfigureRequestEvent) {
let mut wc = xlib::XWindowChanges {
x: event.x,
y: event.y,
width: event.width,
height: event.height,
border_width: event.border_width,
sibling: event.above,
stack_mode: event.detail,
};
unsafe {
xlib::XConfigureWindow(
self.dpy(),
event.window,
event.value_mask as u32,
&mut wc,
);
}
}
pub fn configure_client(&self, client: &Client, border: i32) {
let mut event = xlib::XConfigureEvent {
type_: xlib::ConfigureNotify,
display: self.dpy(),
event: client.window,
window: client.window,
x: client.position.0,
y: client.position.1,
width: client.size.0,
height: client.size.1,
border_width: border,
override_redirect: 0,
send_event: 0,
serial: 0,
above: 0,
};
unsafe {
xlib::XSetWindowBorderWidth(
self.dpy(),
event.window,
event.border_width as u32,
);
xlib::XSendEvent(
self.dpy(),
event.window,
0,
StructureNotifyMask,
&mut event as *mut _ as *mut XEvent,
);
}
}
pub fn map_window(&self, window: Window) {
unsafe {
XMapWindow(self.dpy(), window);
xlib::XSelectInput(
self.dpy(),
window,
EnterWindowMask
| FocusChangeMask
| PropertyChangeMask
| StructureNotifyMask,
);
}
self.grab_global_keybinds(window);
}
pub fn dimensions(&self) -> (i32, i32) {
unsafe {
let mut wa =
std::mem::MaybeUninit::<xlib::XWindowAttributes>::zeroed()
.assume_init();
xlib::XGetWindowAttributes(self.dpy(), self.root, &mut wa);
info!("Root window dimensions: {}, {}", wa.width, wa.height);
(wa.width, wa.height)
}
}
pub fn close_dpy(&self) {
unsafe {
XCloseDisplay(self.dpy());
}
}
pub fn kill_client(&self, client: &Client) {
if !self.send_protocol(client, self.atoms.delete_window) {
unsafe {
XKillClient(self.dpy(), client.window);
}
}
}
pub fn grab_cursor(&self) {
unsafe {
XGrabPointer(
self.dpy(),
self.root,
0,
(ButtonPressMask | ButtonReleaseMask | PointerMotionMask)
as u32,
GrabModeAsync,
GrabModeAsync,
0,
0,
CurrentTime,
);
}
}
pub fn release_cursor(&self) {
unsafe {
XUngrabPointer(self.dpy(), CurrentTime);
}
}
pub fn move_cursor(&self, window: Option<Window>, position: (i32, i32)) {
unsafe {
XWarpPointer(
self.dpy(),
0,
window.unwrap_or(self.root),
0,
0,
0,
0,
position.0,
position.1,
);
}
}
fn check_for_protocol(&self, client: &Client, proto: xlib::Atom) -> bool {
let mut protos: *mut xlib::Atom = null_mut();
let mut num_protos: i32 = 0;
unsafe {
if xlib::XGetWMProtocols(
self.dpy(),
client.window,
&mut protos,
&mut num_protos,
) != 0
{
for i in 0..num_protos {
if *protos.offset(i as isize) == proto {
return true;
}
}
}
}
return false;
}
fn send_protocol(&self, client: &Client, proto: xlib::Atom) -> bool {
if self.check_for_protocol(client, proto) {
let mut data = xlib::ClientMessageData::default();
data.set_long(0, proto as i64);
let mut event = XEvent {
client_message: xlib::XClientMessageEvent {
type_: xlib::ClientMessage,
serial: 0,
display: self.dpy(),
send_event: 0,
window: client.window,
format: 32,
message_type: self.atoms.protocols,
data,
},
};
unsafe {
xlib::XSendEvent(
self.dpy(),
client.window,
0,
xlib::NoEventMask,
&mut event,
);
}
true
} else {
false
}
}
pub fn make_key<S>(&self, key: S, modmask: u32) -> KeyOrButton
where
S: AsRef<str>,
{
let key = self.keycode(key);
KeyOrButton::Key(key, modmask)
}
fn keycode<S>(&self, string: S) -> i32
where
S: AsRef<str>,
{
let c_string = CString::new(string.as_ref()).unwrap();
unsafe {
let keysym = xlib::XStringToKeysym(c_string.as_ptr());
xlib::XKeysymToKeycode(self.dpy(), keysym) as i32
}
}
fn get_numlock_mask(&self) -> u32 {
unsafe {
let modmap = xlib::XGetModifierMapping(self.dpy());
let max_keypermod = (*modmap).max_keypermod;
for i in 0..8 {
for j in 0..max_keypermod {
if *(*modmap)
.modifiermap
.offset((i * max_keypermod + j) as isize)
== xlib::XKeysymToKeycode(
self.dpy(),
x11::keysym::XK_Num_Lock as u64,
)
{
return 1 << i;
}
}
}
}
0
}
pub fn get_clean_mask(&self) -> u32 {
!(self.get_numlock_mask() | LockMask)
& (ShiftMask
| ControlMask
| Mod1Mask
| Mod2Mask
| Mod3Mask
| Mod4Mask
| Mod5Mask)
}
#[allow(dead_code)]
pub fn clean_mask(&self, mask: u32) -> u32 {
mask & self.get_clean_mask()
}
pub fn are_masks_equal(&self, rhs: u32, lhs: u32) -> bool {
let clean = self.get_clean_mask();
rhs & clean == lhs & clean
}
}
impl Display {
pub fn new(display: *mut x11::xlib::Display) -> Self {
Self {
0: Rc::new(display),
}
}
pub fn get(&self) -> *mut x11::xlib::Display {
*self.0
}
}
impl Atoms {
fn init(display: Display) -> Self {
unsafe {
Self {
protocols: {
let name = CString::new("WM_PROTOCOLS").unwrap();
XInternAtom(display.get(), name.as_c_str().as_ptr(), 0)
},
delete_window: {
let name = CString::new("WM_DELETE_WINDOW").unwrap();
XInternAtom(display.get(), name.as_c_str().as_ptr(), 0)
},
active_window: {
let name = CString::new("WM_ACTIVE_WINDOW").unwrap();
XInternAtom(display.get(), name.as_c_str().as_ptr(), 0)
},
take_focus: {
let name = CString::new("WM_TAKE_FOCUS").unwrap();
XInternAtom(display.get(), name.as_c_str().as_ptr(), 0)
},
}
}
}
}
#[allow(dead_code)]
unsafe extern "C" fn xlib_error_handler(
_dpy: *mut x11::xlib::Display,
ee: *mut x11::xlib::XErrorEvent,
) -> std::os::raw::c_int {
let err = ee.as_ref().unwrap();
if err.error_code == x11::xlib::BadWindow
|| err.error_code == x11::xlib::BadDrawable
|| err.error_code == x11::xlib::BadAccess
|| err.error_code == x11::xlib::BadMatch
{
0
} else {
error!(
"wm: fatal error:\nrequest_code: {}\nerror_code: {}",
err.request_code, err.error_code
);
std::process::exit(1);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

View file

@ -1,8 +0,0 @@
#!/bin/sh
/usr/bin/xset b off
/usr/bin/xsetroot -solid darkslategrey
/usr/bin/feh --bg-fill "/mnt/storage/rust/wm/starship.jpg"
xset r rate 250 30
export RUST_BACKTRACE=1
exec /mnt/storage/rust/wm/target/release/wm >& /home/user/.local/portlights.log