├── .gitignore ├── api ├── lib │ ├── api.o │ └── libragnar.a ├── Makefile ├── include │ └── ragnar │ │ └── api.h └── api.c ├── src ├── ipc │ ├── sockets.h │ └── sockets.c ├── config.h ├── structs.h ├── config.c ├── keycallbacks.h └── funcs.h ├── branding ├── clean.png ├── logo.png ├── workflow.png └── screenshot.png ├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── ragnar.desktop ├── install.sh ├── ragnarstart ├── run ├── flake.nix ├── PKGBUILD ├── Makefile ├── flake.lock ├── compile_commands.json ├── README.md ├── CODE_OF_CONDUCT.md ├── cfg └── ragnar.cfg └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | ragnar.o 2 | ragnarstart 3 | *.patch 4 | .cache/ 5 | bin/ 6 | -------------------------------------------------------------------------------- /api/lib/api.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/ragnar/HEAD/api/lib/api.o -------------------------------------------------------------------------------- /src/ipc/sockets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void* ipcserverthread(void* arg); 4 | -------------------------------------------------------------------------------- /api/lib/libragnar.a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/ragnar/HEAD/api/lib/libragnar.a -------------------------------------------------------------------------------- /branding/clean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/ragnar/HEAD/branding/clean.png -------------------------------------------------------------------------------- /branding/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/ragnar/HEAD/branding/logo.png -------------------------------------------------------------------------------- /branding/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/ragnar/HEAD/branding/workflow.png -------------------------------------------------------------------------------- /branding/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cococry/ragnar/HEAD/branding/screenshot.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | custom: ["https://www.paypal.com/donate/?hosted_button_id=YZN5K2X7NZ95E"] 3 | -------------------------------------------------------------------------------- /ragnar.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Encoding=UTF-8 3 | Name=Ragnar 4 | Comment=Ragnar Window Manager 5 | Exec=ragnar 6 | Icon=ragnar 7 | Type=XSession 8 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | cd api 2 | make && sudo make install 3 | cd .. 4 | make config 5 | make && sudo make install 6 | 7 | echo "Successfully installed ragnarwm." 8 | -------------------------------------------------------------------------------- /ragnarstart: -------------------------------------------------------------------------------- 1 | setxkbmap -option caps:escape & 2 | xrandr --output HDMI-A-1 --mode 2560x1440 --rate 165 3 | nitrogen --restore & 4 | rgconfwatch & 5 | boron & 6 | setxkbmap us 7 | -------------------------------------------------------------------------------- /run: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # See xserver bug: https://gitlab.freedesktop.org/xorg/xserver/-/issues/1289 3 | unset XDG_SEAT 4 | 5 | Xephyr -br -ac -reset -screen 1280x720 :1 & 6 | sleep 1s 7 | export DISPLAY=:1 8 | ragnar 9 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "structs.h" 4 | 5 | void initconfig(state_t* s); 6 | void readconfig(state_t* s, config_data_t* data); 7 | void reloadconfig(state_t* s, config_data_t* data); 8 | void destroyconfig(void); 9 | -------------------------------------------------------------------------------- /api/Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS= 3 | all: lib/ragnar.a 4 | lib/ragnar.a: lib/api.o 5 | ar cr lib/libragnar.a lib/*.o 6 | lib/api.o: lib 7 | ${CC} -c api.c -o lib/api.o ${CFLAGS} 8 | 9 | lib: 10 | mkdir lib 11 | clean: 12 | rm -r ./lib 13 | 14 | install: all 15 | cp lib/libragnar.a /usr/local/lib/ 16 | cp -r include/ragnar /usr/local/include 17 | 18 | uninstall: 19 | rm -f /usr/local/lib/libragnar.a 20 | rm -rf /usr/local/include/ragnar/ 21 | 22 | .PHONY: all test clean 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Ragnar Window Manager"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; 6 | flake-utils.url = "github:numtide/flake-utils"; 7 | }; 8 | 9 | outputs = { self, nixpkgs, flake-utils }: 10 | flake-utils.lib.eachDefaultSystem (system: let 11 | pkgs = nixpkgs.legacyPackages.${system}; 12 | 13 | libs = with pkgs; [ 14 | libGL 15 | mesa 16 | libconfig 17 | ] ++ (with pkgs.xorg; [ 18 | libX11 19 | libxcb 20 | xcbutil 21 | xcbutilwm 22 | xorgproto 23 | xcb-util-cursor 24 | xcbutilkeysyms 25 | ]); 26 | 27 | in { 28 | devShells.default = pkgs.mkShell { 29 | packages = with pkgs; [ 30 | gnumake 31 | gcc 32 | ] ++ libs; 33 | }; 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Contributor: Luxzi 2 | 3 | pkgname=ragnarwm 4 | _pkgname="Ragnar" 5 | pkgver='1.3' 6 | pkgrel=1 7 | pkgdesc="Minimal, flexible & user-friendly X tiling window manager" 8 | arch=('x86_64') 9 | url="https://github.com/cococry/Ragnar" 10 | license=('GPL') 11 | groups=() 12 | depends=('xcb' 'xcb-util' '') 13 | makedepends=('git' 'make' 'gcc') 14 | provides=('ragnarwm') 15 | source=("${_pkgname}::git+https://github.com/cococry/${_pkgname}.git") 16 | sha256sums=('SKIP') 17 | 18 | pkgver() { 19 | cd $_pkgname 20 | echo $pkgver 21 | } 22 | 23 | build() { 24 | cd $_pkgname 25 | make ragnar 26 | } 27 | 28 | package() { 29 | cd $_pkgname 30 | install -D -m777 ./ragnar "$pkgdir/usr/bin/ragnar" 31 | install -D -m777 ./ragnar.desktop "$pkgdir/usr/share/applications/ragnar.desktop" 32 | install -D -m777 ./ragnarstart "$pkgdir/usr/bin/ragnarstart" 33 | } 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = cc 2 | CFLAGS = -O3 -ffast-math -Wall -Wextra -pedantic 3 | CFLAGS += -isystem api/include 4 | 5 | LDLIBS = -lxcb -lxcb-keysyms -lxcb-icccm -lxcb-cursor -lxcb-randr -lxcb-composite -lxcb-ewmh -lX11 -lX11-xcb -lGL -lm -lconfig -lxcb-util 6 | 7 | SRC = ./src/*.c ./src/ipc/*.c 8 | BIN = ragnar 9 | 10 | RAGNAR_API = api/lib/ragnar.a 11 | 12 | PREFIX = /usr 13 | BINDIR = $(PREFIX)/bin 14 | 15 | .PHONY: all 16 | all: $(RAGNAR_API) 17 | mkdir -p ./bin 18 | $(CC) -o bin/$(BIN) $(CFLAGS) $(SRC) $(LDLIBS) 19 | 20 | $(RAGNAR_API): 21 | $(MAKE) -C api 22 | 23 | 24 | DEST_DIR := $(HOME)/.config/ragnarwm 25 | CONFIG_FILE := $(DEST_DIR)/ragnar.cfg 26 | 27 | .PHONY: config 28 | config: 29 | @if [ ! -f "$(CONFIG_FILE)" ]; then \ 30 | echo "Config file does not exist. Copying default config..."; \ 31 | mkdir -p "$(DEST_DIR)"; \ 32 | cp -r "./cfg/ragnar.cfg" "$(CONFIG_FILE)"; \ 33 | else \ 34 | echo "Config file already exists. Skipping copy $(CONFIG_FILE)."; \ 35 | fi 36 | 37 | .PHONY: install 38 | install: 39 | install -Dm755 bin/$(BIN) -t $(BINDIR) 40 | install -Dm755 ragnarstart -t $(BINDIR) 41 | cp -f ragnar.desktop $(PREFIX)/share/xsessions/ 42 | 43 | .PHONY: clean 44 | clean: 45 | $(RM) bin/* 46 | 47 | .PHONY: uninstall 48 | uninstall: 49 | $(RM) $(BINDIR)/ragnar 50 | $(RM) $(PREFIX)/share/xsessions/ragnar.desktop 51 | $(RM) $(BINDIR)/ragnarstart 52 | -------------------------------------------------------------------------------- /api/include/ragnar/api.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | typedef int32_t RgWindow; 7 | 8 | #define RG_INVALID_WINDOW -1 9 | 10 | typedef enum { 11 | RgCommandTerminate, 12 | RgCommandGetWindows, 13 | RgCommandKillWindow, 14 | RgCommandFocusWindow, 15 | RgCommandNextWindow, 16 | RgCommandFirstWindow, 17 | RgCommandGetFocus, 18 | RgCommandGetMonitorFocus, 19 | RgCommandGetCursor, 20 | RgCommandGetWindowArea, 21 | RgCommandReloadConfig, 22 | RgCommandSwitchDesktop, 23 | } RgCommandType; 24 | 25 | typedef struct { 26 | float x, y; 27 | } Rgv2; 28 | 29 | typedef struct { 30 | Rgv2 pos, size; 31 | } RgArea; 32 | 33 | void rg_set_trace_logging(bool logging); 34 | 35 | int32_t rg_cmd_terminate(uint32_t exitcode); 36 | 37 | int32_t rg_cmd_get_windows(RgWindow** wins, uint32_t* numwins); 38 | 39 | int32_t rg_cmd_kill_window(RgWindow win); 40 | 41 | int32_t rg_cmd_focus_window(RgWindow win); 42 | 43 | int32_t rg_cmd_next_window(RgWindow win, RgWindow* next); 44 | 45 | int32_t rg_cmd_first_window(RgWindow* first); 46 | 47 | int32_t rg_cmd_get_focus(RgWindow* focus); 48 | 49 | int32_t rg_cmd_get_monitor_focus(int32_t* idx); 50 | 51 | int32_t rg_cmd_get_cursor(Rgv2* cursor); 52 | 53 | int32_t rg_cmd_get_window_area(RgWindow win, RgArea* area); 54 | 55 | int32_t rg_cmd_reload_config(void); 56 | 57 | int32_t rg_cmd_switch_desktop(uint32_t desktop_id); 58 | 59 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1710146030, 9 | "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1723703277, 24 | "narHash": "sha256-nk0RaUB5f68BwtXAYy3WAjqFhVKqIl9Z89RGycTa2vk=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "8b908192e64224420e2d59dfd9b2e4309e154c5d", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixpkgs-unstable", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /compile_commands.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "arguments": [ 4 | "/usr/bin/cc", 5 | "-c", 6 | "-O3", 7 | "-ffast-math", 8 | "-Wall", 9 | "-Wextra", 10 | "-pedantic", 11 | "-o", 12 | "bin/ragnar", 13 | "./src/config.c" 14 | ], 15 | "directory": "/home/cococry/dev/ragnar", 16 | "file": "/home/cococry/dev/ragnar/src/config.c", 17 | "output": "/home/cococry/dev/ragnar/bin/ragnar" 18 | }, 19 | { 20 | "arguments": [ 21 | "/usr/bin/cc", 22 | "-c", 23 | "-O3", 24 | "-ffast-math", 25 | "-Wall", 26 | "-Wextra", 27 | "-pedantic", 28 | "-o", 29 | "bin/ragnar", 30 | "./src/ragnar.c" 31 | ], 32 | "directory": "/home/cococry/dev/ragnar", 33 | "file": "/home/cococry/dev/ragnar/src/ragnar.c", 34 | "output": "/home/cococry/dev/ragnar/bin/ragnar" 35 | }, 36 | { 37 | "arguments": [ 38 | "/usr/bin/cc", 39 | "-c", 40 | "-O3", 41 | "-ffast-math", 42 | "-Wall", 43 | "-Wextra", 44 | "-pedantic", 45 | "-o", 46 | "bin/ragnar", 47 | "./src/ipc/shm.c" 48 | ], 49 | "directory": "/home/cococry/dev/ragnar", 50 | "file": "/home/cococry/dev/ragnar/src/ipc/shm.c", 51 | "output": "/home/cococry/dev/ragnar/bin/ragnar" 52 | }, 53 | { 54 | "arguments": [ 55 | "/usr/bin/cc", 56 | "-c", 57 | "-O3", 58 | "-ffast-math", 59 | "-Wall", 60 | "-Wextra", 61 | "-pedantic", 62 | "-o", 63 | "bin/ragnar", 64 | "./src/ipc/sockets.c" 65 | ], 66 | "directory": "/home/cococry/dev/ragnar", 67 | "file": "/home/cococry/dev/ragnar/src/ipc/sockets.c", 68 | "output": "/home/cococry/dev/ragnar/bin/ragnar" 69 | } 70 | ] 71 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | **Ragnar is a feature-rich, straight-to-the-point dynamic window manager for X** 4 | 5 | The goal of ragnarwm is to create a window manager that can be used as a solid foundation 6 | for fully fledged desktop environments but also serve as a minimal daily driver. Ragnar 7 | contains about 5k lines of code which contain features like an IPC API, configuration file, 8 | tiling layouts, EWMH & ICCM implementation and much more key features. 9 | 10 | Since Version 2.0, the window manager uses the **XCB API** for communicating with the X server, 11 | which is far less bloated compared to the Xlib API. 12 | 13 | 14 | 15 | --- 16 | 17 | # Installation 18 | 19 | Installing ragnarwm is a process that involes two main steps. 20 | 21 | - Install build depdencies 22 | ```console 23 | xcb-util, xcb-proto, xcb-util-keysyms, xcb-util-cursor, xcb-util-wm, xorg-server, xorg-xinit, mesa, libconfig 24 | ``` 25 | 26 | - Install the window manager 27 | ```console 28 | $ ./install.sh 29 | ``` 30 | 31 | # Technical 32 | 33 | ### General 34 | Ragnar uses **reparenting** to handle client windows which gives it the ability to easily create decoration 35 | for client windows. 36 | Client windows are stored within a doubly linked list. 37 | As for the tiling fuctionality, **ragnar does not use some kind of tree datastructure**, instead, 38 | there are **preset layouts** which always resemble the same shape. This choice was made to keep the experience 39 | smooth without thinking about where to place the spawning window. Layouts are still very customizable by changing the sizes of master and slave windows with a simple keybind. 40 | 41 | ### IPC 42 | The source code also contains an **abstracted IPC API** for creating plugins. The API works through 43 | a **socket with binary data**. 44 | 45 | ### Config file 46 | Ragnar uses libconfig to read an external configuration file for the window manager (/home/user/.config/ragnarwm.cfg). 47 | This configuration is read on startup and can be reloaded while the WM is running (Typically through a keybind). 48 | 49 | ### Code Structure 50 | 51 | - **funcs.h** 52 | This file contains the documented function declarations that the window manager uses. Allthough, 53 | it does **not** contain the callback functions that are fired via keybindings. 54 | 55 | - **keycallbacks.h** 56 | This file contains the documented function **definitions** of all callback functions that can be specified 57 | in the configuration file. 58 | 59 | - **ragnar.c** 60 | This is the main translation unit that contains the definitions of all systematic window manager functions, 61 | containg the application loop, event handling, calculating tiling layouts and more. 62 | 63 | - **structs.h** 64 | This file contains structures and function declaration that are used throughout the window manager's code. 65 | 66 | - **config.h/config.c** 67 | Those files handle loading the configuration file with libconfig. 68 | 69 | - **ipc/** 70 | The files socket.h and socket.c handle socket connections from clients via IPC. 71 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | Email. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /api/api.c: -------------------------------------------------------------------------------- 1 | #include "include/ragnar/api.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define SOCKPATH "/tmp/ragnar_socket" 15 | 16 | typedef struct { 17 | int32_t sock; 18 | struct sockaddr_un addr; 19 | } socket_client_t; 20 | 21 | static int32_t clientconnect(socket_client_t* cl); 22 | static int32_t senddata(socket_client_t* cl, const void* data, size_t size); 23 | static int32_t recvdata(socket_client_t* cl, void* data, size_t size); 24 | static int32_t recvv2(socket_client_t* cl, Rgv2* v2); 25 | static int32_t setcmdtype(socket_client_t* cl, RgCommandType type); 26 | static int32_t setdatalength(socket_client_t* cl, uint32_t len); 27 | static int32_t sendcmd(socket_client_t* cl, RgCommandType type, uint8_t* data, uint32_t len); 28 | static int32_t clientinit(socket_client_t* cl); 29 | static int32_t clientclose(socket_client_t* cl); 30 | static void establishconn(socket_client_t* cl); 31 | static void closeconn(socket_client_t* cl); 32 | 33 | static bool s_logging = false; 34 | 35 | int32_t 36 | clientconnect(socket_client_t* cl) { 37 | int code = connect(cl->sock, 38 | (struct sockaddr*)&cl->addr, 39 | sizeof(struct sockaddr_un)); 40 | return code < 0 ? 1 : 0; 41 | } 42 | 43 | int32_t 44 | senddata(socket_client_t* cl, const void* data, size_t size) { 45 | int code = write(cl->sock, data, size); 46 | return code == -1 ? 1 : 0; 47 | } 48 | 49 | int32_t 50 | recvdata(socket_client_t* cl, void* data, size_t size) { 51 | int code = read(cl->sock, data, size); 52 | return code == -1 ? 1 : 0; 53 | } 54 | 55 | int32_t 56 | recvv2(socket_client_t* cl, Rgv2* v2) { 57 | if(recvdata(cl, &v2->x, sizeof(float)) != 0) { 58 | return 1; 59 | } 60 | if(recvdata(cl, &v2->y, sizeof(float)) != 0) { 61 | return 1; 62 | } 63 | 64 | return 0; 65 | } 66 | 67 | int32_t 68 | setcmdtype(socket_client_t* cl, RgCommandType type) { 69 | uint8_t typecode = (uint8_t)type; 70 | return senddata(cl, &typecode, sizeof(typecode)); 71 | } 72 | 73 | int32_t 74 | setdatalength(socket_client_t* cl, uint32_t len) { 75 | uint32_t len_net = htonl(len); 76 | return senddata(cl, &len_net, sizeof(len_net)); 77 | } 78 | 79 | int32_t 80 | sendcmd(socket_client_t* cl, RgCommandType type, uint8_t* data, uint32_t len) { 81 | if(setcmdtype(cl, type) != 0) { 82 | fprintf(stderr, "ragnar api: failed to upload command type.\n"); 83 | return 1; 84 | } 85 | 86 | if(setdatalength(cl, len) != 0) { 87 | fprintf(stderr, "ragnar api: failed to upload data length.\n"); 88 | return 1; 89 | } 90 | 91 | if(senddata(cl, data, len) != 0) { 92 | fprintf(stderr, "ragnar api: failed to upload command data.\n"); 93 | return 1; 94 | } 95 | 96 | return 0; 97 | } 98 | 99 | int32_t 100 | clientinit(socket_client_t* cl) { 101 | cl->sock = socket(AF_UNIX, SOCK_STREAM, 0); 102 | if(cl->sock < 0) { 103 | fprintf(stderr, "ragnar api: failed to create client socket.\n"); 104 | return 1; 105 | } 106 | 107 | memset(&cl->addr, 0, sizeof(struct sockaddr_un)); 108 | cl->addr.sun_family = AF_UNIX; 109 | strncpy(cl->addr.sun_path, SOCKPATH, sizeof(cl->addr.sun_path) - 1); 110 | 111 | if(s_logging) { 112 | printf("ragnar api: created unix domain socket on: '%s'\n", SOCKPATH); 113 | } 114 | return 0; 115 | } 116 | 117 | int32_t 118 | clientclose(socket_client_t* cl) { 119 | int32_t code = close(cl->sock); 120 | return code == -1 ? 1 : 0; 121 | } 122 | 123 | void 124 | establishconn(socket_client_t* cl) { 125 | if(clientinit(cl) != 0) { 126 | fprintf(stderr, "ragnar api: failed to open client connection.\n"); 127 | return; 128 | } 129 | 130 | if(clientconnect(cl) != 0) { 131 | fprintf(stderr, "ragnar api: client failed to connect to ragnar API.\n"); 132 | return; 133 | } 134 | 135 | if(s_logging) { 136 | printf("ragnar api: successfully connected to server.\n"); 137 | } 138 | } 139 | 140 | void 141 | closeconn(socket_client_t* cl) { 142 | if(clientclose(cl) != 0) { 143 | fprintf(stderr, "ragnar api: failed to close client connection.\n"); 144 | return; 145 | } 146 | if(s_logging) { 147 | printf("ragnar api: closed client connection.\n"); 148 | } 149 | } 150 | 151 | void rg_set_trace_logging(bool logging) { 152 | s_logging = logging; 153 | } 154 | 155 | int32_t 156 | rg_cmd_terminate(uint32_t exitcode) { 157 | socket_client_t cl; 158 | establishconn(&cl); 159 | 160 | uint32_t len = sizeof(uint32_t); 161 | uint8_t data[len]; 162 | memcpy(data, &exitcode, sizeof(exitcode)); 163 | 164 | if(sendcmd(&cl, RgCommandTerminate, data, len) != 0) { 165 | fprintf(stderr, "ragnar api: RgCommandTerminate: failed to send command.\n"); 166 | closeconn(&cl); 167 | return 1; 168 | } 169 | 170 | if(s_logging) { 171 | printf("ragnar api: RgCommandTerminate: successfully sent command.\n"); 172 | } 173 | closeconn(&cl); 174 | return 0; 175 | } 176 | 177 | int32_t 178 | rg_cmd_get_windows(RgWindow** wins, uint32_t* numwins) { 179 | (void)wins; 180 | (void)numwins; 181 | uint32_t len = 0; 182 | uint8_t* data = NULL; 183 | 184 | socket_client_t cl; 185 | establishconn(&cl); 186 | 187 | if(sendcmd(&cl, RgCommandGetWindows, data, len) != 0) { 188 | fprintf(stderr, "ragnar api: RgCommandGetWindows: failed to send command.\n"); 189 | closeconn(&cl); 190 | return 1; 191 | } 192 | 193 | uint32_t num; 194 | if (recvdata(&cl, &num, sizeof(num)) < 0) { 195 | perror("read size"); 196 | close(cl.sock); 197 | exit(EXIT_FAILURE); 198 | } 199 | 200 | RgWindow* buf = malloc(num * sizeof(RgWindow)); 201 | if (!buf) { 202 | fprintf(stderr, "ragnar api: RgCommandGetWindows: failed to allocate memory for windows.\n"); 203 | closeconn(&cl); 204 | return 1; 205 | } 206 | 207 | if (recvdata(&cl, buf, num * sizeof(RgWindow)) != 0) { 208 | fprintf(stderr, "ragnar api: RgCommandGetWindows: failed to receive client windows.\n"); 209 | closeconn(&cl); 210 | return 1; 211 | } 212 | 213 | if(s_logging) { 214 | printf("ragnar api: RgCommandGetWindows: successfully sent command.\n"); 215 | } 216 | 217 | closeconn(&cl); 218 | 219 | *numwins = num; 220 | *wins = buf; 221 | 222 | return 0; 223 | } 224 | 225 | int32_t 226 | rg_cmd_kill_window(RgWindow win) { 227 | socket_client_t cl; 228 | establishconn(&cl); 229 | 230 | uint32_t len = sizeof(RgWindow); 231 | 232 | uint8_t data[len]; 233 | memcpy(data, &win, sizeof(win)); 234 | 235 | if(sendcmd(&cl, RgCommandKillWindow, data, len) != 0) { 236 | fprintf(stderr, "ragnar api: RgCommandKillWindow: failed to send command.\n"); 237 | closeconn(&cl); 238 | return 1; 239 | } 240 | 241 | if(s_logging) { 242 | printf("ragnar api: RgCommandKillWindow: successfully sent command.\n"); 243 | } 244 | closeconn(&cl); 245 | return 0; 246 | } 247 | 248 | int32_t 249 | rg_cmd_focus_window(RgWindow win) { 250 | socket_client_t cl; 251 | establishconn(&cl); 252 | 253 | uint32_t len = sizeof(RgWindow); 254 | 255 | uint8_t data[len]; 256 | memcpy(data, &win, sizeof(win)); 257 | 258 | if(sendcmd(&cl, RgCommandFocusWindow, data, len) != 0) { 259 | fprintf(stderr, "ragnar api: RgCommandFocusWindow: failed to send command.\n"); 260 | closeconn(&cl); 261 | return 1; 262 | } 263 | 264 | if(s_logging) { 265 | printf("ragnar api: RgCommandFocusWindow: successfully sent command.\n"); 266 | } 267 | closeconn(&cl); 268 | 269 | return 0; 270 | } 271 | 272 | int32_t 273 | rg_cmd_next_window(RgWindow win, RgWindow* next) { 274 | socket_client_t cl; 275 | establishconn(&cl); 276 | 277 | uint32_t len = sizeof(RgWindow); 278 | uint8_t data[len]; 279 | memcpy(data, &win, sizeof(win)); 280 | 281 | if(sendcmd(&cl, RgCommandNextWindow, data, len) != 0) { 282 | fprintf(stderr, "ragnar api: RgCommandNextWindow: failed to send command.\n"); 283 | closeconn(&cl); 284 | return 1; 285 | } 286 | 287 | if (recvdata(&cl, next, sizeof(RgWindow)) != 0) { 288 | fprintf(stderr, "ragnar api: RgCommandNextWindow: failed to receive next window.\n"); 289 | closeconn(&cl); 290 | return 1; 291 | } 292 | 293 | if(s_logging) { 294 | printf("ragnar api: RgCommandNextWindow: successfully sent command.\n"); 295 | } 296 | closeconn(&cl); 297 | 298 | return 0; 299 | } 300 | 301 | int32_t 302 | rg_cmd_first_window(RgWindow* first) { 303 | socket_client_t cl; 304 | establishconn(&cl); 305 | 306 | if(sendcmd(&cl, RgCommandFirstWindow, NULL, 0) != 0) { 307 | fprintf(stderr, "ragnar api: RgCommandFirstWindow: failed to send command.\n"); 308 | closeconn(&cl); 309 | return 1; 310 | } 311 | 312 | if (recvdata(&cl, first, sizeof(RgWindow)) != 0) { 313 | fprintf(stderr, "ragnar api: RgCommandFirstWindow: failed to receive first window.\n"); 314 | closeconn(&cl); 315 | return 1; 316 | } 317 | 318 | if(s_logging) { 319 | printf("ragnar api: RgCommandFirstWindow: successfully sent command.\n"); 320 | } 321 | closeconn(&cl); 322 | 323 | return 0; 324 | } 325 | int32_t 326 | rg_cmd_get_focus(RgWindow* focus) { 327 | socket_client_t cl; 328 | establishconn(&cl); 329 | 330 | if(sendcmd(&cl, RgCommandGetFocus, NULL, 0) != 0) { 331 | fprintf(stderr, "ragnar api: RgCommandGetFocus: failed to send command.\n"); 332 | closeconn(&cl); 333 | return 1; 334 | } 335 | 336 | if (recvdata(&cl, focus, sizeof(RgWindow)) != 0) { 337 | fprintf(stderr, "ragnar api: RgCommandGetFocus: failed to receive first window.\n"); 338 | closeconn(&cl); 339 | return 1; 340 | } 341 | 342 | if(s_logging) { 343 | printf("ragnar api: RgCommandGetFocus: successfully sent command.\n"); 344 | } 345 | closeconn(&cl); 346 | 347 | return 0; 348 | } 349 | 350 | int32_t 351 | rg_cmd_get_monitor_focus(int32_t* idx) { 352 | socket_client_t cl; 353 | establishconn(&cl); 354 | 355 | if(sendcmd(&cl, RgCommandGetMonitorFocus, NULL, 0) != 0) { 356 | fprintf(stderr, "ragnar api: RgCommandGetMonitorFocus: failed to send command.\n"); 357 | closeconn(&cl); 358 | return 1; 359 | } 360 | 361 | if (recvdata(&cl, idx, sizeof(int32_t)) != 0) { 362 | fprintf(stderr, "ragnar api: RgCommandGetMonitorFocus: failed to receive first window.\n"); 363 | closeconn(&cl); 364 | return 1; 365 | } 366 | 367 | if(s_logging) { 368 | printf("ragnar api: RgCommandGetMonitorFocus: successfully sent command.\n"); 369 | } 370 | 371 | closeconn(&cl); 372 | 373 | return 0; 374 | } 375 | 376 | int32_t 377 | rg_cmd_get_cursor(Rgv2* cursor) { 378 | socket_client_t cl; 379 | establishconn(&cl); 380 | 381 | if(sendcmd(&cl, RgCommandGetCursor, NULL, 0) != 0) { 382 | fprintf(stderr, "ragnar api: RgCommandGetCursor: failed to send command.\n"); 383 | closeconn(&cl); 384 | return 1; 385 | } 386 | 387 | if(recvv2(&cl, cursor) != 0) { 388 | fprintf(stderr, "ragnar api: RgCommandGetCursor: failed to receive cursor data.\n"); 389 | closeconn(&cl); 390 | return 1; 391 | } 392 | 393 | if(s_logging) { 394 | printf("ragnar api: RgCommandGetCursor: successfully sent command.\n"); 395 | } 396 | 397 | closeconn(&cl); 398 | 399 | return 0; 400 | } 401 | 402 | int32_t 403 | rg_cmd_get_window_area(RgWindow win, RgArea* area) { 404 | socket_client_t cl; 405 | establishconn(&cl); 406 | 407 | uint32_t len = sizeof(RgWindow); 408 | uint8_t data[len]; 409 | memcpy(data, &win, sizeof(win)); 410 | 411 | if(sendcmd(&cl, RgCommandGetWindowArea, data, len) != 0) { 412 | fprintf(stderr, "ragnar api: RgCommandGetWindowArea: failed to send command.\n"); 413 | closeconn(&cl); 414 | return 1; 415 | } 416 | 417 | if(recvv2(&cl, &area->pos) != 0) { 418 | fprintf(stderr, "ragnar api: RgCommandGetWindowArea: failed to receive window position.\n"); 419 | closeconn(&cl); 420 | return 1; 421 | } 422 | 423 | if(recvv2(&cl, &area->size) != 0) { 424 | fprintf(stderr, "ragnar api: RgCommandGetWindowArea: failed to receive window size.\n"); 425 | closeconn(&cl); 426 | return 1; 427 | } 428 | 429 | if(s_logging) { 430 | printf("ragnar api: RgCommandGetWindowArea: successfully sent command.\n"); 431 | } 432 | 433 | closeconn(&cl); 434 | 435 | return 0; 436 | } 437 | 438 | int32_t 439 | rg_cmd_reload_config(void) { 440 | socket_client_t cl; 441 | establishconn(&cl); 442 | 443 | if(sendcmd(&cl, RgCommandReloadConfig, NULL, 0) != 0) { 444 | fprintf(stderr, "ragnar api: RgCommandReloadConfig: failed to send command.\n"); 445 | closeconn(&cl); 446 | return 1; 447 | } 448 | 449 | if(s_logging) { 450 | printf("ragnar api: RgCommandReloadConfig: successfully sent command.\n"); 451 | } 452 | closeconn(&cl); 453 | return 0; 454 | 455 | } 456 | 457 | int32_t 458 | rg_cmd_switch_desktop(uint32_t desktop_id) { 459 | socket_client_t cl; 460 | establishconn(&cl); 461 | 462 | uint32_t len = sizeof(uint32_t); 463 | uint8_t data[len]; 464 | memcpy(data, &desktop_id, sizeof(desktop_id)); 465 | if(sendcmd(&cl, RgCommandSwitchDesktop, data, len) != 0) { 466 | fprintf(stderr, "ragnar api: RgCommandSwitchDesktop: failed to send command.\n"); 467 | closeconn(&cl); 468 | return 1; 469 | } 470 | 471 | if(s_logging) { 472 | printf("ragnar api: RgCommandSwitchDesktop: successfully sent command.\n"); 473 | } 474 | closeconn(&cl); 475 | return 0; 476 | 477 | } 478 | -------------------------------------------------------------------------------- /src/ipc/sockets.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../funcs.h" 14 | #include "../structs.h" 15 | #include "../config.h" 16 | #include 17 | 18 | #define SOCKPATH "/tmp/ragnar_socket" 19 | #define MSGSIZE 256 20 | 21 | #include "sockets.h" 22 | 23 | typedef void (*cmd_handler_t)(state_t* s, const uint8_t* data, int32_t clientfd); 24 | 25 | typedef struct { 26 | cmd_handler_t handler; 27 | uint32_t len; 28 | RgCommandType type; 29 | } cmd_data_t; 30 | 31 | static client_t* extractclient(state_t* s, const uint8_t* data); 32 | static void sendv2(int32_t clientfd, state_t* s, v2_t* v); 33 | 34 | static void cmdterminate(state_t* s, const uint8_t* data, int32_t clientfd); 35 | static void cmdgetwins(state_t* s, const uint8_t* data, int32_t clientfd); 36 | static void cmdkillwin(state_t* s, const uint8_t* data, int32_t clientfd); 37 | static void cmdfocuswin(state_t* s, const uint8_t* data, int32_t clientfd); 38 | static void cmdnextwin(state_t* s, const uint8_t* data, int32_t clientfd); 39 | static void cmdfirstwin(state_t* s, const uint8_t* data, int32_t clientfd); 40 | static void cmdgetfocus(state_t* s, const uint8_t* data, int32_t clientfd); 41 | static void cmdgetmonfocus(state_t* s, const uint8_t* data, int32_t clientfd); 42 | static void cmdgetcursor(state_t* s, const uint8_t* data, int32_t clientfd); 43 | static void cmdgetwinarea(state_t* s, const uint8_t* data, int32_t clientfd); 44 | static void cmdreloadconfig(state_t* s, const uint8_t* data, int32_t clientfd); 45 | static void cmdswitchdesktop(state_t* s, const uint8_t* data, int32_t clientfd); 46 | 47 | static void handlecmd(state_t* s, uint8_t cmdid, const uint8_t* data, 48 | size_t len, int32_t clientfd); 49 | 50 | static cmd_data_t cmdhandlers[] = { 51 | { .handler = cmdterminate, .len = sizeof(uint32_t), .type = RgCommandTerminate }, 52 | { .handler = cmdgetwins, .len = 0, .type = RgCommandGetWindows }, 53 | { .handler = cmdkillwin, .len = sizeof(RgWindow), .type = RgCommandKillWindow }, 54 | { .handler = cmdfocuswin, .len = sizeof(RgWindow), .type = RgCommandFocusWindow }, 55 | { .handler = cmdnextwin, .len = sizeof(RgWindow), .type = RgCommandNextWindow }, 56 | { .handler = cmdfirstwin, .len = 0, .type = RgCommandFirstWindow }, 57 | { .handler = cmdgetfocus, .len = 0, .type = RgCommandGetFocus }, 58 | { .handler = cmdgetmonfocus, .len = 0, .type = RgCommandGetMonitorFocus }, 59 | { .handler = cmdgetcursor, .len = 0, .type = RgCommandGetCursor }, 60 | { .handler = cmdgetwinarea, .len = sizeof(RgWindow), .type = RgCommandGetWindowArea }, 61 | { .handler = cmdreloadconfig, .len = 0, .type = RgCommandReloadConfig}, 62 | { .handler = cmdswitchdesktop, .len = sizeof(uint32_t), .type = RgCommandSwitchDesktop}, 63 | }; 64 | 65 | client_t* 66 | extractclient(state_t* s, const uint8_t* data) { 67 | RgWindow win; 68 | memcpy(&win, data, sizeof(RgWindow)); 69 | 70 | client_t* cl = clientfromwin(s, win); 71 | if(!cl) { 72 | logmsg(s, LogLevelError, 73 | "ipc: window %i is invalid, not killing."); 74 | return NULL; 75 | } 76 | 77 | return cl; 78 | } 79 | 80 | void 81 | sendv2(int32_t clientfd, state_t* s, v2_t* v) { 82 | Rgv2 v_rg = (Rgv2){ 83 | .x = v->x, 84 | .y = v->y 85 | }; 86 | 87 | if(write(clientfd, &v_rg.x, sizeof(float)) == -1) { 88 | logmsg(s, LogLevelError, 89 | "ipc: failed to send vec2 X position."); 90 | } 91 | 92 | if(write(clientfd, &v_rg.y, sizeof(float)) == -1) { 93 | logmsg(s, LogLevelError, 94 | "ipc: failed to send vec2 Y position."); 95 | } 96 | } 97 | 98 | void 99 | cmdterminate(state_t* s, const uint8_t* data, int32_t clientfd) { 100 | (void)clientfd; 101 | uint32_t exitcode; 102 | memcpy(&exitcode, data, sizeof(uint32_t)); 103 | 104 | logmsg(s, LogLevelTrace, "ipc: RgCommandTerminate: received command with exit code: %i", exitcode); 105 | terminate(s, exitcode); 106 | } 107 | 108 | void 109 | cmdgetwins(state_t* s, const uint8_t* data, int32_t clientfd) { 110 | (void)data; 111 | logmsg(s, LogLevelTrace, "ipc: RgCommandGetWindows: received command.\n"); 112 | 113 | 114 | uint32_t numwins = 0; 115 | for(monitor_t* mon = s->monitors; mon != NULL; mon = mon->next) { 116 | for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { 117 | numwins++; 118 | } 119 | } 120 | RgWindow wins[numwins]; 121 | uint32_t i = 0; 122 | for(monitor_t* mon = s->monitors; mon != NULL; mon = mon->next) { 123 | for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { 124 | wins[i++] = cl->win; 125 | } 126 | } 127 | 128 | if(write(clientfd, &numwins, sizeof(numwins)) == -1) { 129 | logmsg(s, LogLevelError, 130 | "ipc: RgCommandGetWindows: failed to send number of client windows."); 131 | } 132 | if(write(clientfd, wins, sizeof(wins)) == -1) { 133 | logmsg(s, LogLevelError, 134 | "ipc: RgCommandGetWindows: failed to send array of client windows."); 135 | } 136 | } 137 | 138 | void 139 | cmdkillwin(state_t* s, const uint8_t* data, int32_t clientfd) { 140 | (void)clientfd; 141 | client_t* cl; 142 | logmsg(s, LogLevelTrace, 143 | "ipc: RgCommandKillWindow: received command."); 144 | if(!(cl = extractclient(s, data))) { 145 | logmsg(s, LogLevelError, 146 | "ipc: RgCommandKillWindow: No client associated with window."); 147 | return; 148 | } 149 | killclient(s, cl); 150 | } 151 | 152 | void 153 | cmdfocuswin(state_t* s, const uint8_t* data, int32_t clientfd) { 154 | (void)clientfd; 155 | client_t* cl; 156 | logmsg(s, LogLevelTrace, 157 | "ipc: RgCommandFocusWindow: received command."); 158 | if(!(cl = extractclient(s, data))) { 159 | logmsg(s, LogLevelError, 160 | "ipc: RgCommandFocusWindow: No client associated with window."); 161 | return; 162 | } 163 | focusclient(s, cl, true); 164 | } 165 | 166 | void 167 | cmdnextwin(state_t* s, const uint8_t* data, int32_t clientfd) { 168 | (void)clientfd; 169 | client_t* cl; 170 | logmsg(s, LogLevelTrace, 171 | "ipc: RgCommandNextWindow: received command."); 172 | if(!(cl = extractclient(s, data))) { 173 | logmsg(s, LogLevelError, 174 | "ipc: RgCommandNextWindow: No client associated with window."); 175 | return; 176 | } 177 | 178 | RgWindow next = RG_INVALID_WINDOW; 179 | if(cl->next) { 180 | next = cl->next->win; 181 | } 182 | 183 | if(write(clientfd, &next, sizeof(next)) == -1) { 184 | logmsg(s, LogLevelError, 185 | "ipc: RgCommandNextWindow: failed to send next window."); 186 | } 187 | } 188 | 189 | void 190 | cmdfirstwin(state_t* s, const uint8_t* data, int32_t clientfd) { 191 | (void)data; 192 | logmsg(s, LogLevelTrace, 193 | "ipc: RgCommandFirstWindow: received command."); 194 | 195 | RgWindow first = RG_INVALID_WINDOW; 196 | if(s->monitors->clients) { 197 | first = s->monitors->clients->win ? (RgWindow)s->monitors->clients->win : RG_INVALID_WINDOW; 198 | } 199 | 200 | if(write(clientfd, &first, sizeof(first)) == -1) { 201 | logmsg(s, LogLevelError, 202 | "ipc: RgCommandFirstWindow: failed to send first client window."); 203 | } 204 | } 205 | 206 | void 207 | cmdgetfocus(state_t* s, const uint8_t* data, int32_t clientfd) { 208 | (void)data; 209 | logmsg(s, LogLevelTrace, 210 | "ipc: RgCommandGetFocus: received command."); 211 | 212 | RgWindow focus = s->focus ? (RgWindow)s->focus->win : RG_INVALID_WINDOW; 213 | 214 | if(write(clientfd, &focus, sizeof(focus)) == -1) { 215 | logmsg(s, LogLevelError, 216 | "ipc: RgCommandGetFocus: failed to send first client window."); 217 | } 218 | } 219 | 220 | void 221 | cmdgetmonfocus(state_t* s, const uint8_t* data, int32_t clientfd) { 222 | (void)data; 223 | logmsg(s, LogLevelTrace, 224 | "ipc: RgCommandGetMonitorFocus: received command."); 225 | 226 | 227 | int32_t focus = -1; 228 | if(s->monfocus) { 229 | focus = (int32_t)s->monfocus->idx; 230 | } 231 | 232 | if(write(clientfd, &focus, sizeof(focus)) == -1) { 233 | logmsg(s, LogLevelError, 234 | "ipc: RgCommandGetMonitorFocus: failed to send first client window."); 235 | } 236 | } 237 | 238 | void 239 | cmdgetcursor(state_t* s, const uint8_t* data, int32_t clientfd) { 240 | (void)data; 241 | logmsg(s, LogLevelTrace, 242 | "ipc: RgCommandGetCursor: received command."); 243 | 244 | bool success; 245 | v2_t cursor = cursorpos(s, &success); 246 | if(!success) { 247 | logmsg(s, LogLevelError, 248 | "ipc: RgCommandGetCursor: failed to get cursor position."); 249 | return; 250 | } 251 | 252 | sendv2(clientfd, s, &cursor); 253 | } 254 | void 255 | cmdgetwinarea(state_t* s, const uint8_t* data, int32_t clientfd) { 256 | (void)data; 257 | logmsg(s, LogLevelTrace, 258 | "ipc: RgCommandGetWindowArea: received command."); 259 | client_t* cl; 260 | if(!(cl = extractclient(s, data))) { 261 | logmsg(s, LogLevelError, 262 | "ipc: RgCommandGetWindowArea: No client associated with window."); 263 | return; 264 | } 265 | 266 | sendv2(clientfd, s, &cl->area.pos); 267 | sendv2(clientfd, s, &cl->area.size); 268 | } 269 | 270 | void 271 | cmdreloadconfig(state_t* s, const uint8_t* data, int32_t clientfd) { 272 | (void)data; 273 | (void)clientfd; 274 | logmsg(s, LogLevelTrace, 275 | "ipc: RgCommandReloadConfig: received command."); 276 | 277 | reloadconfig(s, &s->config); 278 | } 279 | 280 | void 281 | cmdswitchdesktop(state_t* s, const uint8_t* data, int32_t clientfd) { 282 | (void)clientfd; 283 | uint32_t desktop; 284 | memcpy(&desktop, data, sizeof(uint32_t)); 285 | logmsg(s, LogLevelTrace, 286 | "ipc: RgCommandSwitchDesktop: received command."); 287 | 288 | switchmonitordesktop(s, desktop); 289 | } 290 | 291 | void 292 | handlecmd(state_t* s, uint8_t cmdid, const uint8_t* data, size_t len, 293 | int32_t clientfd) { 294 | bool exec = false; 295 | for(uint32_t i = 0; i < sizeof(cmdhandlers) / sizeof(cmd_data_t); i++) { 296 | if(cmdid == (uint8_t)cmdhandlers[i].type && len == cmdhandlers[i].len) { 297 | cmdhandlers[i].handler(s, data, clientfd); 298 | exec = true; 299 | } 300 | } 301 | if(!exec) { 302 | logmsg(s, LogLevelWarn, 303 | "ipc: Received invalid command with unknown command ID or invalid length (ID: %i).", cmdid); 304 | } 305 | } 306 | 307 | void* 308 | ipcserverthread(void* arg) { 309 | state_t* s = (state_t*)arg; 310 | 311 | int32_t serverfd, clientfd; 312 | struct sockaddr_un addr; 313 | uint8_t buf[MSGSIZE]; 314 | ssize_t numread; 315 | 316 | // Create a Unix domain socket 317 | serverfd = socket(AF_UNIX, SOCK_STREAM, 0); 318 | if (serverfd < 0) { 319 | logmsg(s, LogLevelError, "ipc: Failed to create unix domain socket for IPC."); 320 | terminate(s, EXIT_FAILURE); 321 | } 322 | 323 | // Set up the address structure 324 | memset(&addr, 0, sizeof(struct sockaddr_un)); 325 | addr.sun_family = AF_UNIX; 326 | strncpy(addr.sun_path, SOCKPATH, sizeof(addr.sun_path) - 1); 327 | 328 | // Remove any existing socket file 329 | unlink(SOCKPATH); 330 | 331 | // Bind the socket 332 | if (bind(serverfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_un)) < 0) { 333 | logmsg(s, LogLevelError, "ipc: Failed to bind the IPC socket."); 334 | close(serverfd); 335 | terminate(s, EXIT_FAILURE); 336 | } 337 | 338 | // Listen for connections 339 | if (listen(serverfd, 5) < 0) { 340 | logmsg(s, LogLevelError, "ipc: Failed to listen for IPC connections."); 341 | close(serverfd); 342 | terminate(s, EXIT_FAILURE); 343 | } 344 | 345 | logmsg(s, LogLevelTrace, "ipc: Server is listening for IPC connections."); 346 | 347 | while (true) { 348 | // Accept a new client connection 349 | clientfd = accept(serverfd, NULL, NULL); 350 | if (clientfd < 0) { 351 | logmsg(s, LogLevelTrace, "ipc: Failed to accept IPC client connection."); 352 | close(serverfd); 353 | continue; 354 | } 355 | 356 | // Read the command ID 357 | uint8_t command_id; 358 | numread = read(clientfd, &command_id, sizeof(command_id)); 359 | if (numread != sizeof(command_id)) { 360 | logmsg(s, LogLevelTrace, "ipc: Failed to read command ID of IPC client with FD: %i", clientfd); 361 | close(clientfd); 362 | continue; 363 | } 364 | 365 | uint32_t len; 366 | numread = read(clientfd, &len, sizeof(len)); 367 | if (numread != sizeof(len)) { 368 | logmsg(s, LogLevelTrace, "ipc: Failed to read data length of IPC client with FD: %i", clientfd); 369 | close(clientfd); 370 | continue; 371 | } 372 | 373 | len = ntohl(len); 374 | 375 | if (len > sizeof(buf) - ((sizeof(command_id) + sizeof(len)))) { 376 | logmsg(s, LogLevelTrace, 377 | "ipc: Data length of IPC client with FD: %i is too large to fit into the data buffer.", clientfd); 378 | close(clientfd); 379 | continue; 380 | } 381 | 382 | numread = read(clientfd, buf, len); 383 | if (numread != (ssize_t)len) { 384 | logmsg(s, LogLevelTrace, 385 | "ipc: Failed to read data of client with FD: %i", clientfd); 386 | close(clientfd); 387 | continue; 388 | } 389 | 390 | // Handle the command 391 | handlecmd(s, command_id, buf, len, clientfd); 392 | 393 | // Close the client socket 394 | close(clientfd); 395 | } 396 | } 397 | 398 | -------------------------------------------------------------------------------- /cfg/ragnar.cfg: -------------------------------------------------------------------------------- 1 | # ██████ █████ ██████ ███ ██ █████ ██████ ██████ ██████ ███ ██ ███████ ██ ██████ 2 | # ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ 3 | # ██████ ███████ ██ ███ ██ ██ ██ ███████ ██████ ██ ██ ██ ██ ██ ██ █████ ██ ██ ███ 4 | # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ 5 | # ██ ██ ██ ██ ██████ ██ ████ ██ ██ ██ ██ ██████ ██████ ██ ████ ██ ██ ██████ 6 | 7 | # Options: 8 | # ---------------------- 9 | # Modifiers: 10 | # - "Shift" 11 | # - "Control" 12 | # - "Alt" 13 | # - "Super" 14 | # Mouse buttons: 15 | # - "LeftMouse" 16 | # - "MiddleMouse" 17 | # - "RightMouse" 18 | # Layout types: 19 | # - "LayoutFloating" 20 | # - "LayoutTiledMaster" 21 | # - "LayoutVerticalStripes" 22 | # - "LayoutHorizontalStripes" 23 | # Cursor images: 24 | # - "arrow" 25 | # - "based_arrow" 26 | # - "cross" 27 | # - "hand1" 28 | # - "hand2" 29 | # - "double_arrow" 30 | # - "top_left_arrow" 31 | # - "bottom_right_arrow" 32 | # - "left_ptr" 33 | # - "right_ptr" 34 | # - "xterm" 35 | # Keycallback functions: 36 | # - terminate_successfully 37 | # - cyclefocusdown 38 | # - cyclefocusup 39 | # - killfocus 40 | # - togglefullscreen 41 | # - raisefocus 42 | # - cycledesktopup 43 | # - cycledesktopdown 44 | # - cyclefocusdesktopup 45 | # - cyclefocusdesktopdown 46 | # - switchdesktop 47 | # - switchfocusdesktop 48 | # - runcmd 49 | # - addfocustolayout 50 | # - settiledmaster 51 | # - setverticalstripes 52 | # - sethorizontalstripes 53 | # - setfloatingmode 54 | # - updatebarslayout 55 | # - cycledownlayout 56 | # - addmasterlayout 57 | # - removemasterlayout 58 | # - incmasterarealayout 59 | # - decmasterarealayout 60 | # - incgapsizelayout 61 | # - decgapsizelayout 62 | # - inclayoutsizefocus 63 | # - declayoutsizefocus 64 | # - movefocusup 65 | # - movefocusdown 66 | # - movefocusleft 67 | # - movefocusright 68 | # - cyclefocusmonitordown 69 | # - cyclefocusmonitorup 70 | # - togglescratchpad 71 | # 72 | # ---------------------- 73 | # NOTE: For all key options, see: 74 | # https://github.com/cococry/ragnar/blob/main/src/structs.h#L89 75 | # ---------------------- 76 | # 77 | 78 | # Specifies the width of the border around client 79 | # windows 80 | win_border_width = 0; 81 | # Specifies the color of the border around client 82 | # windows 83 | win_border_color = 0xE6DFDC; 84 | # Specifies the color of the border around 85 | # selected/focused client windows 86 | win_border_color_selected = 0xE6DFDC; 87 | 88 | # Specifies the main modifier key that is 89 | # used to execute window manager shortcuts 90 | mod_key = "Super"; 91 | # Specifies the modifier key that is used 92 | # to interact with clients windows 93 | win_mod = "Super"; 94 | 95 | # Specfies the mouse button that needs to be 96 | # held in order to move client windows 97 | move_button = "LeftMouse"; 98 | # Specfies the mouse button that needs to be 99 | # held in order to resize client windows 100 | resize_button = "RightMouse"; 101 | 102 | # Specifies the desktop index that is initially 103 | # selected on every monitor 104 | initial_desktop = 0; 105 | # Specfies the number of allocated virtual 106 | # desktops 107 | num_desktops = 9; 108 | # Specifies the name of every virtual desktop 109 | # in order. 110 | desktop_names = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]; 111 | 112 | # Specifies whether or not server-side window 113 | # decorations should be enabled. 114 | use_decoration = false; 115 | # Specifies whether or not server-side titlebars 116 | # should be shown on startup. (Ignored when 117 | # use_decoration is set to false.) 118 | show_titlebars_init = false; 119 | 120 | # Specifies the height (in pixels) of the 121 | # titlebar of client windows. 122 | titlebar_height = 30 123 | # Specifies the color of titlebars of 124 | # client windows 125 | titlebar_color = 0xffffff; 126 | 127 | # Specifies the color of the font that is used 128 | # across the window manager's UI 129 | font_color = 0xff0000ff; 130 | # Specifies the path to the font file to use as 131 | # the window manager's font 132 | font_path = "/usr/share/fonts/TTF/JetBrainsMonoNerdFont-Bold.ttf"; 133 | 134 | # Specifies the area that the master window takes 135 | # up in master-slave layouts initially. 136 | # (in 0.0-1.0 %) 137 | layout_master_area = 0.5; 138 | # Specifies the minimum area that master windows 139 | # need to take up in master-slave layouts 140 | # (in 0.0-1.0 %) 141 | layout_master_area_min = 0.1; 142 | # Specifies the maximum a2rea that master windows 143 | # can take up in master-slave layouts 144 | # (in 0.0-1.0 %) 145 | layout_master_area_max = 0.9; 146 | # Specifies the amount that the master area changes/steps 147 | # when it is decreased/increased. 148 | # (in 0.0-1.0 %) 149 | layout_master_area_step = 0.1; 150 | 151 | # Specifies the amount that areas of windows 152 | # within layouts change when they are increased/decreased 153 | # (in px) 154 | layout_size_step = 100.0; 155 | # Specifies the minimum area that windows within 156 | # layouts need to take up 157 | # (in px) 158 | layout_size_min = 150.0; 159 | 160 | # Specifies the amount that windows are moved 161 | # by when using 'move' shortcuts for floating windows 162 | # (in px) 163 | key_win_move_step = 100.0; 164 | 165 | # Specifies the initial gap between windows 166 | # within layouts (in px) 167 | win_layout_gap = 5; 168 | # Specifies the maximum gap that windows within 169 | # layouts can have around each other (in px) 170 | win_layout_gap_max = 150; 171 | # Specifies the amount that the gap between 172 | # non-floating windows changes when it's 173 | # decreased/increased (in px) 174 | # (in px) 175 | win_layout_gap_step = 5; 176 | 177 | # Specifies the layout that is initially used 178 | # for every virtual desktop 179 | initial_layout = "LayoutTiledMaster"; 180 | 181 | # Advanced Configuration 182 | # -------------------------------- 183 | 184 | # Specifies the framerate at which motion notify 185 | # events are captured. This is used to streamline 186 | # performance. Especially on high polling rate mouses, 187 | # lag can be very noticable when not throtteling motion 188 | # notify events. 189 | motion_notify_debounce_fps = 60; 190 | 191 | # Specifies the maximum number of 'strut'-window that 192 | # the window manager can capture. Struts are information 193 | # about window positions and sizes that are used to correctly 194 | # establish window layouts with bars or other status windows. 195 | max_struts = 8; 196 | 197 | # Specifies if decoration that is rendered with OpenGL should 198 | # use Vsync 199 | gl_vsync = false; 200 | 201 | # Specifies the file where to log messages to. 202 | # (Ignored if 'log_messages' or 'should_log_to_file' 203 | # are disabled.) 204 | log_file = "/home/cococry/ragnarwm.log"; 205 | 206 | # Specifies the cursor image to use for the root window 207 | cursor_image = "arrow"; 208 | 209 | # Specifies whether or not to log messages 210 | # to the console 211 | log_messages = true; 212 | 213 | # Specifies whether or not to log messages 214 | # to the log file 215 | should_log_to_file = true; 216 | 217 | # Specifies the maximum number of scratchpads 218 | # that can be allocated 219 | max_scratchpads = 10; 220 | 221 | # -------------------------------- 222 | 223 | # =========== Key bindings =========== 224 | 225 | keybinds = ( 226 | { 227 | mod = "Super"; 228 | key = "KeyEscape"; 229 | do = "terminate_successfully"; 230 | }, 231 | { 232 | mod = "%mod_key"; 233 | key = "KeyTab"; 234 | do = "cyclefocusdown"; 235 | }, 236 | { 237 | mod = "%mod_key | Shift"; 238 | key = "KeyTab"; 239 | do = "cyclefocusup"; 240 | }, 241 | { 242 | mod = "%mod_key"; 243 | key = "KeyQ"; 244 | do = "killfocus"; 245 | }, 246 | { 247 | mod = "%mod_key"; 248 | key = "KeyF"; 249 | do = "togglefullscreen"; 250 | }, 251 | { 252 | mod = "%mod_key"; 253 | key = "KeyR"; 254 | do = "raisefocus"; 255 | }, 256 | { 257 | mod = "%mod_key"; 258 | key = "KeyR"; 259 | do = "raisefocus"; 260 | }, 261 | { 262 | mod = "%mod_key | Shift"; 263 | key = "KeyW"; 264 | do = "movefocusup"; 265 | }, 266 | { 267 | mod = "%mod_key | Shift"; 268 | key = "KeyA"; 269 | do = "movefocusleft"; 270 | }, 271 | { 272 | mod = "%mod_key | Shift"; 273 | key = "KeyS"; 274 | do = "movefocusdown"; 275 | }, 276 | { 277 | mod = "%mod_key | Shift"; 278 | key = "KeyD"; 279 | do = "movefocusright"; 280 | }, 281 | { 282 | mod = "%mod_key"; 283 | key = "KeyRight"; 284 | do = "cyclefocusmonitorup"; 285 | }, 286 | { 287 | mod = "%mod_key"; 288 | key = "KeyLeft"; 289 | do = "cyclefocusmonitordown"; 290 | }, 291 | { 292 | mod = "%mod_key"; 293 | key = "KeyD"; 294 | do = "cycledesktopup"; 295 | }, 296 | { 297 | mod = "%mod_key"; 298 | key = "KeyA"; 299 | do = "cycledesktopdown"; 300 | }, 301 | { 302 | mod = "%mod_key"; 303 | key = "KeyP"; 304 | do = "cyclefocusdesktopup"; 305 | }, 306 | { 307 | mod = "%mod_key"; 308 | key = "KeyO"; 309 | do = "cyclefocusdesktopdown"; 310 | }, 311 | { 312 | mod = "%mod_key"; 313 | key = "Key1"; 314 | do = "switchdesktop"; 315 | i = 0; 316 | }, 317 | { 318 | mod = "%mod_key"; 319 | key = "Key2"; 320 | do = "switchdesktop"; 321 | i = 1; 322 | }, 323 | { 324 | mod = "%mod_key"; 325 | key = "Key3"; 326 | do = "switchdesktop"; 327 | i = 2; 328 | }, 329 | { 330 | mod = "%mod_key"; 331 | key = "Key4"; 332 | do = "switchdesktop"; 333 | i = 3; 334 | }, 335 | { 336 | mod = "%mod_key"; 337 | key = "Key5"; 338 | do = "switchdesktop"; 339 | i = 4; 340 | }, 341 | { 342 | mod = "%mod_key"; 343 | key = "Key6"; 344 | do = "switchdesktop"; 345 | i = 5; 346 | }, 347 | { 348 | mod = "%mod_key"; 349 | key = "Key7"; 350 | do = "switchdesktop"; 351 | i = 6; 352 | }, 353 | { 354 | mod = "%mod_key"; 355 | key = "Key8"; 356 | do = "switchdesktop"; 357 | i = 7; 358 | }, 359 | { 360 | mod = "%mod_key"; 361 | key = "Key9"; 362 | do = "switchdesktop"; 363 | i = 8; 364 | }, 365 | { 366 | mod = "%mod_key | Shift"; 367 | key = "Key1"; 368 | do = "switchfocusdesktop"; 369 | i = 0; 370 | }, 371 | { 372 | mod = "%mod_key | Shift"; 373 | key = "Key2"; 374 | do = "switchfocusdesktop"; 375 | i = 1; 376 | }, 377 | { 378 | mod = "%mod_key | Shift"; 379 | key = "Key3"; 380 | do = "switchfocusdesktop"; 381 | i = 2; 382 | }, 383 | { 384 | mod = "%mod_key | Shift"; 385 | key = "Key4"; 386 | do = "switchfocusdesktop"; 387 | i = 3; 388 | }, 389 | { 390 | mod = "%mod_key | Shift"; 391 | key = "Key5"; 392 | do = "switchfocusdesktop"; 393 | i = 4; 394 | }, 395 | { 396 | mod = "%mod_key | Shift"; 397 | key = "Key6"; 398 | do = "switchfocusdesktop"; 399 | i = 5; 400 | }, 401 | { 402 | mod = "%mod_key | Shift"; 403 | key = "Key7"; 404 | do = "switchfocusdesktop"; 405 | i = 6; 406 | }, 407 | { 408 | mod = "%mod_key | Shift"; 409 | key = "Key8"; 410 | do = "switchfocusdesktop"; 411 | i = 7; 412 | }, 413 | { 414 | mod = "%mod_key | Shift"; 415 | key = "Key9"; 416 | do = "switchfocusdesktop"; 417 | i = 8; 418 | }, 419 | { 420 | mod = "%mod_key | Shift"; 421 | key = "KeyT"; 422 | do = "settiledmaster"; 423 | }, 424 | { 425 | mod = "%mod_key | Shift"; 426 | key = "KeyR"; 427 | do = "setfloatingmode"; 428 | }, 429 | { 430 | mod = "%mod_key | Shift"; 431 | key = "KeyH"; 432 | do = "sethorizontalstripes"; 433 | }, 434 | { 435 | mod = "%mod_key | Shift"; 436 | key = "KeyV"; 437 | do = "setverticalstripes"; 438 | }, 439 | { 440 | mod = "%mod_key"; 441 | key = "KeySpace"; 442 | do = "addfocustolayout"; 443 | }, 444 | { 445 | mod = "%mod_key | Shift"; 446 | key = "KeyB"; 447 | do = "updatebarslayout"; 448 | }, 449 | { 450 | mod = "%mod_key"; 451 | key = "KeyJ"; 452 | do = "cycledownlayout"; 453 | }, 454 | { 455 | mod = "%mod_key"; 456 | key = "KeyK"; 457 | do = "cycleuplayout"; 458 | }, 459 | { 460 | mod = "%mod_key | Shift"; 461 | key = "KeyJ"; 462 | do = "inclayoutsizefocus"; 463 | }, 464 | { 465 | mod = "%mod_key | Shift"; 466 | key = "KeyK"; 467 | do = "declayoutsizefocus"; 468 | }, 469 | { 470 | mod = "%mod_key"; 471 | key = "KeyM"; 472 | do = "addmasterlayout"; 473 | }, 474 | { 475 | mod = "%mod_key | Shift"; 476 | key = "KeyM"; 477 | do = "removemasterlayout"; 478 | }, 479 | { 480 | mod = "%mod_key"; 481 | key = "KeyH"; 482 | do = "decmasterarealayout"; 483 | }, 484 | { 485 | mod = "%mod_key"; 486 | key = "KeyL"; 487 | do = "incmasterarealayout"; 488 | }, 489 | { 490 | mod = "%mod_key"; 491 | key = "KeyMinus"; 492 | do = "decgapsizelayout"; 493 | }, 494 | { 495 | mod = "%mod_key"; 496 | key = "KeyPlus"; 497 | do = "incgapsizelayout"; 498 | }, 499 | { 500 | mod = "%mod_key"; 501 | key = "KeyReturn"; 502 | do = "runcmd"; 503 | cmd = "alacritty &"; 504 | }, 505 | { 506 | mod = "%mod_key"; 507 | key = "KeyS"; 508 | do = "runcmd"; 509 | cmd = "dmenu &"; 510 | }, 511 | { 512 | mod = "%mod_key"; 513 | key = "KeyW"; 514 | do = "runcmd"; 515 | cmd = "firefox &"; 516 | }, 517 | { 518 | mod = "%mod_key"; 519 | key = "KeyE"; 520 | do = "runcmd"; 521 | cmd = "flameshot gui &"; 522 | }, 523 | { 524 | mod = "%mod_key"; 525 | key = "KeyF1"; 526 | do = "togglescratchpad"; 527 | cmd = "alacritty &"; 528 | i = 0; 529 | }, 530 | { 531 | mod = "%mod_key"; 532 | key = "KeyF2"; 533 | do = "togglescratchpad"; 534 | cmd = "alacritty -e nvim &"; 535 | i = 1; 536 | }, 537 | { 538 | mod = "%mod_key"; 539 | key = "KeyF3"; 540 | do = "togglescratchpad"; 541 | cmd = "alacritty -e mocp &"; 542 | i = 2; 543 | }, 544 | { 545 | mod = "%mod_key"; 546 | key = "KeyC"; 547 | do = "reloadconfigfile"; 548 | }, 549 | { 550 | mod = "%mod_key"; 551 | key = "KeyAudioLowerVolume"; 552 | do = "runcmd"; 553 | cmd = "amixer sset Master 5%-"; 554 | }, 555 | { 556 | mod = "%mod_key"; 557 | key = "KeyAudioRaiseVolume"; 558 | do = "runcmd"; 559 | cmd = "amixer sset Master 5%+"; 560 | }, 561 | { 562 | mod = "%mod_key"; 563 | key = "KeyAudioMute"; 564 | do = "runcmd"; 565 | cmd = "amixer sset Master toggle"; 566 | }, 567 | ); 568 | -------------------------------------------------------------------------------- /src/structs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #define EDGE_WIDTH 5 16 | 17 | typedef struct state_t state_t; 18 | typedef struct passthrough_data_t passthrough_data_t; 19 | 20 | void terminate_successfully(state_t* s, passthrough_data_t data); 21 | void cyclefocusdown(state_t* s, passthrough_data_t data); 22 | void cyclefocusup(state_t* s, passthrough_data_t data); 23 | void killfocus(state_t* s, passthrough_data_t data); 24 | void togglefullscreen(state_t* s, passthrough_data_t data); 25 | void raisefocus(state_t* s, passthrough_data_t data); 26 | void cycledesktopup(state_t* s, passthrough_data_t data); 27 | void cycledesktopdown(state_t* s, passthrough_data_t data); 28 | void cyclefocusdesktopup(state_t* s, passthrough_data_t data); 29 | void cyclefocusdesktopdown(state_t* s, passthrough_data_t data); 30 | void switchdesktop(state_t* s, passthrough_data_t data); 31 | void switchfocusdesktop(state_t* s, passthrough_data_t data); 32 | void runcmd(state_t* s, passthrough_data_t data); 33 | void addfocustolayout(state_t* s, passthrough_data_t data); 34 | void settiledmaster(state_t* s, passthrough_data_t data); 35 | void setverticalstripes(state_t* s, passthrough_data_t data); 36 | void sethorizontalstripes(state_t* s, passthrough_data_t data); 37 | void setfloatingmode(state_t* s, passthrough_data_t data); 38 | void updatebarslayout(state_t* s, passthrough_data_t data); 39 | void cycledownlayout(state_t* s, passthrough_data_t data); 40 | void cycleuplayout(state_t* s, passthrough_data_t data); 41 | void addmasterlayout(state_t* s, passthrough_data_t data); 42 | void removemasterlayout(state_t* s, passthrough_data_t data); 43 | void incmasterarealayout(state_t* s, passthrough_data_t data); 44 | void decmasterarealayout(state_t* s, passthrough_data_t data); 45 | void incgapsizelayout(state_t* s, passthrough_data_t data); 46 | void decgapsizelayout(state_t* s, passthrough_data_t data); 47 | void inclayoutsizefocus(state_t* s, passthrough_data_t data); 48 | void declayoutsizefocus(state_t* s, passthrough_data_t data); 49 | void movefocusup(state_t* s, passthrough_data_t data); 50 | void movefocusdown(state_t* s, passthrough_data_t data); 51 | void movefocusleft(state_t* s, passthrough_data_t data); 52 | void movefocusright(state_t* s, passthrough_data_t data); 53 | void cyclefocusmonitordown(state_t* s, passthrough_data_t data); 54 | void cyclefocusmonitorup(state_t* s, passthrough_data_t data); 55 | void togglescratchpad(state_t* s, passthrough_data_t data); 56 | void reloadconfigfile(state_t* s, passthrough_data_t data); 57 | 58 | #define _XCB_EV_LAST 36 59 | 60 | /* Evaluates to the length (count of elements) in a given array */ 61 | #define ARRLEN(arr) (sizeof(arr) / sizeof(arr[0])) 62 | /* Evaluates to the minium of two given numbers */ 63 | #define MIN(a, b) (((a)<(b))?(a):(b)) 64 | /* Evaluates to the maximum of two given numbers */ 65 | #define MAX(a, b) (((a)>(b))?(a):(b)) 66 | 67 | #define MWM_HINTS_DECORATIONS 2 68 | #define MWM_DECOR_ALL 1 69 | 70 | #define VEC_INIT_CAP 4 71 | 72 | #define vector_init(vec) \ 73 | do { \ 74 | (vec)->items = malloc(sizeof(*(vec)) * VEC_INIT_CAP); \ 75 | (vec)->size = 0; \ 76 | (vec)->cap = 0; \ 77 | } while (0) 78 | 79 | #define vector_free(vec) \ 80 | do { \ 81 | if((vec)->items) { \ 82 | free((vec)->items); \ 83 | (vec)->items = NULL; \ 84 | } \ 85 | (vec)->size = 0; \ 86 | (vec)->cap = 0; \ 87 | } while (0) 88 | 89 | #define vector_remove_by_idx(vec, idx) \ 90 | do { \ 91 | for (uint32_t _i = (idx); _i < (vec)->size - 1; _i++) { \ 92 | (vec)->items[_i] = (vec)->items[_i + 1]; \ 93 | } \ 94 | (vec)->size--; \ 95 | } while (0) 96 | 97 | #define vector_append(vec, item) \ 98 | do { \ 99 | if ((vec)->size >= (vec)->cap) { \ 100 | (vec)->cap = (vec)->cap == 0 ? VEC_INIT_CAP : (vec)->cap*2; \ 101 | (vec)->items = realloc((vec)->items, (vec)->cap*sizeof(*(vec)->items)); \ 102 | } \ 103 | (vec)->items[(vec)->size++] = (item); \ 104 | } while (0) 105 | 106 | 107 | 108 | typedef enum { 109 | Shift = XCB_MOD_MASK_SHIFT, 110 | Control = XCB_MOD_MASK_CONTROL, 111 | Alt = XCB_MOD_MASK_1, 112 | Super = XCB_MOD_MASK_4 113 | } kb_modifier_t; 114 | 115 | typedef enum { 116 | LeftMouse = XCB_BUTTON_MASK_1, 117 | MiddleMouse = XCB_BUTTON_MASK_2, 118 | RightMouse = XCB_BUTTON_MASK_3, 119 | } mousebtn_t; 120 | 121 | typedef enum { 122 | LayoutFloating = 0, 123 | LayoutTiledMaster, 124 | LayoutVerticalStripes, 125 | LayoutHorizontalStripes 126 | } layout_type_t; 127 | 128 | typedef enum { 129 | LayeringOrderNormal = 0, 130 | LayeringOrderBelow, 131 | LayeringOrderAbove, 132 | } layering_order_t; 133 | 134 | typedef enum { 135 | EWMHsupported = 0, 136 | EWMHname, 137 | EWMHstate, 138 | EWMHstateHidden, 139 | EWMHstateAbove, 140 | EWMHstateBelow, 141 | EWMHcheck, 142 | EWMHfullscreen, 143 | EWMHactiveWindow, 144 | EWMHwindowType, 145 | EWMHwindowTypeDialog, 146 | EWMHwindowTypePopup, 147 | EWMHclientList, 148 | EWMHcurrentDesktop, 149 | EWMHnumberOfDesktops, 150 | EWMHdesktopNames, 151 | EWMHcount 152 | } ewmh_atom_t; 153 | 154 | typedef enum { 155 | WMprotocols = 0, 156 | WMdelete, 157 | WMstate, 158 | WMtakeFocus, 159 | WMcount 160 | } wm_atom_t; 161 | 162 | typedef enum { 163 | LogLevelTrace = 0, 164 | LogLevelWarn, 165 | LogLevelError 166 | } log_level_t; 167 | 168 | struct passthrough_data_t { 169 | const char* cmd; 170 | int32_t i; 171 | }; 172 | 173 | typedef struct state_t state_t; 174 | 175 | typedef void (*keycallback_t)(state_t* s, passthrough_data_t data); 176 | 177 | 178 | typedef struct { 179 | uint16_t modmask; 180 | xcb_keysym_t key; 181 | keycallback_t cb; 182 | passthrough_data_t data; 183 | } keybind_t; 184 | 185 | typedef struct { 186 | uint32_t flags; 187 | uint32_t functions; 188 | uint32_t decorations; 189 | int32_t input_mode; 190 | uint32_t status; 191 | } motif_wm_hints_t; 192 | 193 | typedef enum { 194 | KeyVoidSymbol = XK_VoidSymbol, 195 | KeyBackSpace = XK_BackSpace, 196 | KeyTab = XK_Tab, 197 | KeyLinefeed = XK_Linefeed, 198 | KeyClear = XK_Clear, 199 | KeyReturn = XK_Return, 200 | KeyPause = XK_Pause, 201 | KeyScroll_Lock = XK_Scroll_Lock, 202 | KeySys_Req = XK_Sys_Req, 203 | KeyEscape = XK_Escape, 204 | KeyDelete = XK_Delete, 205 | KeyHome = XK_Home, 206 | KeyLeft = XK_Left, 207 | KeyUp = XK_Up, 208 | KeyRight = XK_Right, 209 | KeyDown = XK_Down, 210 | KeyPage_Up = XK_Page_Up, 211 | KeyPage_Down = XK_Page_Down, 212 | KeyEnd = XK_End, 213 | KeyBegin = XK_Begin, 214 | KeySpace = XK_space, 215 | KeyExclam = XK_exclam, 216 | KeyQuotedbl = XK_quotedbl, 217 | KeyNumberSign = XK_numbersign, 218 | KeyDollar = XK_dollar, 219 | KeyPercent = XK_percent, 220 | KeyAmpersand = XK_ampersand, 221 | KeyApostrophe = XK_apostrophe, 222 | KeyParenleft = XK_parenleft, 223 | KeyParenright = XK_parenright, 224 | KeyAsterisk = XK_asterisk, 225 | KeyPlus = XK_plus, 226 | KeyComma = XK_comma, 227 | KeyMinus = XK_minus, 228 | KeyPeriod = XK_period, 229 | KeySlash = XK_slash, 230 | Key0 = XK_0, 231 | Key1 = XK_1, 232 | Key2 = XK_2, 233 | Key3 = XK_3, 234 | Key4 = XK_4, 235 | Key5 = XK_5, 236 | Key6 = XK_6, 237 | Key7 = XK_7, 238 | Key8 = XK_8, 239 | Key9 = XK_9, 240 | KeyColon = XK_colon, 241 | KeySemicolon = XK_semicolon, 242 | KeyLess = XK_less, 243 | KeyEqual = XK_equal, 244 | KeyGreater = XK_greater, 245 | KeyQuestion = XK_question, 246 | KeyAt = XK_at, 247 | KeyBracketLeft = XK_bracketleft, 248 | KeyBackslash = XK_backslash, 249 | KeyBracketRight = XK_bracketright, 250 | KeyAsciiCircum = XK_asciicircum, 251 | KeyUnderscore = XK_underscore, 252 | KeyGrave = XK_grave, 253 | KeyA = XK_a, 254 | KeyB = XK_b, 255 | KeyC = XK_c, 256 | KeyD = XK_d, 257 | KeyE = XK_e, 258 | KeyF = XK_f, 259 | KeyG = XK_g, 260 | KeyH = XK_h, 261 | KeyI = XK_i, 262 | KeyJ = XK_j, 263 | KeyK = XK_k, 264 | KeyL = XK_l, 265 | KeyM = XK_m, 266 | KeyN = XK_n, 267 | KeyO = XK_o, 268 | KeyP = XK_p, 269 | KeyQ = XK_q, 270 | KeyR = XK_r, 271 | KeyS = XK_s, 272 | KeyT = XK_t, 273 | KeyU = XK_u, 274 | KeyV = XK_v, 275 | KeyW = XK_w, 276 | KeyX = XK_x, 277 | KeyY = XK_y, 278 | KeyZ = XK_z, 279 | KeyBraceLeft = XK_braceleft, 280 | KeyBar = XK_bar, 281 | KeyBraceRight = XK_braceright, 282 | KeyAsciiTilde = XK_asciitilde, 283 | KeyNum_Lock = XK_Num_Lock, 284 | KeyKP_0 = XK_KP_0, 285 | KeyKP_1 = XK_KP_1, 286 | KeyKP_2 = XK_KP_2, 287 | KeyKP_3 = XK_KP_3, 288 | KeyKP_4 = XK_KP_4, 289 | KeyKP_5 = XK_KP_5, 290 | KeyKP_6 = XK_KP_6, 291 | KeyKP_7 = XK_KP_7, 292 | KeyKP_8 = XK_KP_8, 293 | KeyKP_9 = XK_KP_9, 294 | KeyKP_Decimal = XK_KP_Decimal, 295 | KeyKP_Divide = XK_KP_Divide, 296 | KeyKP_Multiply = XK_KP_Multiply, 297 | KeyKP_Subtract = XK_KP_Subtract, 298 | KeyKP_Add = XK_KP_Add, 299 | KeyKP_Enter = XK_KP_Enter, 300 | KeyKP_Equal = XK_KP_Equal, 301 | KeyF1 = XK_F1, 302 | KeyF2 = XK_F2, 303 | KeyF3 = XK_F3, 304 | KeyF4 = XK_F4, 305 | KeyF5 = XK_F5, 306 | KeyF6 = XK_F6, 307 | KeyF7 = XK_F7, 308 | KeyF8 = XK_F8, 309 | KeyF9 = XK_F9, 310 | KeyF10 = XK_F10, 311 | KeyF11 = XK_F11, 312 | KeyF12 = XK_F12, 313 | KeyF13 = XK_F13, 314 | KeyF14 = XK_F14, 315 | KeyF15 = XK_F15, 316 | KeyF16 = XK_F16, 317 | KeyF17 = XK_F17, 318 | KeyF18 = XK_F18, 319 | KeyF19 = XK_F19, 320 | KeyF20 = XK_F20, 321 | KeyF21 = XK_F21, 322 | KeyF22 = XK_F22, 323 | KeyF23 = XK_F23, 324 | KeyF24 = XK_F24, 325 | KeyF25 = XK_F25, 326 | KeyF26 = XK_F26, 327 | KeyF27 = XK_F27, 328 | KeyF28 = XK_F28, 329 | KeyF29 = XK_F29, 330 | KeyF30 = XK_F30, 331 | KeyF31 = XK_F31, 332 | KeyF32 = XK_F32, 333 | KeyF33 = XK_F33, 334 | KeyF34 = XK_F34, 335 | KeyF35 = XK_F35, 336 | KeyShift_L = XK_Shift_L, 337 | KeyShift_R = XK_Shift_R, 338 | KeyControl_L = XK_Control_L, 339 | KeyControl_R = XK_Control_R, 340 | KeyCaps_Lock = XK_Caps_Lock, 341 | KeyShift_Lock = XK_Shift_Lock, 342 | KeyMeta_L = XK_Meta_L, 343 | KeyMeta_R = XK_Meta_R, 344 | KeyAlt_L = XK_Alt_L, 345 | KeyAlt_R = XK_Alt_R, 346 | KeySuper_L = XK_Super_L, 347 | KeySuper_R = XK_Super_R, 348 | KeyHyper_L = XK_Hyper_L, 349 | KeyHyper_R = XK_Hyper_R, 350 | } keycode_t; 351 | 352 | typedef struct { 353 | float x, y; 354 | } v2_t; 355 | 356 | typedef struct { 357 | v2_t pos, size; 358 | } area_t; 359 | 360 | typedef struct { 361 | uint32_t left, right, top, bottom; 362 | int32_t startx, endx; 363 | int32_t starty, endy; 364 | } strut_t; 365 | 366 | typedef struct monitor_t monitor_t; 367 | 368 | typedef struct { 369 | uint32_t nmaster; 370 | float masterarea; 371 | int32_t gapsize; 372 | layout_type_t curlayout; 373 | bool mastermaxed; 374 | } layout_props_t; 375 | 376 | 377 | typedef struct client_t client_t; 378 | 379 | typedef enum { 380 | EdgeNone = 0, 381 | EdgeLeft, 382 | EdgeRight, 383 | EdgeTop, 384 | EdgeBottom, 385 | EdgeTopleft, 386 | EdgeTopright, 387 | EdgeBottomleft, 388 | EdgeBottomright 389 | } window_edge_t; 390 | 391 | typedef struct { 392 | xcb_window_t win; 393 | window_edge_t edge; 394 | } edgegrab_t; 395 | 396 | struct client_t { 397 | area_t area, area_prev; 398 | bool fullscreen, floating, floating_prev, 399 | fixed, hidden; 400 | 401 | bool is_scratchpad; 402 | 403 | int32_t scratchpad_index; 404 | 405 | xcb_window_t win, frame; 406 | 407 | v2_t closebutton, layoutbutton; 408 | 409 | client_t* next; 410 | 411 | size_t borderwidth; 412 | 413 | monitor_t* mon; 414 | uint32_t desktop; 415 | 416 | edgegrab_t* edges; 417 | 418 | bool urgent, ignoreunmap, ignoreexpose, decorated, neverfocus, showedgewindows; 419 | 420 | v2_t minsize; 421 | v2_t maxsize; 422 | 423 | float layoutsizeadd; 424 | 425 | char* name; 426 | }; 427 | 428 | typedef struct { 429 | uint32_t idx; 430 | } desktop_t; 431 | typedef struct { 432 | char* name; 433 | bool init; 434 | } named_desktop_t; 435 | 436 | struct monitor_t { 437 | area_t area; 438 | monitor_t* next; 439 | uint32_t idx; 440 | 441 | named_desktop_t* activedesktops; 442 | uint32_t desktopcount; 443 | layout_props_t* layouts; 444 | 445 | client_t* clients; 446 | }; 447 | 448 | typedef struct { 449 | uint32_t maxstruts; 450 | uint32_t maxdesktops; 451 | uint32_t maxscratchpads; 452 | 453 | uint32_t winborderwidth; 454 | uint32_t winbordercolor; 455 | uint32_t winbordercolor_selected; 456 | 457 | kb_modifier_t modkey; 458 | kb_modifier_t winmod; 459 | 460 | mousebtn_t movebtn; 461 | mousebtn_t resizebtn; 462 | 463 | uint32_t desktopinit; 464 | 465 | char** desktopnames; 466 | 467 | bool usedecoration; 468 | 469 | double layoutmasterarea; 470 | double layoutmasterarea_min; 471 | double layoutmasterarea_max; 472 | double layoutmasterarea_step; 473 | 474 | double layoutsize_step; 475 | double layoutsize_min; 476 | 477 | double keywinmove_step; 478 | 479 | int32_t winlayoutgap; 480 | int32_t winlayoutgap_max; 481 | int32_t winlayoutgap_step; 482 | 483 | layout_type_t initlayout; 484 | 485 | keybind_t* keybinds; 486 | uint32_t numkeybinds; 487 | 488 | uint32_t motion_notify_debounce_fps; 489 | 490 | bool glvsync; 491 | 492 | char* logfile; 493 | bool logmessages; 494 | bool shouldlogtofile; 495 | 496 | char* cursorimage; 497 | } config_data_t; 498 | 499 | typedef struct { 500 | xcb_window_t win; 501 | bool hidden, needs_restart; 502 | } scratchpad_t; 503 | 504 | typedef struct { 505 | xcb_window_t* items; 506 | uint32_t size, cap; 507 | } popup_list_t; 508 | 509 | 510 | struct state_t { 511 | window_edge_t grabedge; 512 | xcb_connection_t* con; 513 | xcb_ewmh_connection_t ewmh; 514 | xcb_window_t root; 515 | xcb_screen_t* screen; 516 | 517 | float lastexposetime, lastmotiontime; 518 | 519 | Display* dsp; 520 | 521 | bool ignore_enter_layout; 522 | 523 | client_t* focus; 524 | popup_list_t popups; 525 | 526 | v2_t grabcursor; 527 | area_t grabwin; 528 | 529 | monitor_t* monitors; 530 | monitor_t* monfocus; 531 | 532 | xcb_atom_t wm_atoms[WMcount]; 533 | xcb_atom_t ewmh_atoms[EWMHcount]; 534 | 535 | desktop_t* curdesktop; 536 | 537 | strut_t* winstruts; 538 | uint32_t nwinstruts; 539 | 540 | config_data_t config; 541 | 542 | scratchpad_t* scratchpads; 543 | int32_t mapping_scratchpad_index; 544 | }; 545 | 546 | 547 | typedef void (*event_handler_t)(state_t* s, xcb_generic_event_t* ev); 548 | 549 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | #include "config.h" 2 | #include "funcs.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | typedef struct { 13 | const char *name; 14 | keycode_t value; 15 | } key_mapping_t; 16 | 17 | typedef struct { 18 | const char *name; 19 | keycallback_t cb; 20 | } key_cb_mapping_t; 21 | 22 | const key_mapping_t keymappings[] = { 23 | {"KeyVoidSymbol", KeyVoidSymbol}, 24 | {"KeyBackSpace", KeyBackSpace}, 25 | {"KeyTab", KeyTab}, 26 | {"KeyLinefeed", KeyLinefeed}, 27 | {"KeyClear", KeyClear}, 28 | {"KeyReturn", KeyReturn}, 29 | {"KeyPause", KeyPause}, 30 | {"KeyScroll_Lock", KeyScroll_Lock}, 31 | {"KeySys_Req", KeySys_Req}, 32 | {"KeyEscape", KeyEscape}, 33 | {"KeyDelete", KeyDelete}, 34 | {"KeyHome", KeyHome}, 35 | {"KeyLeft", KeyLeft}, 36 | {"KeyUp", KeyUp}, 37 | {"KeyRight", KeyRight}, 38 | {"KeyDown", KeyDown}, 39 | {"KeyPage_Up", KeyPage_Up}, 40 | {"KeyPage_Down", KeyPage_Down}, 41 | {"KeyEnd", KeyEnd}, 42 | {"KeyBegin", KeyBegin}, 43 | {"KeySpace", KeySpace}, 44 | {"KeyExclam", KeyExclam}, 45 | {"KeyQuotedbl", KeyQuotedbl}, 46 | {"KeyNumberSign", KeyNumberSign}, 47 | {"KeyDollar", KeyDollar}, 48 | {"KeyPercent", KeyPercent}, 49 | {"KeyAmpersand", KeyAmpersand}, 50 | {"KeyApostrophe", KeyApostrophe}, 51 | {"KeyParenleft", KeyParenleft}, 52 | {"KeyParenright", KeyParenright}, 53 | {"KeyAsterisk", KeyAsterisk}, 54 | {"KeyPlus", KeyPlus}, 55 | {"KeyComma", KeyComma}, 56 | {"KeyMinus", KeyMinus}, 57 | {"KeyPeriod", KeyPeriod}, 58 | {"KeySlash", KeySlash}, 59 | {"Key0", Key0}, 60 | {"Key1", Key1}, 61 | {"Key2", Key2}, 62 | {"Key3", Key3}, 63 | {"Key4", Key4}, 64 | {"Key5", Key5}, 65 | {"Key6", Key6}, 66 | {"Key7", Key7}, 67 | {"Key8", Key8}, 68 | {"Key9", Key9}, 69 | {"KeyColon", KeyColon}, 70 | {"KeySemicolon", KeySemicolon}, 71 | {"KeyLess", KeyLess}, 72 | {"KeyEqual", KeyEqual}, 73 | {"KeyGreater", KeyGreater}, 74 | {"KeyQuestion", KeyQuestion}, 75 | {"KeyAt", KeyAt}, 76 | {"KeyBracketLeft", KeyBracketLeft}, 77 | {"KeyBackslash", KeyBackslash}, 78 | {"KeyBracketRight", KeyBracketRight}, 79 | {"KeyAsciiCircum", KeyAsciiCircum}, 80 | {"KeyUnderscore", KeyUnderscore}, 81 | {"KeyGrave", KeyGrave}, 82 | {"KeyA", KeyA}, 83 | {"KeyB", KeyB}, 84 | {"KeyC", KeyC}, 85 | {"KeyD", KeyD}, 86 | {"KeyE", KeyE}, 87 | {"KeyF", KeyF}, 88 | {"KeyG", KeyG}, 89 | {"KeyH", KeyH}, 90 | {"KeyI", KeyI}, 91 | {"KeyJ", KeyJ}, 92 | {"KeyK", KeyK}, 93 | {"KeyL", KeyL}, 94 | {"KeyM", KeyM}, 95 | {"KeyN", KeyN}, 96 | {"KeyO", KeyO}, 97 | {"KeyP", KeyP}, 98 | {"KeyQ", KeyQ}, 99 | {"KeyR", KeyR}, 100 | {"KeyS", KeyS}, 101 | {"KeyT", KeyT}, 102 | {"KeyU", KeyU}, 103 | {"KeyV", KeyV}, 104 | {"KeyW", KeyW}, 105 | {"KeyX", KeyX}, 106 | {"KeyY", KeyY}, 107 | {"KeyZ", KeyZ}, 108 | {"KeyBraceLeft", KeyBraceLeft}, 109 | {"KeyBar", KeyBar}, 110 | {"KeyBraceRight", KeyBraceRight}, 111 | {"KeyAsciiTilde", KeyAsciiTilde}, 112 | {"KeyNum_Lock", KeyNum_Lock}, 113 | {"KeyKP_0", KeyKP_0}, 114 | {"KeyKP_1", KeyKP_1}, 115 | {"KeyKP_2", KeyKP_2}, 116 | {"KeyKP_3", KeyKP_3}, 117 | {"KeyKP_4", KeyKP_4}, 118 | {"KeyKP_5", KeyKP_5}, 119 | {"KeyKP_6", KeyKP_6}, 120 | {"KeyKP_7", KeyKP_7}, 121 | {"KeyKP_8", KeyKP_8}, 122 | {"KeyKP_9", KeyKP_9}, 123 | {"KeyKP_Decimal", KeyKP_Decimal}, 124 | {"KeyKP_Divide", KeyKP_Divide}, 125 | {"KeyKP_Multiply", KeyKP_Multiply}, 126 | {"KeyKP_Subtract", KeyKP_Subtract}, 127 | {"KeyKP_Add", KeyKP_Add}, 128 | {"KeyKP_Enter", KeyKP_Enter}, 129 | {"KeyKP_Equal", KeyKP_Equal}, 130 | {"KeyF1", KeyF1}, 131 | {"KeyF2", KeyF2}, 132 | {"KeyF3", KeyF3}, 133 | {"KeyF4", KeyF4}, 134 | {"KeyF5", KeyF5}, 135 | {"KeyF6", KeyF6}, 136 | {"KeyF7", KeyF7}, 137 | {"KeyF8", KeyF8}, 138 | {"KeyF9", KeyF9}, 139 | {"KeyF10", KeyF10}, 140 | {"KeyF11", KeyF11}, 141 | {"KeyF12", KeyF12}, 142 | {"KeyF13", KeyF13}, 143 | {"KeyF14", KeyF14}, 144 | {"KeyF15", KeyF15}, 145 | {"KeyF16", KeyF16}, 146 | {"KeyF17", KeyF17}, 147 | {"KeyF18", KeyF18}, 148 | {"KeyF19", KeyF19}, 149 | {"KeyF20", KeyF20}, 150 | {"KeyF21", KeyF21}, 151 | {"KeyF22", KeyF22}, 152 | {"KeyF23", KeyF23}, 153 | {"KeyF24", KeyF24}, 154 | {"KeyF25", KeyF25}, 155 | {"KeyF26", KeyF26}, 156 | {"KeyF27", KeyF27}, 157 | {"KeyF28", KeyF28}, 158 | {"KeyF29", KeyF29}, 159 | {"KeyF30", KeyF30}, 160 | {"KeyF31", KeyF31}, 161 | {"KeyF32", KeyF32}, 162 | {"KeyF33", KeyF33}, 163 | {"KeyF34", KeyF34}, 164 | {"KeyF35", KeyF35}, 165 | {"KeyShift_L", KeyShift_L}, 166 | {"KeyShift_R", KeyShift_R}, 167 | {"KeyControl_L", KeyControl_L}, 168 | {"KeyControl_R", KeyControl_R}, 169 | {"KeyCaps_Lock", KeyCaps_Lock}, 170 | {"KeyShift_Lock", KeyShift_Lock}, 171 | {"KeyMeta_L", KeyMeta_L}, 172 | {"KeyMeta_R", KeyMeta_R}, 173 | {"KeyAlt_L", KeyAlt_L}, 174 | {"KeyAlt_R", KeyAlt_R}, 175 | {"KeySuper_L", KeySuper_L}, 176 | {"KeySuper_R", KeySuper_R}, 177 | {"KeyHyper_L", KeyHyper_L}, 178 | {"KeyHyper_R", KeyHyper_R}, 179 | {"KeyLowerAudio", KeyHyper_R}, 180 | 181 | 182 | {"KeyAudioLowerVolume", XF86XK_AudioLowerVolume}, 183 | {"KeyAudioRaiseVolume", XF86XK_AudioRaiseVolume}, 184 | {"KeyAudioMute", XF86XK_AudioMute}, 185 | {"KeyAudioPlay", XF86XK_AudioPlay}, 186 | {"KeyAudioStop", XF86XK_AudioStop}, 187 | {"KeyAudioPrev", XF86XK_AudioPrev}, 188 | {"KeyAudioNext", XF86XK_AudioNext}, 189 | {"KeyAudioRecord", XF86XK_AudioRecord}, 190 | {"KeyAudioRewind", XF86XK_AudioRewind}, 191 | {"KeyAudioForward", XF86XK_AudioForward}, 192 | {"KeyAudioPause", XF86XK_AudioPause}, 193 | 194 | {"KeyLaunchMail", XF86XK_Mail}, 195 | {"KeyLaunchMedia", XF86XK_AudioMedia}, 196 | {"KeyLaunchHome", XF86XK_HomePage}, 197 | {"KeyLaunchFavorites", XF86XK_Favorites}, 198 | {"KeyLaunchSearch", XF86XK_Search}, 199 | 200 | {"KeyBrightnessUp", XF86XK_MonBrightnessUp}, 201 | {"KeyBrightnessDown", XF86XK_MonBrightnessDown}, 202 | {"KeyScreenSaver", XF86XK_ScreenSaver}, 203 | {"KeySleep", XF86XK_Sleep}, 204 | {"KeyPowerOff", XF86XK_PowerOff}, 205 | {"KeyWakeUp", XF86XK_WakeUp}, 206 | 207 | {"KeyCalculator", XF86XK_Calculator}, 208 | {"KeyFileManager", XF86XK_Explorer}, 209 | {"KeyTerminal", XF86XK_Terminal}, 210 | {"KeyWWW", XF86XK_WWW}, 211 | {"KeyMail", XF86XK_Mail}, 212 | 213 | {"KeyBattery", XF86XK_Battery}, 214 | {"KeyBluetooth", XF86XK_Bluetooth}, 215 | {"KeyWLAN", XF86XK_WLAN}, 216 | {"KeyTouchpadToggle", XF86XK_TouchpadToggle}, 217 | {"KeyTouchpadOn", XF86XK_TouchpadOn}, 218 | {"KeyTouchpadOff", XF86XK_TouchpadOff}, 219 | 220 | {"KeyEject", XF86XK_Eject}, 221 | {"KeyRotateWindows", XF86XK_RotateWindows}, 222 | {"KeyClose", XF86XK_Close}, 223 | 224 | {"KeyAudioMicMute", XF86XK_AudioMicMute}, 225 | {"KeyKbdBrightnessUp", XF86XK_KbdBrightnessUp}, 226 | {"KeyKbdBrightnessDown", XF86XK_KbdBrightnessDown}, 227 | {"KeyKbdLightOnOff", XF86XK_KbdLightOnOff} 228 | }; 229 | const key_cb_mapping_t keycbmappings[] = { 230 | {"terminate_successfully", terminate_successfully}, 231 | {"cyclefocusdown", cyclefocusdown}, 232 | {"cyclefocusup", cyclefocusup}, 233 | {"killfocus", killfocus}, 234 | {"togglefullscreen", togglefullscreen}, 235 | {"raisefocus", raisefocus}, 236 | {"cycledesktopup", cycledesktopup}, 237 | {"cycledesktopdown", cycledesktopdown}, 238 | {"cyclefocusdesktopup", cyclefocusdesktopup}, 239 | {"cyclefocusdesktopdown", cyclefocusdesktopdown}, 240 | {"switchdesktop", switchdesktop}, 241 | {"switchfocusdesktop", switchfocusdesktop}, 242 | {"runcmd", runcmd}, 243 | {"addfocustolayout", addfocustolayout}, 244 | {"settiledmaster", settiledmaster}, 245 | {"setverticalstripes", setverticalstripes}, 246 | {"sethorizontalstripes", sethorizontalstripes}, 247 | {"setfloatingmode", setfloatingmode}, 248 | {"updatebarslayout", updatebarslayout}, 249 | {"cycledownlayout", cycledownlayout}, 250 | {"cycleuplayout", cycleuplayout}, 251 | {"addmasterlayout", addmasterlayout}, 252 | {"removemasterlayout", removemasterlayout}, 253 | {"incmasterarealayout", incmasterarealayout}, 254 | {"decmasterarealayout", decmasterarealayout}, 255 | {"incgapsizelayout", incgapsizelayout}, 256 | {"decgapsizelayout", decgapsizelayout}, 257 | {"inclayoutsizefocus", inclayoutsizefocus}, 258 | {"declayoutsizefocus", declayoutsizefocus}, 259 | {"movefocusup", movefocusup}, 260 | {"movefocusdown", movefocusdown}, 261 | {"movefocusleft", movefocusleft}, 262 | {"movefocusright", movefocusright}, 263 | {"cyclefocusmonitordown", cyclefocusmonitordown}, 264 | {"cyclefocusmonitorup", cyclefocusmonitorup}, 265 | {"togglescratchpad", togglescratchpad}, 266 | {"reloadconfigfile", reloadconfigfile}, 267 | }; 268 | 269 | static config_t cfghndl; 270 | 271 | static char* replaceplaceholder(const char* str, const char* placeholder, const char* value); 272 | 273 | static uint16_t kbmodsfromstr(state_t* s, const char* modifiers); 274 | static keycode_t keycodefromstr(const char* keycode); 275 | static keycallback_t keycbfromstr(const char* cbstr); 276 | 277 | static bool cfgreadint(state_t* s, int32_t* val, const char* label); 278 | static bool cfgreadfloat(state_t* s, double* val, const char* label); 279 | static bool cfgreadbool(state_t* s, bool* val, const char* label); 280 | static bool cfgreadstr(state_t* s, const char** val, const char* label); 281 | 282 | static bool cfgevalkbmod(state_t* s, kb_modifier_t* mod, const char* label); 283 | static bool cfgevalmousebtn(state_t* s, mousebtn_t* btn, const char* label); 284 | static layout_type_t cfgevallayouttype(state_t* s, const char* label); 285 | static char** cfgevalstrarr(state_t* s, const char* label); 286 | static keybind_t* cfgevalkeybinds(state_t* s, uint32_t* numkeybinds, const char* label); 287 | 288 | char* 289 | replaceplaceholder(const char* str, const char* placeholder, const char* value) { 290 | char* result; 291 | char* ins; 292 | char* tmp; 293 | size_t len_placeholder; 294 | size_t len_front; 295 | size_t count; 296 | 297 | if (!str || !placeholder) 298 | return NULL; 299 | len_placeholder = strlen(placeholder); 300 | if (len_placeholder == 0) 301 | return NULL; 302 | if (!value) 303 | value = ""; 304 | 305 | // Count the number of replacements needed 306 | ins = (char*)str; 307 | for (count = 0; (tmp = strstr(ins, placeholder)); ++count) { 308 | ins = tmp + len_placeholder; 309 | } 310 | 311 | tmp = result = malloc(strlen(str) + (strlen(value) - len_placeholder) * count + 1); 312 | if (!result) 313 | return NULL; 314 | 315 | // Replace each occurrence of placeholder with the value 316 | while (count--) { 317 | ins = strstr(str, placeholder); 318 | len_front = ins - str; 319 | tmp = strncpy(tmp, str, len_front) + len_front; 320 | tmp = strcpy(tmp, value) + strlen(value); 321 | str += len_front + len_placeholder; 322 | } 323 | strcpy(tmp, str); 324 | return result; 325 | } 326 | 327 | uint16_t 328 | kbmodsfromstr(state_t* s, const char* modifiers) { 329 | uint16_t bitmask = 0; 330 | char buffer[64]; 331 | const char *delim = " |"; 332 | 333 | const char* modstr; 334 | if(!cfgreadstr(s, &modstr, "mod_key")) { 335 | return bitmask; 336 | } 337 | 338 | modifiers = replaceplaceholder(modifiers, "%mod_key", modstr); 339 | 340 | while (*modifiers) { 341 | while (isspace(*modifiers)) modifiers++; 342 | 343 | char *end = buffer; 344 | while (*modifiers && !strchr(delim, *modifiers)) { 345 | *end++ = *modifiers++; 346 | } 347 | *end = '\0'; 348 | 349 | if (strcmp(buffer, "Shift") == 0) { 350 | bitmask |= Shift; 351 | } else if (strcmp(buffer, "Control") == 0) { 352 | bitmask |= Control; 353 | } else if (strcmp(buffer, "Alt") == 0) { 354 | bitmask |= Alt; 355 | } else if (strcmp(buffer, "Super") == 0) { 356 | bitmask |= Super; 357 | } 358 | 359 | while (isspace(*modifiers) || *modifiers == '|') modifiers++; 360 | } 361 | 362 | return bitmask; 363 | } 364 | 365 | keycode_t 366 | keycodefromstr(const char* keycode) { 367 | for (size_t i = 0; i < sizeof(keymappings) / sizeof(keymappings[0]); ++i) { 368 | if (strcmp(keycode, keymappings[i].name) == 0) { 369 | return keymappings[i].value; 370 | } 371 | } 372 | return -1; 373 | } 374 | 375 | keycallback_t 376 | keycbfromstr(const char* cbstr) { 377 | for (size_t i = 0; i < sizeof(keycbmappings) / sizeof(keycbmappings[0]); ++i) { 378 | if (strcmp(cbstr, keycbmappings[i].name) == 0) { 379 | return keycbmappings[i].cb; 380 | } 381 | } 382 | return NULL; 383 | } 384 | 385 | bool 386 | cfgreadint(state_t* s, int32_t* val ,const char* label) { 387 | bool success = (bool)config_lookup_int(&cfghndl, label, val); 388 | 389 | if(!success) { 390 | logmsg(s, LogLevelError, "config: %s is not set.", label); 391 | } 392 | 393 | return success; 394 | } 395 | 396 | bool 397 | cfgreadfloat(state_t* s, double* val, const char* label) { 398 | bool success = (bool)config_lookup_float(&cfghndl, label, val); 399 | 400 | if(!success) { 401 | logmsg(s, LogLevelError, "config: %s is not set.", label); 402 | } 403 | 404 | return success; 405 | } 406 | 407 | bool 408 | cfgreadbool(state_t* s, bool* val, const char* label) { 409 | bool success = (bool)config_lookup_bool(&cfghndl, label, (int32_t*)val); 410 | 411 | if(!success) { 412 | logmsg(s, LogLevelError, "config: %s is not set.", label); 413 | } 414 | 415 | return success; 416 | } 417 | 418 | bool 419 | cfgreadstr(state_t* s, const char** val, const char* label) { 420 | bool success = (bool)config_lookup_string(&cfghndl, label, val); 421 | 422 | if(!success) { 423 | logmsg(s, LogLevelError, "config: %s is not set.", label); 424 | } 425 | 426 | return success; 427 | } 428 | 429 | bool 430 | cfgevalkbmod(state_t* s, kb_modifier_t* mod, const char* label) { 431 | bool success = false; 432 | 433 | const char* modstr; 434 | success = cfgreadstr(s, &modstr, label); 435 | 436 | if(strcmp(modstr, "Shift") == 0) { 437 | *mod = Shift; 438 | return success; 439 | } 440 | if(strcmp(modstr, "Control") == 0) { 441 | *mod = Control; 442 | return success; 443 | } 444 | if(strcmp(modstr, "Alt") == 0) { 445 | *mod = Alt; 446 | return success; 447 | } 448 | if(strcmp(modstr, "Super") == 0) { 449 | *mod = Super; 450 | return success; 451 | } 452 | 453 | logmsg(s, LogLevelError, "config: invalid keyboard modifier specified."); 454 | return false; 455 | } 456 | 457 | bool 458 | cfgevalmousebtn(state_t* s, mousebtn_t* btn, const char* label) { 459 | bool success = false; 460 | 461 | const char* btnstr; 462 | success = cfgreadstr(s, &btnstr, label); 463 | 464 | if(strcmp(btnstr, "LeftMouse") == 0) { 465 | *btn = LeftMouse; 466 | } else if(strcmp(btnstr, "MiddleMouse") == 0) { 467 | *btn = MiddleMouse; 468 | return success; 469 | } else if(strcmp(btnstr, "RightMouse") == 0) { 470 | *btn = RightMouse; 471 | return success; 472 | } else { 473 | logmsg(s, LogLevelError, "config: invalid mouse button specified."); 474 | return false; 475 | } 476 | return false; 477 | } 478 | 479 | layout_type_t 480 | cfgevallayouttype(state_t* s, const char* label) { 481 | 482 | const char* layoutstr; 483 | cfgreadstr(s, &layoutstr, label); 484 | 485 | layout_type_t layout; 486 | 487 | if(strcmp(layoutstr, "LayoutFloating") == 0) { 488 | layout = LayoutFloating; 489 | } else if(strcmp(layoutstr, "LayoutTiledMaster") == 0) { 490 | layout = LayoutTiledMaster; 491 | } else if(strcmp(layoutstr, "LayoutHorizontalStripes") == 0) { 492 | layout = LayoutHorizontalStripes; 493 | } else if(strcmp(layoutstr, "LayoutVerticalStripes") == 0) { 494 | layout = LayoutVerticalStripes; 495 | } else { 496 | layout = -1; 497 | logmsg(s, LogLevelError, "config: invalid layout type specified."); 498 | } 499 | 500 | 501 | return layout; 502 | } 503 | 504 | char** 505 | cfgevalstrarr(state_t* s, const char* label) { 506 | const config_setting_t* setting; 507 | 508 | setting = config_lookup(&cfghndl, label); 509 | if(!setting) { 510 | logmsg(s, LogLevelError, "config: %s is not set.", label); 511 | return NULL; 512 | } 513 | 514 | uint32_t len = (uint32_t)config_setting_length(setting); 515 | 516 | char** arr = malloc(len * sizeof(char*)); 517 | for (uint32_t i = 0; i < len; i++) { 518 | const char* val = config_setting_get_string_elem(setting, i); 519 | if (val) { 520 | arr[i] = malloc(strlen(val) + 1); 521 | strcpy(arr[i], val); 522 | } 523 | } 524 | 525 | return arr; 526 | } 527 | 528 | keybind_t* 529 | cfgevalkeybinds(state_t* s, uint32_t* numkeybinds, const char* label) { 530 | const config_setting_t* setting; 531 | setting = config_lookup(&cfghndl, "keybinds"); 532 | if(!setting) { 533 | logmsg(s, LogLevelError, "config: %s is not set.", label); 534 | return NULL; 535 | } 536 | uint32_t len = config_setting_length(setting); 537 | *numkeybinds = len; 538 | keybind_t* keys = malloc(sizeof(keybind_t) * len); 539 | 540 | for(uint32_t i = 0; i < len; ++i) 541 | { 542 | config_setting_t* keybind = config_setting_get_elem(setting, i); 543 | 544 | const char* mods_str; 545 | const char* key_str; 546 | const char* do_str; 547 | 548 | config_setting_lookup_string(keybind, "mod", &mods_str); 549 | config_setting_lookup_string(keybind, "key", &key_str); 550 | config_setting_lookup_string(keybind, "do", &do_str); 551 | 552 | uint16_t mods = kbmodsfromstr(s, mods_str); 553 | keycode_t key = keycodefromstr(key_str); 554 | keycallback_t cb = keycbfromstr(do_str); 555 | 556 | if((int32_t)key == -1) { 557 | logmsg(s, LogLevelError, "config: Invalid keycode '%s'.", key_str); 558 | continue; 559 | } 560 | 561 | if(!cb) { 562 | logmsg(s, LogLevelError, "config: Invalid function action '%s (specify a valid function to execute 'runcmd')'.", do_str); 563 | continue; 564 | } 565 | const char* cmd = NULL; 566 | config_setting_lookup_string(keybind, "cmd", &cmd); 567 | 568 | uint32_t i_val = 0; 569 | config_setting_lookup_int(keybind, "i", (int32_t*)&i_val); 570 | 571 | keys[i] = (keybind_t){ 572 | .cb = cb, 573 | .key = key, 574 | .data = (passthrough_data_t){ 575 | .i = i_val, 576 | .cmd = cmd 577 | }, 578 | .modmask = mods 579 | }; 580 | } 581 | 582 | return keys; 583 | } 584 | 585 | 586 | void 587 | initconfig(state_t* s) { 588 | config_init(&cfghndl); 589 | 590 | const char* home = getenv("HOME"); 591 | if(!home) { 592 | logmsg(s, LogLevelError, "cannot read config file because HOME is not defined."); 593 | } 594 | 595 | char *cfg_path; 596 | char const cfg_path_global[] = "/etc/ragnarwm/ragnar.cfg"; 597 | 598 | asprintf(&cfg_path, "%s/.config/ragnarwm/ragnar.cfg", home); 599 | 600 | printf("ragnar: attempting to read config at %s or %s\n", cfg_path, cfg_path_global); 601 | if ( 602 | !config_read_file(&cfghndl, cfg_path) 603 | && !config_read_file(&cfghndl, cfg_path_global) 604 | ) { 605 | logmsg(s, LogLevelError, "%s:%d - %s\n", config_error_file(&cfghndl), 606 | config_error_line(&cfghndl), config_error_text(&cfghndl)); 607 | printf("ragnar: %s:%d - %s\n", config_error_file(&cfghndl), 608 | config_error_line(&cfghndl), config_error_text(&cfghndl)); 609 | 610 | destroyconfig(); 611 | terminate(s, EXIT_FAILURE); 612 | } 613 | 614 | } 615 | 616 | void 617 | readconfig(state_t* s, config_data_t* data) { 618 | if(!data) return; 619 | 620 | bool success = false; 621 | 622 | success = cfgreadint(s, (int32_t*)&data->maxstruts, "max_struts"); 623 | success = cfgreadint(s, (int32_t*)&data->maxdesktops, "num_desktops"); 624 | success = cfgreadint(s, (int32_t*)&data->maxscratchpads, "max_scratchpads"); 625 | 626 | success = cfgreadint(s, (int32_t*)&data->winborderwidth, "win_border_width"); 627 | success = cfgreadint(s, (int32_t*)&data->winbordercolor, "win_border_color"); 628 | success = cfgreadint(s, (int32_t*)&data->winbordercolor_selected, "win_border_color_selected"); 629 | 630 | success = cfgevalkbmod(s, &data->modkey, "mod_key"); 631 | success = cfgevalkbmod(s, &data->winmod, "win_mod"); 632 | 633 | success = cfgevalmousebtn(s, &data->movebtn, "move_button"); 634 | success = cfgevalmousebtn(s, &data->resizebtn, "resize_button"); 635 | 636 | success = cfgreadint(s, (int32_t*)&data->desktopinit, "initial_desktop"); 637 | 638 | data->desktopnames = cfgevalstrarr(s, "desktop_names"); 639 | success = data->desktopnames != NULL; 640 | 641 | success = cfgreadbool(s, &data->usedecoration, "use_decoration"); 642 | 643 | success = cfgreadfloat(s, &data->layoutmasterarea, "layout_master_area"); 644 | success = cfgreadfloat(s, &data->layoutmasterarea_min, "layout_master_area_min"); 645 | success = cfgreadfloat(s, &data->layoutmasterarea_max, "layout_master_area_max"); 646 | success = cfgreadfloat(s, &data->layoutmasterarea_step, "layout_master_area_step"); 647 | 648 | success = cfgreadfloat(s, &data->layoutsize_step, "layout_size_step"); 649 | success = cfgreadfloat(s, &data->layoutsize_min, "layout_size_min"); 650 | 651 | success = cfgreadfloat(s, &data->keywinmove_step, "key_win_move_step"); 652 | 653 | 654 | success = cfgreadint(s, (int32_t*)&data->winlayoutgap, "win_layout_gap"); 655 | success = cfgreadint(s, (int32_t*)&data->winlayoutgap_max, "win_layout_gap_max"); 656 | success = cfgreadint(s, (int32_t*)&data->winlayoutgap_step, "win_layout_gap_step"); 657 | 658 | data->initlayout = cfgevallayouttype(s, "initial_layout"); 659 | 660 | success = cfgreadint(s, (int32_t*)&data->motion_notify_debounce_fps, "motion_notify_debounce_fps"); 661 | success = cfgreadbool(s, &data->glvsync, "gl_vsync"); 662 | 663 | success = cfgreadstr(s, (const char**)&data->cursorimage, "cursor_image"); 664 | 665 | const char* home = getenv("HOME"); 666 | const char* relpath = "/.ragnarwm.log"; 667 | char* logpath = malloc(strlen(home) + strlen(relpath) + 2); 668 | sprintf(logpath, "%s%s", home, relpath); 669 | data->logfile = logpath; 670 | 671 | success = cfgreadbool(s, &data->logmessages, "log_messages"); 672 | success = cfgreadbool(s, &data->shouldlogtofile, "should_log_to_file"); 673 | 674 | 675 | data->keybinds = cfgevalkeybinds(s, (uint32_t*)&data->numkeybinds, "keybinds"); 676 | 677 | success = data->keybinds != NULL; 678 | 679 | if(!success) { 680 | terminate(s, EXIT_FAILURE); 681 | } else { 682 | printf("ragnar: successfully read config file.\n"); 683 | } 684 | } 685 | 686 | void 687 | reloadconfig(state_t* s, config_data_t* data) { 688 | destroyconfig(); 689 | 690 | bool using_decoration = s->config.usedecoration; 691 | 692 | initconfig(s); 693 | readconfig(s, data); 694 | 695 | // Load the default root cursor image 696 | loaddefaultcursor(s); 697 | 698 | // Grab the window manager's keybinds 699 | grabkeybinds(s); 700 | 701 | if(!using_decoration) { 702 | s->config.usedecoration = false; 703 | } 704 | 705 | // Reload struts 706 | s->nwinstruts = 0; 707 | getwinstruts(s, s->root); 708 | 709 | // Reload desktops 710 | for(monitor_t* mon = s->monitors; mon != NULL; mon = mon->next) { 711 | for(uint32_t i = 0; i < s->config.maxdesktops; i++) { 712 | mon->layouts[i].nmaster = 1; 713 | mon->layouts[i].gapsize = s->config.winlayoutgap; 714 | mon->layouts[i].masterarea = MIN(MAX(s->config.layoutmasterarea, 0.0), 1.0); 715 | mon->layouts[i].curlayout = s->config.initlayout; 716 | } 717 | 718 | uint32_t lastdesktopcount = mon->desktopcount; 719 | uint32_t curdesktop = mondesktop(s, mon)->idx; 720 | 721 | mon->desktopcount = 0; 722 | free(mon->activedesktops); 723 | mon->activedesktops = malloc(sizeof(*mon->activedesktops) * s->config.maxdesktops); 724 | for(uint32_t i = 0; i < s->config.maxdesktops; i++) { 725 | createdesktop(s, i, mon); 726 | mon->activedesktops[i].init = true; 727 | } 728 | 729 | uint32_t desktopcount = 0; 730 | for(uint32_t i = 0; i < mon->desktopcount; i++) { 731 | if(mon->activedesktops[i].init) { 732 | desktopcount++; 733 | } 734 | } 735 | xcb_change_property(s->con, XCB_PROP_MODE_REPLACE, s->root, s->ewmh_atoms[EWMHnumberOfDesktops], 736 | XCB_ATOM_CARDINAL, 32, 1, &desktopcount); 737 | uploaddesktopnames(s, mon); 738 | 739 | if(lastdesktopcount > mon->desktopcount) { 740 | if(curdesktop > mon->desktopcount) { 741 | switchdesktop(s, (passthrough_data_t){.i = mon->desktopcount - 1}); 742 | } 743 | for(monitor_t* mon = s->monitors; mon != NULL; mon = mon->next) { 744 | for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { 745 | if(cl->mon == mon && cl->desktop >= s->config.maxdesktops) { 746 | switchclientdesktop(s, cl, s->config.maxdesktops - 1); 747 | } 748 | } 749 | } 750 | } 751 | } 752 | 753 | s->scratchpads = realloc(s->scratchpads, sizeof(*s->scratchpads) * s->config.maxscratchpads); 754 | 755 | for(uint32_t i = 0; i < s->config.maxscratchpads; i++) { 756 | if(s->scratchpads[i].win != 0) { 757 | xcb_unmap_window(s->con, s->scratchpads[i].win); 758 | s->scratchpads[i].win = 0; 759 | s->scratchpads[i].hidden = true; 760 | s->scratchpads[i].needs_restart = true; 761 | } 762 | } 763 | 764 | s->mapping_scratchpad_index = -1; 765 | 766 | for(monitor_t* mon = s->monitors; mon != NULL; mon = mon->next) { 767 | for(client_t* cl = mon->clients; cl != NULL; cl = cl->next) { 768 | setbordercolor(s, cl, s->config.winbordercolor); 769 | setborderwidth(s, cl, s->config.winborderwidth); 770 | } 771 | } 772 | 773 | for(monitor_t* mon = s->monitors; mon != NULL; mon = mon->next) { 774 | makelayout(s, mon); 775 | } 776 | 777 | xcb_flush(s->con); 778 | } 779 | 780 | void 781 | destroyconfig(void) { 782 | config_destroy(&cfghndl); 783 | } 784 | -------------------------------------------------------------------------------- /src/keycallbacks.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "config.h" 3 | #include "funcs.h" 4 | #include "structs.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | /** 12 | * @brief Terminates the window manager with 13 | * EXIT_SUCCESS exit code. 14 | * 15 | * @param s The window manager's state 16 | * @param data The data to use for the function (unused here) 17 | */ 18 | inline void terminate_successfully(state_t* s, passthrough_data_t data) { 19 | (void)data; 20 | terminate(s, EXIT_SUCCESS); 21 | } 22 | 23 | /** 24 | * @brief Cycles the currently focused client one down 25 | * 26 | * @param s The window manager's state 27 | * @param data The data to use for the function (unused here) 28 | */ 29 | inline void cyclefocusdown(state_t* s, passthrough_data_t data) { 30 | (void)data; 31 | if (!s->monfocus->clients || !s->focus) 32 | return; 33 | 34 | client_t* focus = NULL; 35 | for (focus = s->focus->next; 36 | focus && focus->desktop != mondesktop(s, focus->mon)->idx; 37 | focus = focus->next); 38 | if (!focus) 39 | for (focus = s->monfocus->clients; 40 | focus && focus->desktop != mondesktop(s, focus->mon)->idx; 41 | focus = focus->next); 42 | 43 | if(!focus) return; 44 | focusclient(s, focus, false); 45 | raiseclient(s, focus); 46 | } 47 | 48 | /** 49 | * @brief Cycles the currently focused client one up 50 | * 51 | * @param s The window manager's state 52 | * @param data The data to use for the function (unused here) 53 | */ 54 | inline void cyclefocusup(state_t* s, passthrough_data_t data) { 55 | (void)data; 56 | if (!s->monfocus->clients || !s->focus) 57 | return; 58 | 59 | client_t* focus = NULL; 60 | client_t* cl; 61 | for (cl = s->monfocus->clients; cl != s->focus; cl = cl->next) { 62 | if (cl->desktop == mondesktop(s, cl->mon)->idx) { 63 | focus = cl; 64 | } 65 | } 66 | if(!focus) { 67 | for (; cl; cl = cl->next) { 68 | if (cl->desktop == mondesktop(s, cl->mon)->idx) { 69 | focus = cl; 70 | } 71 | } 72 | } 73 | if (!focus) return; 74 | focusclient(s, focus, false); 75 | raiseclient(s, focus); 76 | } 77 | 78 | /** 79 | * @brief Kills the currently focused window 80 | * @param s The window manager's state 81 | * @param data The data to use for the function (unused here) 82 | */ 83 | inline void killfocus(state_t* s, passthrough_data_t data) { 84 | (void)data; 85 | if(!s->focus || s->focus->is_scratchpad) { 86 | return; 87 | } 88 | killclient(s, s->focus); 89 | } 90 | 91 | 92 | 93 | /** 94 | * @brief Toggles fullscreen mode on the currently focused client 95 | * 96 | * @param s The window manager's state 97 | * @param data The data to use for the function (unused here) 98 | */ 99 | inline void togglefullscreen(state_t* s, passthrough_data_t data) { 100 | (void)data; 101 | 102 | if(!s->focus) return; 103 | bool fs = !(s->focus->fullscreen); 104 | setfullscreen(s, s->focus, fs); 105 | } 106 | 107 | /** 108 | * @brief Raises the currently focused client 109 | * 110 | * @param s The window manager's state 111 | * @param data The data to use for the function (unused here) 112 | */ 113 | inline void raisefocus(state_t* s, passthrough_data_t data) { 114 | (void)data; 115 | if(!s->focus) return; 116 | raiseclient(s, s->focus); 117 | } 118 | 119 | /** 120 | * @brief Cycles the currently selected desktop index one desktop up 121 | * 122 | * @param s The window manager's state 123 | * @param data The data to use for the function (unused here) 124 | */ 125 | inline void cycledesktopup(state_t* s, passthrough_data_t data) { 126 | (void)data; 127 | 128 | int32_t newdesktop = mondesktop(s, s->monfocus)->idx; 129 | if(newdesktop + 1 < (int32_t)s->config.maxdesktops) { 130 | newdesktop++; 131 | } else { 132 | newdesktop = 0; 133 | } 134 | switchdesktop(s, (passthrough_data_t){.i = newdesktop}); 135 | } 136 | 137 | /** 138 | * @brief Cycles the currently selected desktop index one desktop down 139 | * 140 | * @param s The window manager's state 141 | * @param data The data to use for the function (unused here) 142 | */ 143 | inline void cycledesktopdown(state_t* s, passthrough_data_t data) { 144 | (void)data; 145 | 146 | int32_t newdesktop = mondesktop(s, s->monfocus)->idx; 147 | if(newdesktop - 1 >= 0) { 148 | newdesktop--; 149 | } else { 150 | uint32_t ninit = 0; 151 | for(uint32_t i = 0; i < s->monfocus->desktopcount; i++) { 152 | if(!s->monfocus->activedesktops[i].init) continue; 153 | ninit++; 154 | } 155 | newdesktop = ninit - 1; 156 | 157 | 158 | } 159 | switchdesktop(s, (passthrough_data_t){.i = newdesktop}); 160 | } 161 | 162 | /* 163 | * Cycles the desktop, that the focused client is on, up 164 | * 165 | * @param s The window manager's state 166 | * @param data The data to use for the function (unused here) 167 | * */ 168 | inline void cyclefocusdesktopup(state_t* s, passthrough_data_t data) { 169 | (void)data; 170 | 171 | if(!s->focus) return; 172 | int32_t new_desktop = s->focus->desktop; 173 | if(new_desktop + 1 < (int32_t)s->config.maxdesktops) { 174 | new_desktop++; 175 | } else { 176 | new_desktop = 0; 177 | } 178 | switchclientdesktop(s, s->focus, new_desktop); 179 | } 180 | 181 | /* 182 | * Cycles the desktop, that the focused client is on, down 183 | * 184 | * @param s The window manager's state 185 | * @param data The data to use for the function (unused here) 186 | * */ 187 | inline void cyclefocusdesktopdown(state_t* s, passthrough_data_t data) { 188 | (void)data; 189 | 190 | if(!s->focus) return; 191 | int32_t new_desktop = s->focus->desktop; 192 | if(new_desktop - 1 >= 0) { 193 | new_desktop--; 194 | } else { 195 | new_desktop = s->config.maxdesktops - 1; 196 | } 197 | switchclientdesktop(s, s->focus, new_desktop); 198 | } 199 | 200 | /** 201 | * @brief Switches the currently selected desktop index to the given 202 | * index and notifies EWMH that there was a desktop change 203 | * 204 | * @param s The window manager's state 205 | * @param data The .i member is used as the desktop to switch to 206 | * */ 207 | inline void switchdesktop(state_t* s, passthrough_data_t data) { 208 | switchmonitordesktop(s, data.i); 209 | } 210 | 211 | 212 | /** 213 | * @brief Switches the desktop of the currently selected client to a given index 214 | * 215 | * @param s The window manager's state 216 | * @param data The .i member is used as the desktop to switch to 217 | * */ 218 | inline void switchfocusdesktop(state_t* s, passthrough_data_t data) { 219 | if(!s->focus) return; 220 | switchclientdesktop(s, s->focus, data.i); 221 | } 222 | 223 | /** 224 | * @brief Runs a given command by forking the process and using execl. 225 | * 226 | * @param cmd The command to run 227 | */ 228 | inline void runcmd(state_t* s, passthrough_data_t data) { 229 | (void)s; 230 | if (data.cmd == NULL) { 231 | return; 232 | } 233 | 234 | pid_t pid = fork(); 235 | if (pid == 0) { 236 | // Child process 237 | execl("/bin/sh", "sh", "-c", data.cmd, (char *)NULL); 238 | // If execl fails 239 | logmsg(s, LogLevelError, "failed to execute command '%s'.", data.cmd); 240 | _exit(EXIT_FAILURE); 241 | } else if (pid > 0) { 242 | // Parent process 243 | int32_t status; 244 | waitpid(pid, &status, 0); 245 | return; 246 | } else { 247 | // Fork failed 248 | perror("fork"); 249 | logmsg(s, LogLevelError, "failed to execute command '%s'.", data.cmd); 250 | return; 251 | } 252 | } 253 | 254 | /** 255 | * @brief Adds the currently focused client to the current 256 | * layout. 257 | * 258 | * @param s The window manager's state 259 | * @param data The data to use for the function (unused here) 260 | */ 261 | inline void addfocustolayout(state_t* s, passthrough_data_t data) { 262 | (void)data; 263 | 264 | if(s->focus->is_scratchpad) return; 265 | 266 | s->focus->floating = false; 267 | 268 | resetlayoutsizes(s, s->monfocus); 269 | makelayout(s, s->monfocus); 270 | } 271 | 272 | /** 273 | * @brief Sets the current layout to tiled master 274 | * and adds every on-screen client to the layout 275 | * layout. 276 | * 277 | * @param s The window manager's state 278 | * @param data The data to use for the function (unused here) 279 | */ 280 | inline void settiledmaster(state_t* s, passthrough_data_t data) { 281 | (void)data; 282 | 283 | for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { 284 | if(!cl->is_scratchpad) { 285 | cl->floating = false; 286 | } 287 | } 288 | uint32_t deskidx = mondesktop(s, s->monfocus)->idx; 289 | s->monfocus->layouts[deskidx].curlayout = LayoutTiledMaster; 290 | 291 | resetlayoutsizes(s, s->monfocus); 292 | 293 | makelayout(s, s->monfocus); 294 | } 295 | 296 | /** 297 | * @brief Sets the current layout to vertical stripes 298 | * and adds every on-screen client to the layout 299 | * layout. 300 | * 301 | * @param s The window manager's state 302 | * @param data The data to use for the function (unused here) 303 | */ 304 | inline void setverticalstripes(state_t* s, passthrough_data_t data) { 305 | (void)data; 306 | 307 | for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { 308 | if(clientonscreen(s, cl, s->monfocus) && !cl->is_scratchpad) { 309 | cl->floating = false; 310 | } 311 | } 312 | uint32_t deskidx = mondesktop(s, s->monfocus)->idx; 313 | s->monfocus->layouts[deskidx].curlayout = LayoutVerticalStripes; 314 | 315 | resetlayoutsizes(s, s->monfocus); 316 | 317 | makelayout(s, s->monfocus); 318 | } 319 | 320 | /** 321 | * @brief Sets the current layout to horizontal stripes 322 | * and adds every on-screen client to the layout 323 | * layout. 324 | * 325 | * @param s The window manager's state 326 | * @param data The data to use for the function (unused here) 327 | */ 328 | inline void sethorizontalstripes(state_t* s, passthrough_data_t data) { 329 | (void)data; 330 | 331 | for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { 332 | if(clientonscreen(s, cl, s->monfocus) && !cl->is_scratchpad) { 333 | cl->floating = false; 334 | } 335 | } 336 | uint32_t deskidx = mondesktop(s, s->monfocus)->idx; 337 | s->monfocus->layouts[deskidx].curlayout = LayoutHorizontalStripes; 338 | 339 | resetlayoutsizes(s, s->monfocus); 340 | 341 | makelayout(s, s->monfocus); 342 | } 343 | 344 | /** 345 | * @brief Sets the current layout to floating 346 | * and removes every on-screen client to the layout 347 | * layout. 348 | * 349 | * @param s The window manager's state 350 | * @param data The data to use for the function (unused here) 351 | */ 352 | inline void setfloatingmode(state_t* s, passthrough_data_t data) { 353 | (void)data; 354 | 355 | for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { 356 | if(clientonscreen(s, cl, s->monfocus)) { 357 | cl->floating = true; 358 | } 359 | } 360 | uint32_t deskidx = mondesktop(s, s->monfocus)->idx; 361 | s->monfocus->layouts[deskidx].curlayout = LayoutFloating; 362 | 363 | makelayout(s, s->monfocus); 364 | } 365 | 366 | /** 367 | * @brief Updates the inernal list of window struts that 368 | * the layout knows about. 369 | * @param s The window manager's state 370 | * @param data The data to use for the function (unused here) 371 | */ 372 | inline void updatebarslayout(state_t* s, passthrough_data_t data) { 373 | (void)data; 374 | // Gather strut information 375 | s->nwinstruts = 0; 376 | getwinstruts(s, s->root); 377 | makelayout(s, s->monfocus); 378 | } 379 | 380 | /** 381 | * @brief Cycles the focused window down in the layout list by 382 | * swapping it with the client after the it. 383 | * 384 | * @param s The window manager's state 385 | * @param data The data to use for the function (unused here) 386 | */ 387 | inline void cycledownlayout(state_t* s, passthrough_data_t data) { 388 | (void)data; 389 | if (!s->monfocus->clients || !s->focus) 390 | return; 391 | 392 | s->ignore_enter_layout = true; 393 | 394 | client_t* focus = NULL; 395 | for (focus = s->focus->next; 396 | focus && focus->desktop != mondesktop(s, focus->mon)->idx; 397 | focus = focus->next); 398 | if (!focus) 399 | for (focus = s->monfocus->clients; 400 | focus && focus->desktop != mondesktop(s, focus->mon)->idx; 401 | focus = focus->next); 402 | 403 | if(!focus) return; 404 | 405 | swapclients(s, s->focus, focus); 406 | resetlayoutsizes(s, s->monfocus); 407 | makelayout(s, s->monfocus); 408 | } 409 | 410 | /** 411 | * @brief Cycles the focused window up in the layout list by 412 | * swapping it with the client before the it. 413 | * 414 | * @param s The window manager's state 415 | * @param data The data to use for the function (unused here) 416 | */ 417 | inline void cycleuplayout(state_t* s, passthrough_data_t data) { 418 | (void)data; 419 | if (!s->monfocus->clients || !s->focus) 420 | return; 421 | 422 | s->ignore_enter_layout = true; 423 | 424 | client_t* focus = NULL; 425 | client_t* cl; 426 | for (cl = s->monfocus->clients; cl != s->focus; cl = cl->next) { 427 | if (cl->desktop == mondesktop(s, cl->mon)->idx) { 428 | focus = cl; 429 | } 430 | } 431 | if(!focus) { 432 | for (; cl; cl = cl->next) { 433 | if (cl->desktop == mondesktop(s, cl->mon)->idx) { 434 | focus = cl; 435 | } 436 | } 437 | } 438 | if (!focus) return; 439 | 440 | swapclients(s, s->focus, focus); 441 | resetlayoutsizes(s, s->monfocus); 442 | makelayout(s, s->monfocus); 443 | } 444 | 445 | /** 446 | * @brief Increments the number of windows that are seen as master windows 447 | * in master-slave layouts 448 | * 449 | * @param s The window manager's state 450 | * @param data The data to use for the function (unused here) 451 | */ 452 | inline void addmasterlayout(state_t* s, passthrough_data_t data) { 453 | (void)data; 454 | 455 | uint32_t nlayout = numinlayout(s, s->monfocus); 456 | uint32_t deskidx = mondesktop(s, s->monfocus)->idx; 457 | layout_props_t* layout = &s->monfocus->layouts[deskidx]; 458 | 459 | if(nlayout - (layout->nmaster + 1) >= 1 && nlayout > 1) { 460 | layout->nmaster++; 461 | } 462 | 463 | resetlayoutsizes(s, s->monfocus); 464 | makelayout(s, s->monfocus); 465 | } 466 | 467 | /** 468 | * @brief Decrements the number of windows that are seen as master windows 469 | * in master-slave layouts 470 | * 471 | * @param s The window manager's state 472 | * @param data The data to use for the function (unused here) 473 | */ 474 | inline void removemasterlayout(state_t* s, passthrough_data_t data) { 475 | (void)data; 476 | 477 | uint32_t deskidx = mondesktop(s, s->monfocus)->idx; 478 | layout_props_t* layout = &s->monfocus->layouts[deskidx]; 479 | 480 | if(layout->nmaster - 1 >= 1) { 481 | layout->nmaster--; 482 | } 483 | 484 | resetlayoutsizes(s, s->monfocus); 485 | makelayout(s, s->monfocus); 486 | } 487 | 488 | /** 489 | * @brief Increments the area that the master window takes 490 | * up by 'layoutmasterarea_step'. (in %) 491 | * 492 | * @param s The window manager's state 493 | * @param data The data to use for the function (unused here) 494 | */ 495 | inline void incmasterarealayout(state_t* s, passthrough_data_t data) { 496 | (void)data; 497 | 498 | uint32_t deskidx = mondesktop(s, s->monfocus)->idx; 499 | layout_props_t* layout = &s->monfocus->layouts[deskidx]; 500 | 501 | if(layout->masterarea 502 | + s->config.layoutmasterarea_step <= s->config.layoutmasterarea_max) { 503 | layout->masterarea += s->config.layoutmasterarea_step; 504 | } 505 | makelayout(s, s->monfocus); 506 | } 507 | 508 | /** 509 | * @brief Decrements the area that the master window takes 510 | * up by 'layoutmasterarea_step'. (in %) 511 | * 512 | * @param s The window manager's state 513 | * @param data The data to use for the function (unused here) 514 | */ 515 | inline void decmasterarealayout(state_t* s, passthrough_data_t data) { 516 | (void)data; 517 | uint32_t deskidx = mondesktop(s, s->monfocus)->idx; 518 | layout_props_t* layout = &s->monfocus->layouts[deskidx]; 519 | 520 | if(layout->mastermaxed) return; 521 | 522 | if(layout->masterarea 523 | - s->config.layoutmasterarea_step >= s->config.layoutmasterarea_min) { 524 | layout->masterarea -= s->config.layoutmasterarea_step; 525 | } 526 | makelayout(s, s->monfocus); 527 | } 528 | 529 | 530 | /** 531 | * @brief Increments the size of the gaps between windows 532 | * in layouts in the current layout. 533 | * 534 | * @param s The window manager's state 535 | * @param data The data to use for the function (unused here) 536 | */ 537 | inline void incgapsizelayout(state_t* s, passthrough_data_t data) { 538 | (void)data; 539 | uint32_t deskidx = mondesktop(s, s->monfocus)->idx; 540 | layout_props_t* layout = &s->monfocus->layouts[deskidx]; 541 | 542 | if(layout->gapsize + s->config.winlayoutgap_step <= s->config.winlayoutgap_max) { 543 | layout->gapsize += s->config.winlayoutgap_step; 544 | } 545 | makelayout(s, s->monfocus); 546 | } 547 | 548 | /** 549 | * @brief Increments the height of a client within layouts 550 | * in which can be stacked on top of each other. 551 | * 552 | * @param s The window manager's state 553 | * @param data The data to use for the function (unused here) 554 | */ 555 | inline void inclayoutsizefocus(state_t* s, passthrough_data_t data) { 556 | (void)data; 557 | if(!s->focus) return; 558 | 559 | bool horizontal = false; 560 | layout_type_t curlayout = getcurlayout(s, s->monfocus); 561 | // Only layouts where clients are stacked on top of each other 562 | if( curlayout == LayoutTiledMaster || 563 | curlayout == LayoutHorizontalStripes) { 564 | horizontal = true; 565 | } 566 | 567 | uint32_t nmaster = 0, nslaves = 0; 568 | enumartelayout(s, s->monfocus, &nmaster, &nslaves); 569 | 570 | bool ismaster = isclientmaster(s, s->focus, s->monfocus); 571 | 572 | // The last client cannot change size itself as it is only 573 | // influenced by the client ontop of it 574 | if(!s->focus->next || (s->focus->next && s->focus->next->desktop != s->focus->desktop) 575 | || (s->focus->next && s->focus->next->mon != s->focus->mon)) return; 576 | 577 | uint32_t i = 0; 578 | for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { 579 | if(cl->floating || cl->desktop != mondesktop(s, cl->mon)->idx) continue; 580 | if(i == nmaster - 1 && cl == s->focus) { 581 | return; 582 | } 583 | i++; 584 | } 585 | 586 | // If the height of the window influenced by the focus is smaller 587 | // than the minimum height a window can be in the layout, return 588 | float nextsize = horizontal ? s->focus->next->area.size.y : s->focus->next->area.size.x; 589 | if(nextsize - s->config.layoutsize_step < s->config.layoutsize_min) { 590 | if(horizontal) { 591 | s->focus->next->area.size.y = s->config.layoutsize_min; 592 | } else { 593 | s->focus->next->area.size.x = s->config.layoutsize_min; 594 | } 595 | return; 596 | } 597 | 598 | s->ignore_enter_layout = true; 599 | 600 | // If the window is a master and there are more than one master 601 | // or the window is a slave and there are more than one slave, 602 | // the clients height can be changed 603 | if((nmaster > 1 && ismaster) || (nslaves > 1 && !ismaster)) { 604 | s->focus->layoutsizeadd += s->config.layoutsize_step; 605 | } 606 | 607 | // Recreate the layout 608 | makelayout(s, s->monfocus); 609 | } 610 | 611 | inline void declayoutsizefocus(state_t* s, passthrough_data_t data) { 612 | (void)data; 613 | if(!s->focus) return; 614 | 615 | bool horizontal = false; 616 | layout_type_t curlayout = getcurlayout(s, s->monfocus); 617 | // Only layouts where clients are stacked on top of each other 618 | if( curlayout == LayoutTiledMaster || 619 | curlayout == LayoutHorizontalStripes) { 620 | horizontal = true; 621 | } 622 | 623 | uint32_t nmaster = 0, nslaves = 0; 624 | enumartelayout(s, s->monfocus, &nmaster, &nslaves); 625 | 626 | bool ismaster = isclientmaster(s, s->focus, s->monfocus); 627 | 628 | // The last client cannot change size itself as it is only 629 | // influenced by the client ontop of it 630 | if(!s->focus->next || (s->focus->next && s->focus->next->desktop != s->focus->desktop) 631 | || (s->focus->next && s->focus->next->mon != s->focus->mon)) return; 632 | 633 | uint32_t i = 0; 634 | for(client_t* cl = s->monfocus->clients; cl != NULL; cl = cl->next) { 635 | if(cl->floating || cl->desktop != mondesktop(s, cl->mon)->idx 636 | || cl->mon != s->monfocus) continue; 637 | if(i == nmaster - 1 && cl == s->focus) { 638 | return; 639 | } 640 | i++; 641 | } 642 | 643 | // If the height of the focus is smaller 644 | // than the minimum height a window can be in the layout, return 645 | float size = horizontal ? s->focus->area.size.y : s->focus->area.size.x; 646 | if(size - s->config.layoutsize_step < s->config.layoutsize_min) { 647 | if(horizontal) { 648 | s->focus->area.size.y = s->config.layoutsize_min; 649 | } else { 650 | s->focus->area.size.x = s->config.layoutsize_min; 651 | } 652 | return; 653 | } 654 | 655 | s->ignore_enter_layout = true; 656 | 657 | // If the window is a master and there are more than one master 658 | // or the window is a slave and there are more than one slave, 659 | // the clients height can be changed 660 | if((nmaster > 1 && ismaster) || (nslaves > 1 && !ismaster)) { 661 | s->focus->layoutsizeadd -= s->config.layoutsize_step; 662 | } 663 | 664 | // Recreate the layout 665 | makelayout(s, s->monfocus); 666 | } 667 | 668 | /** 669 | * @brief Decrements the size of the gaps between windows 670 | * in layouts in the current layout. 671 | * 672 | * @param s The window manager's state 673 | * @param data The data to use for the function (unused here) 674 | */ 675 | inline void decgapsizelayout(state_t* s, passthrough_data_t data) { 676 | (void)data; 677 | uint32_t deskidx = mondesktop(s, s->monfocus)->idx; 678 | layout_props_t* layout = &s->monfocus->layouts[deskidx]; 679 | 680 | if(layout->gapsize - s->config.winlayoutgap_step >= 0) { 681 | layout->gapsize -= s->config.winlayoutgap_step; 682 | } 683 | makelayout(s, s->monfocus); 684 | } 685 | 686 | 687 | /** 688 | * @brief Moves the focused client up by 'keywinmove_step' 689 | * (in px) 690 | * 691 | * @param s The window manager's state 692 | * @param data The data to use for the function (unused here) 693 | */ 694 | inline void movefocusup(state_t* s, passthrough_data_t data) { 695 | (void)data; 696 | if(!s->focus->floating) return; 697 | 698 | v2_t pos = s->focus->area.pos; 699 | v2_t dest = (v2_t){pos.x, 700 | MIN(MAX(pos.y - s->config.keywinmove_step, s->monfocus->area.pos.y), 701 | s->monfocus->area.pos.y + s->monfocus->area.size.y 702 | - s->focus->area.size.y)}; 703 | moveclient(s, s->focus, dest, true); 704 | s->ignore_enter_layout = true; 705 | } 706 | 707 | /** 708 | * @brief Moves the focused client down by 'keywinmove_step' 709 | * (in px) 710 | * 711 | * @param s The window manager's state 712 | * @param data The data to use for the function (unused here) 713 | */ 714 | inline void movefocusdown(state_t* s, passthrough_data_t data) { 715 | (void)data; 716 | if(!s->focus->floating) return; 717 | 718 | v2_t pos = s->focus->area.pos; 719 | v2_t dest = (v2_t){pos.x, 720 | MIN(MAX(pos.y + s->config.keywinmove_step, s->monfocus->area.pos.y), 721 | s->monfocus->area.pos.y + s->monfocus->area.size.y 722 | - s->focus->area.size.y)}; 723 | moveclient(s, s->focus, dest, true); 724 | s->ignore_enter_layout = true; 725 | } 726 | 727 | /** 728 | * @brief Moves the focused client left by 'keywinmove_step' 729 | * (in px) 730 | * 731 | * @param s The window manager's state 732 | * @param data The data to use for the function (unused here) 733 | */ 734 | inline void movefocusleft(state_t* s, passthrough_data_t data) { 735 | (void)data; 736 | if(!s->focus->floating) return; 737 | 738 | v2_t pos = s->focus->area.pos; 739 | v2_t dest = (v2_t){MIN(MAX(pos.x - s->config.keywinmove_step, s->monfocus->area.pos.x), 740 | s->monfocus->area.pos.x + s->monfocus->area.size.x 741 | - s->focus->area.size.x), pos.y}; 742 | moveclient(s, s->focus, dest, true); 743 | s->ignore_enter_layout = true; 744 | } 745 | 746 | /** 747 | * @brief Moves the focused client right by 'keywinmove_step' 748 | * (in px) 749 | * 750 | * @param s The window manager's state 751 | * @param data The data to use for the function (unused here) 752 | */ 753 | inline void movefocusright(state_t* s, passthrough_data_t data) { 754 | (void)data; 755 | if(!s->focus->floating) return; 756 | 757 | v2_t pos = s->focus->area.pos; 758 | v2_t dest = (v2_t){MIN(MAX(pos.x + s->config.keywinmove_step, s->monfocus->area.pos.x), 759 | s->monfocus->area.pos.x + s->monfocus->area.size.x 760 | - s->focus->area.size.x), pos.y}; 761 | moveclient(s, s->focus, dest, true); 762 | s->ignore_enter_layout = true; 763 | } 764 | 765 | 766 | 767 | /** 768 | * @brief Cycles the currently focused client one monitor 769 | * down (to the left). 770 | * 771 | * @param s The window manager's state 772 | * @param data The data to use for the function (unused here) 773 | */ 774 | 775 | inline void cyclefocusmonitordown(state_t* s, passthrough_data_t data) { 776 | (void)data; 777 | if(!s->focus) return; 778 | s->ignore_enter_layout = true; 779 | 780 | monitor_t* prevmon = NULL; 781 | if(s->focus->mon == s->monitors) { 782 | for(monitor_t* mon = s->monitors; mon; mon = mon->next) { 783 | prevmon = mon; 784 | } 785 | } else { 786 | for(monitor_t* mon = s->monitors; mon && mon->next; mon = mon->next) { 787 | if(mon->next == s->focus->mon) { 788 | prevmon = mon; 789 | break; 790 | } 791 | } 792 | } 793 | 794 | if(!prevmon) return; 795 | 796 | area_t afocusmon = s->focus->mon->area; 797 | 798 | bool fs = s->focus->fullscreen; 799 | bool floating = s->focus->floating; 800 | if(fs) { 801 | setfullscreen(s, s->focus, false); 802 | } 803 | 804 | // Unset fullscreen for all clients on the 805 | // previous monitor 806 | for(client_t* cl = prevmon->clients; cl != NULL; cl = cl->next) { 807 | if(cl->fullscreen) { 808 | setfullscreen(s, cl, false); 809 | cl->floating = floating; 810 | } 811 | } 812 | 813 | // Moving 814 | { 815 | v2_t relpos; 816 | relpos.x = s->focus->area.pos.x - afocusmon.pos.x; 817 | relpos.y = s->focus->area.pos.y - afocusmon.pos.y; 818 | 819 | float normx = relpos.x / afocusmon.size.x; 820 | float normy = relpos.y / afocusmon.size.y; 821 | 822 | v2_t dest; 823 | dest.x = prevmon->area.pos.x + (normx * prevmon->area.size.x); 824 | dest.y = prevmon->area.pos.y + (normy * prevmon->area.size.y); 825 | 826 | removefromlayout(s, s->focus); 827 | makelayout(s, s->focus->mon); 828 | s->focus->floating = floating; 829 | 830 | moveclient(s, s->focus, dest, false); 831 | 832 | monremoveclient(s->focus->mon, s->focus); 833 | monaddclient(prevmon, s->focus); 834 | s->focus->mon = prevmon; 835 | updateewmhdesktops(s, s->focus->mon); 836 | s->monfocus = s->focus->mon; 837 | s->focus->desktop = mondesktop(s, s->focus->mon)->idx; 838 | } 839 | 840 | // Resizing 841 | { 842 | float scalex = prevmon->area.size.x / afocusmon.size.x; 843 | float scaley = prevmon->area.size.y / afocusmon.size.y; 844 | 845 | v2_t dest; 846 | dest.x = s->focus->area.size.x * scalex; 847 | dest.y = s->focus->area.size.y * scaley; 848 | 849 | resizeclient(s, s->focus, dest); 850 | } 851 | 852 | makelayout(s, prevmon); 853 | 854 | if(fs) { 855 | setfullscreen(s, s->focus, true); 856 | } 857 | } 858 | 859 | /** 860 | * @brief Cycles the currently focused client one monitor 861 | * down (to the left). 862 | * 863 | * @param s The window manager's state 864 | * @param data The data to use for the function (unused here) 865 | */ 866 | 867 | inline void cyclefocusmonitorup(state_t* s, passthrough_data_t data) { 868 | (void)data; 869 | if(!s->focus) return; 870 | 871 | s->ignore_enter_layout = true; 872 | 873 | monitor_t* nextmon = s->focus->mon->next; 874 | if(!nextmon) { 875 | nextmon = s->monitors; 876 | } 877 | if(!nextmon) return; 878 | 879 | area_t afocusmon = s->focus->mon->area; 880 | 881 | bool fs = s->focus->fullscreen; 882 | bool floating = s->focus->floating; 883 | 884 | if(fs) { 885 | setfullscreen(s, s->focus, false); 886 | } 887 | 888 | // Unset fullscreen for all clients on the 889 | // next monitor 890 | for(client_t* cl = nextmon->clients; cl != NULL; cl = cl->next) { 891 | if(cl->fullscreen) { 892 | setfullscreen(s, cl, false); 893 | cl->floating = floating; 894 | } 895 | } 896 | 897 | // Moving 898 | { 899 | v2_t relpos; 900 | relpos.x = s->focus->area.pos.x - afocusmon.pos.x; 901 | relpos.y = s->focus->area.pos.y - afocusmon.pos.y; 902 | 903 | float normx = relpos.x / afocusmon.size.x; 904 | float normy = relpos.y / afocusmon.size.y; 905 | 906 | v2_t dest; 907 | dest.x = nextmon->area.pos.x + (normx * nextmon->area.size.x); 908 | dest.y = nextmon->area.pos.y + (normy * nextmon->area.size.y); 909 | 910 | removefromlayout(s, s->focus); 911 | makelayout(s, s->focus->mon); 912 | s->focus->floating = floating; 913 | moveclient(s, s->focus, dest, false); 914 | 915 | monremoveclient(s->focus->mon, s->focus); 916 | monaddclient(nextmon, s->focus); 917 | s->focus->mon = nextmon; 918 | updateewmhdesktops(s, s->focus->mon); 919 | s->monfocus = s->focus->mon; 920 | s->focus->desktop = mondesktop(s, s->focus->mon)->idx; 921 | } 922 | // Resizing 923 | { 924 | float scalex = nextmon->area.size.x / afocusmon.size.x; 925 | float scaley = nextmon->area.size.y / afocusmon.size.y; 926 | 927 | v2_t dest; 928 | dest.x = s->focus->area.size.x * scalex; 929 | dest.y = s->focus->area.size.y * scaley; 930 | 931 | resizeclient(s, s->focus, dest); 932 | } 933 | 934 | makelayout(s, nextmon); 935 | 936 | if(fs) { 937 | setfullscreen(s, s->focus, true); 938 | } 939 | } 940 | 941 | inline void togglescratchpad(state_t* s, passthrough_data_t data) { 942 | if(s->scratchpads[data.i].needs_restart) { 943 | s->mapping_scratchpad_index = data.i; 944 | runcmd(s, data); 945 | return; 946 | } 947 | 948 | client_t* cl = clientfromwin(s, s->scratchpads[data.i].win); 949 | 950 | if(s->scratchpads[data.i].hidden) { 951 | showclient(s, cl); 952 | raiseclient(s, cl); 953 | } else { 954 | hideclient(s, cl); 955 | } 956 | 957 | s->scratchpads[data.i].hidden = !s->scratchpads[data.i].hidden; 958 | } 959 | 960 | inline void reloadconfigfile(state_t* s, passthrough_data_t data) { 961 | (void)data; 962 | reloadconfig(s, &s->config); 963 | } 964 | -------------------------------------------------------------------------------- /src/funcs.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "structs.h" 4 | #include 5 | #include 6 | 7 | /* === FUNCTION DECLARATIONS === */ 8 | 9 | /** 10 | * @brief Sets up the WM state and the X server 11 | * 12 | * @param s The window manager's state 13 | * This function establishes a connection to the X server, 14 | * sets up the root window and window manager keybindings. 15 | * The event mask of the root window is being cofigured to 16 | * listen to necessary events. 17 | * After the configuration of the root window, all the specified 18 | * keybinds in config.h are grabbed by the window manager. 19 | */ 20 | void setup(state_t* s); 21 | 22 | /** 23 | * @brief Event loop of the window manager 24 | * 25 | * This function polls for X server events and 26 | * handles them accoringly by calling the associated event handler. 27 | */ 28 | void loop(state_t* s); 29 | 30 | /** 31 | * @brief Terminates the window manager 32 | * 33 | * This function terminates the window manager by 34 | * giving up the connection to the X server and 35 | * exiting the program. 36 | */ 37 | void terminate(state_t* s, int32_t exitcode); 38 | 39 | /** 40 | * @brief Manages all windows that are avaiable on the 41 | * X display. 42 | * 43 | * @param s The window manager' state 44 | */ 45 | void managewins(state_t* s); 46 | 47 | void updateedgewindows(state_t* s, client_t* cl); 48 | 49 | void toggleedgewindows(state_t* s, client_t* cl, bool toggle); 50 | 51 | void createwindowedges(state_t* s, client_t* cl); 52 | 53 | /** 54 | * @brief Creates a client from a given X windwo 55 | * 56 | * @param s The window manager' state 57 | * @param win The window from which to create a client 58 | */ 59 | client_t* makeclient(state_t* s, xcb_window_t win); 60 | 61 | /** 62 | * @brief Evaluates if a given point is inside a given area 63 | * 64 | * @param p The point to check if it is inside the given area 65 | * @param area The area to check 66 | * 67 | * @return True if the point p is in the area, false if it is not in the area 68 | */ 69 | bool pointinarea(v2_t p, area_t a); 70 | 71 | /** 72 | * @brief Returns the current cursor position on the X display 73 | * 74 | * @param s The window manager's state 75 | * @param success Gets assigned whether or not the pointer query 76 | * was successfull. 77 | * 78 | * @return The cursor position as a two dimensional vector 79 | */ 80 | v2_t cursorpos(state_t* s, bool* success); 81 | 82 | /** 83 | * @brief Returns the area (position and size) of a given window 84 | * 85 | * @param s The window manager's state 86 | * @param win The window to get the area from 87 | * @param success Gets assigned whether or not the geometry query 88 | * was successfull. 89 | * 90 | * @return The area of the given window 91 | */ 92 | 93 | area_t winarea(state_t* s, xcb_window_t win, bool* success); 94 | 95 | /** 96 | * @brief Creates a X window with a trucolor visual 97 | * and colormap. 98 | * 99 | * @param s The window manager's state 100 | * @param a The geometry of the window 101 | * @param bw The border width of the window 102 | * 103 | * @return The ID of the created X window 104 | */ 105 | xcb_window_t truecolorwindow(state_t* s, area_t a, uint32_t bw); 106 | 107 | /** 108 | * @brief Gets the area (in float) of the overlap between two areas 109 | * 110 | * @param a The inner area 111 | * @param a The outer area 112 | * 113 | * @return The overlap between the two areas 114 | */ 115 | float getoverlaparea(area_t a, area_t b); 116 | 117 | /** 118 | * @brief Sets the border color of a given clients window 119 | * 120 | * @param s The window manager's state 121 | * @param cl The client to set the border color of 122 | * @param color The border color 123 | */ 124 | void setbordercolor(state_t* s, client_t* cl, uint32_t color); 125 | 126 | /** 127 | * @brief Sets the border width of a given clients window 128 | * and updates its 'borderwidth' variable 129 | * 130 | * @param s The window manager's state 131 | * @param cl The client to set the border width of 132 | * @param width The border width 133 | */ 134 | void setborderwidth(state_t* s, client_t* cl, uint32_t width); 135 | 136 | 137 | /** 138 | * @brief Removes a given client from the linked list 139 | * of clients of a given monitor (not deallocating the client) 140 | * 141 | * @param mon The monitor to remove the client from 142 | * @param cl The client to remove 143 | */ 144 | void monremoveclient(monitor_t *mon, client_t *cl); 145 | 146 | /** 147 | * @brief Adds a given client to the front of the linked list 148 | * of clients of a given monitor 149 | * 150 | * @param mon The monitor to add the client to 151 | * @param cl The client to add 152 | */ 153 | void monaddclient(monitor_t *mon, client_t *cl); 154 | 155 | /** 156 | * @brief Moves the window of a given client and updates its area. 157 | * 158 | * @param s The window manager's state 159 | * @param cl The client to move 160 | * @param pos The position to move the client to 161 | */ 162 | void moveclient(state_t* s, client_t* cl, v2_t pos, bool manage_mons); 163 | 164 | /** 165 | * @brief Resizes the window of a given client and updates its area. 166 | * 167 | * @param s The window manager's state 168 | * @param cl The client to resize 169 | * @param pos The new size of the clients window 170 | */ 171 | void resizeclient(state_t* s, client_t* cl, v2_t size); 172 | 173 | /** 174 | * @brief Moves and resizes the window of a given client and updates its area. 175 | * 176 | * @param s The window manager's state 177 | * @param cl The client to move and resize 178 | * @param a The new area (position and size) for the client's window 179 | */ 180 | void moveresizeclient(state_t* s, client_t* cl, area_t a); 181 | 182 | /** 183 | * @brief Raises the window of a given client to the top of the stack 184 | * 185 | * @param s The window manager's state 186 | * @param cl The client to raise 187 | */ 188 | void raiseclient(state_t* s, client_t* cl); 189 | 190 | /** 191 | * @brief Checks if a client has a WM_DELETE atom set 192 | * 193 | * @param s The window manager's state 194 | * @param cl The client to check delete atom for 195 | * 196 | * @return Whether or not the given client has a WM_DELETE atom 197 | */ 198 | bool clienthasdeleteatom(state_t* s, client_t* cl); 199 | 200 | /** 201 | * @brief Returns wether or not a client should be added 202 | * to the tiling layout based on window properties: 203 | * (_NET_WM_WINDOW_TYPE, NET_WM_WINDOW_TYPE_NORMAL) 204 | * 205 | * @param s The window manager's state 206 | * @param cl The client to check if it should tile for 207 | * 208 | * @return Whether or not a given client should tile 209 | */ 210 | bool clientshouldtile(state_t* s, client_t* cl); 211 | 212 | /** 213 | * @brief Checks if a client is on a given monitor and if it is 214 | * currently visible (on the current desktop) 215 | * 216 | * @param cl The client to check visibility for 217 | * 218 | * @return Whether or not the given client is visible and on the given monitor 219 | */ 220 | bool clientonscreen(state_t* s, client_t* cl, monitor_t* mon); 221 | 222 | /** 223 | * @brief Retrieves the name of a given client (allocates memory) 224 | * 225 | * @param s The window manager's state 226 | * @param cl The client to retrieve a name from 227 | * 228 | * @return The name of the given client 229 | */ 230 | char* getclientname(state_t* s, client_t* cl); 231 | 232 | /** 233 | * @brief Kills a given client by destroying the associated window and 234 | * removing it from the linked list. 235 | * 236 | * @param s The window manager's state 237 | * @param cl The client to kill 238 | */ 239 | void killclient(state_t* s, client_t* cl); 240 | 241 | /** 242 | * @brief Sets the current focus to a given client 243 | * 244 | * @param s The window manager's state 245 | * @param cl The client to focus 246 | */ 247 | void focusclient(state_t* s, client_t* cl, bool upload_ewmh_desktops); 248 | 249 | /** 250 | * @brief Sets the X input focus to a given client and handles EWMH atoms & 251 | * focus event. 252 | * 253 | * @param s The window manager's state 254 | * @param cl The client to set the input focus to 255 | */ 256 | void setxfocus(state_t* s, client_t* cl); 257 | 258 | /** 259 | * @brief Creates a frame window for the content of a client window and decoration to live in. 260 | * The frame window encapsulates the client's window and decoration like titlebar. 261 | * 262 | * @param s The window manager's state 263 | * @param cl The client to create a frame window for 264 | */ 265 | void frameclient(state_t* s, client_t* cl); 266 | 267 | /** 268 | * @brief Destroys and unmaps the frame window of a given client 269 | * which consequently removes the client's window from the display 270 | * 271 | * @param s The window manager's state 272 | * @param cl The client to unframe 273 | */ 274 | void unframeclient(state_t* s, client_t* cl); 275 | 276 | /** 277 | * @brief Removes the focus from client's window by setting the 278 | * X input focus to the root window and unsetting the highlight color 279 | * of the window's border. 280 | * 281 | * @param s The window manager's state 282 | * @param cl The client to unfocus 283 | */ 284 | void unfocusclient(state_t* s, client_t* cl); 285 | 286 | void removescratchpad(state_t* s, uint32_t idx); 287 | 288 | /** 289 | * @brief Notifies a given client window about it's configuration 290 | * (geometry) by sending a configure notify event to it. 291 | * 292 | * @param s The window manager's state 293 | * @param cl The client to send a configure notify event to 294 | */ 295 | void configclient(state_t* s, client_t* cl); 296 | 297 | /* 298 | * @brief Hides a given client by unframing it's window 299 | * 300 | * @param s The window manager's state 301 | * @param cl The client to hide 302 | */ 303 | void hideclient(state_t* s, client_t* cl); 304 | 305 | /* 306 | * @brief Shows a given client by unframing it's window 307 | * 308 | * @param s The window manager's state 309 | * @param cl The client to show 310 | */ 311 | void showclient(state_t* s, client_t* cl); 312 | 313 | /** 314 | * @brief Sends a given X event by atom to the window of a given client 315 | * 316 | * @param s The window manager's state 317 | * @param cl The client to send the event to 318 | * @param protocol The atom to store the event 319 | */ 320 | bool raiseevent(state_t* s, client_t* cl, xcb_atom_t protocol); 321 | 322 | /** 323 | * @brief Checks for window type of clients by EWMH and acts 324 | * accoringly. 325 | * 326 | * @param cl The client to set/update the window type of 327 | * @param s The window manager's state 328 | */ 329 | void setwintype(state_t* s, client_t* cl); 330 | 331 | /** 332 | * @brief Sets/unsets urgency flag on a given client 333 | * accoringly. 334 | * 335 | * @param s The window manager's state 336 | * @param cl The client to set/unset urgency on 337 | * @param urgent The urgency of the client's window 338 | */ 339 | void seturgent(state_t* s, client_t* cl, bool urgent); 340 | 341 | 342 | /** 343 | * @brief Returns the next on-screen client after the 344 | * given client. 345 | * 346 | * @param s The window manager's state 347 | * @param skip_floating Whether or not to skip floating clients 348 | */ 349 | client_t* nextvisible(state_t* s, bool skip_floating); 350 | 351 | /** 352 | * @brief Gets the value of a given property on a 353 | * window of a given client. 354 | * 355 | * @param s The window manager's state 356 | * @param cl The client to retrieve the property value from 357 | * @param prop The property to retrieve 358 | * 359 | * @return The value of the given property on the given window 360 | */ 361 | xcb_atom_t getclientprop(state_t* s, client_t* cl, xcb_atom_t prop); 362 | 363 | /** 364 | * @brief Puts a given client in or out of fullscreen 365 | * mode based on the input. 366 | * 367 | * @param s The window manager's state 368 | * @param cl The client to toggle fullscreen of 369 | * @param fullscreen Whether the client should be set 370 | * fullscreen or not 371 | */ 372 | void setfullscreen(state_t* s, client_t* cl, bool fullscreen); 373 | 374 | /** 375 | * @brief Switches the desktop of a given client and hides that client. 376 | * 377 | * @param s The window manager's state 378 | * @param cl The client to switch the desktop of 379 | * @param desktop The desktop to set the client of 380 | */ 381 | void switchclientdesktop(state_t* s, client_t* cl, int32_t desktop); 382 | 383 | /** 384 | * @brief Switches the currently selected desktop index to the given 385 | * index and notifies EWMH that there was a desktop change 386 | * 387 | * @param s The window manager's state 388 | * @param desktop Used as the desktop to switch to 389 | * */ 390 | void switchmonitordesktop(state_t* s, int32_t desktop); 391 | 392 | /** 393 | * @brief Returns how many client are currently in the layout on a 394 | * given monitor. 395 | * 396 | * @param s The window manager's state 397 | * @param mon The monitor to count clients on 398 | * */ 399 | uint32_t numinlayout(state_t* s, monitor_t* mon); 400 | 401 | /** 402 | * @brief Returns the current layout of the current virtual desktop on 403 | * a given monitor 404 | * 405 | * @param s The window manager's state 406 | * @param mon The monitor to get the current layout of the current 407 | * virtual desktop of. 408 | * */ 409 | layout_type_t getcurlayout(state_t* s, monitor_t* mon); 410 | 411 | /** 412 | * @brief Establishes the current tiling layout for the windows 413 | * 414 | * @param s The window manager's state 415 | * @param mon The monitor to use as the frame of the layout 416 | */ 417 | void makelayout(state_t* s, monitor_t* mon); 418 | 419 | /** 420 | * @brief Resets the size modifications of 421 | * all client windows within the layout. 422 | * 423 | * @param s The window manager's state 424 | * @param mon The monitor on which the layout is 425 | */ 426 | void resetlayoutsizes(state_t* s, monitor_t* mon); 427 | 428 | /** 429 | * @brief Establishes a tiled master layout for the windows that are 430 | * currently visible. 431 | * 432 | * @param s The window manager's state 433 | * @param mon The monitor to use as the frame of the layout 434 | */ 435 | void tiledmaster(state_t* s, monitor_t* mon); 436 | 437 | /** 438 | * @brief Establishes a layout in which windows are 439 | * layed out left to right as vertical stripes. 440 | * 441 | * @param s The window manager's state 442 | * @param mon The monitor to use as the frame of the layout 443 | */ 444 | void verticalstripes(state_t* s, monitor_t* mon); 445 | 446 | /** 447 | * @brief Establishes a layout in which windows are 448 | * layed out top to bottom as horizontal stripes 449 | * 450 | * @param s The window manager's state 451 | * @param mon The monitor to use as the frame of the layout 452 | */ 453 | void horizontalstripes(state_t* s, monitor_t* mon); 454 | 455 | /** 456 | * @brief Swaps two clients within the linked list of clients 457 | * 458 | * @param s The window manager's state 459 | * @param c1 The client to swap with c2 460 | * @param c2 The client to swap with c1 461 | */ 462 | void swapclients(state_t* s, client_t* c1, client_t* c2); 463 | 464 | /** 465 | * @brief Uploads the active desktop names and sends them to EWMH 466 | * @param s The window manager's state 467 | */ 468 | void uploaddesktopnames(state_t* s, monitor_t* mon); 469 | 470 | /** 471 | * @brief Uploads the active desktop names, current desktop and number of desktops 472 | * and sends them to EWMH 473 | * @param s The window manager's state 474 | */ 475 | void updateewmhdesktops(state_t* s, monitor_t* mon); 476 | 477 | /** 478 | * @brief Creates a new virtual desktop and notifies EWMH about it. 479 | * @param s The window manager's state 480 | * @param idx The index of the virtual desktop 481 | * @param mon The monitor that the virtual desktop is on 482 | */ 483 | void createdesktop(state_t* s, uint32_t idx, monitor_t* mon); 484 | 485 | /** 486 | * @brief Initializes all important atoms for EWMH & 487 | * NetWM compatibility. 488 | * 489 | * @param s The window manager's state 490 | * */ 491 | void setupatoms(state_t* s); 492 | 493 | /** 494 | * @brief Grabs all the keybinds specified in config.h for the window 495 | * manager. The function also ungrabs all previously grabbed keys 496 | * 497 | * @param s The window manager's state 498 | * */ 499 | void grabkeybinds(state_t* s); 500 | 501 | /** 502 | * @brief Loads and sets the default cursor image of the window manager. 503 | * The default image is the left facing pointer. 504 | * */ 505 | void loaddefaultcursor(state_t* s); 506 | 507 | bool iswindowpopup(state_t* s, xcb_window_t win); 508 | 509 | /** 510 | * @brief Takes in a size for a client window and adjusts it 511 | * if it does not meet the requirements of the client's hints. 512 | * The adjusted value is returned. 513 | * 514 | * @param s The window manager's state 515 | * @param cl The client of which to get the adjusted size of 516 | * @param size The size before applying hints 517 | * 518 | * @return The size after applying hints 519 | * */ 520 | v2_t applysizehints(state_t* s, client_t* cl, v2_t size); 521 | 522 | 523 | /** 524 | * @brief Returns the layering order in EWMH that is 525 | * set for a given client (checks for macros like 526 | * _NET_WM_STATE_ABOVE, _NET_WM_STATE_BELOW) 527 | * 528 | * @param s the state of the window manager 529 | * @param cl the client for checking the layering order 530 | * 531 | * @return The layering order enum for the given client 532 | */ 533 | layering_order_t clientlayering(state_t* s, client_t* cl); 534 | 535 | /** 536 | * @brief Adds a given client to the current tiling layout 537 | * 538 | * @param s The window manager's state 539 | * @param cl The client to add to the layout 540 | * */ 541 | void addtolayout(state_t* s, client_t* cl); 542 | 543 | /** 544 | * @brief Returns the number of master- and slave windows 545 | * within a given layout 546 | * 547 | * @param s The window manager's state 548 | * @param mon The monitor on which the layout is 549 | * @param nmaster The number of master windows [out] 550 | * @param nslaves The number of slave windows [out] 551 | * */ 552 | void enumartelayout(state_t* s, monitor_t* mon, uint32_t* nmaster, uint32_t* nslaves); 553 | 554 | /** 555 | * @brief Checks if a given client is a master window 556 | * within layouts that can have master windows. 557 | * 558 | * @param s The window manager's state 559 | * @param cl The client to check if it is a master window 560 | * @param mon The monitor on which the layout is 561 | * */ 562 | bool isclientmaster(state_t* s, client_t* cl, monitor_t* mon); 563 | 564 | 565 | /** 566 | * @brief Removes a given client from the current tiling layout 567 | * 568 | * @param s The window manager's state 569 | * @param cl The client to remove from the layout 570 | * */ 571 | void removefromlayout(state_t* s, client_t* cl); 572 | 573 | 574 | /** 575 | * @brief Handles a map request event on the X server by 576 | * adding the mapped window to the linked list of clients and 577 | * setting up necessary stuff. 578 | * 579 | * @param s The window manager's state 580 | * @param ev The generic event 581 | */ 582 | void evmaprequest(state_t* s, xcb_generic_event_t* ev); 583 | 584 | void evmapnotify(state_t* s, xcb_generic_event_t* ev); 585 | 586 | /** 587 | * @brief Handles a X unmap event by unmapping the window 588 | * and removing the associated client from the linked list. 589 | * 590 | * @param s The window manager's state 591 | * @param ev The generic event 592 | */ 593 | void evunmapnotify(state_t* s, xcb_generic_event_t* ev); 594 | 595 | 596 | /** 597 | * @brief Handles an X destroy event by unframing the client 598 | * associated with the event and removing it from the list of 599 | * clients. 600 | * 601 | * @param s The window manager's state 602 | * @param ev The generic event 603 | */ 604 | void evdestroynotify(state_t* s, xcb_generic_event_t* ev); 605 | 606 | /** 607 | * @brief Handles a X enter-window event by focusing the window 608 | * that's associated with the enter event. 609 | * 610 | * @param s The window manager's state 611 | * @param ev The generic event 612 | */ 613 | void eventernotify(state_t* s, xcb_generic_event_t* ev); 614 | 615 | 616 | /** 617 | * @brief Handles a X key press event by checking if the pressed 618 | * key (and modifiers) match any window manager keybind and then executing 619 | * that keybinds function. 620 | * 621 | * @param s The window manager's state 622 | * @param ev The generic event 623 | */ 624 | void evkeypress(state_t* s, xcb_generic_event_t* ev); 625 | 626 | /** 627 | * @brief Handles a X button press event by focusing the client 628 | * associated with the pressed window and setting cursor and window grab positions 629 | * 630 | * @param s The window manager's state 631 | * @param ev The generic event 632 | */ 633 | void evbuttonpress(state_t* s, xcb_generic_event_t* ev); 634 | 635 | void evbuttonrelease(state_t* s, xcb_generic_event_t* ev); 636 | 637 | /** 638 | * @brief Handles a X motion notify event by moving the clients window if left mouse 639 | * button is held and resizing the clients window if right mouse is held. 640 | * 641 | * @param s The window manager's state 642 | * @param ev The generic event 643 | */ 644 | void evmotionnotify(state_t* s, xcb_generic_event_t* ev); 645 | 646 | /** 647 | * @brief Handles a Xorg configure request by configuring the client that 648 | * is associated with the window how the event requested it. 649 | * 650 | * @param s The window manager's state 651 | * @param ev The generic event 652 | */ 653 | void evconfigrequest(state_t* s, xcb_generic_event_t* ev); 654 | 655 | /* 656 | * @brief Handles a X configure notify event. If the root window 657 | * recieved a configure notify event, the list of monitors is refreshed. 658 | * 659 | * @param s The window manager's state 660 | * @param ev The generic event 661 | * */ 662 | void evconfignotify(state_t* s, xcb_generic_event_t* ev); 663 | 664 | /** 665 | * @brief Handles a X property notify event for window properties by 666 | * handling various Extended Window Manager Hints (EWMH). 667 | * 668 | * @param s The window manager's state 669 | * @param ev The generic event 670 | */ 671 | void evpropertynotify(state_t* s, xcb_generic_event_t* ev); 672 | 673 | /** 674 | * @brief Handles a X client message event by setting fullscreen or 675 | * urgency (based on the event request). 676 | * 677 | * @param s The window manager's state 678 | * @param ev The generic event 679 | */ 680 | void evclientmessage(state_t* s, xcb_generic_event_t* ev); 681 | 682 | void evfocusin(state_t* s, xcb_generic_event_t* ev); 683 | 684 | 685 | 686 | /** 687 | * @brief Adds a client window to the linked list of clients 688 | * 689 | * @param s The window manager's state 690 | * @param win The window to create a client from and add it 691 | * to the clients 692 | * 693 | * @return The newly created client 694 | */ 695 | client_t* addclient(state_t* s, client_t** clients, xcb_window_t win); 696 | 697 | void setcursorforresize(state_t* s, xcb_window_t win, window_edge_t edge); 698 | 699 | window_edge_t getedgefromwindow(client_t* cl, xcb_window_t win); 700 | 701 | /** 702 | * @brief Removes a given client from the list of clients 703 | * by window. 704 | * 705 | * @param s The window manager's state 706 | * @param win The window of the client to remove 707 | */ 708 | void releaseclient(state_t* s, xcb_window_t win); 709 | 710 | /** 711 | * @brief Returns the associated client from a given window. 712 | * Returns NULL if there is no client associated with the window. 713 | * 714 | * @param s The window manager's state 715 | * @param win The window to get the client from 716 | * 717 | * @return The client associated with the given window (NULL if no associated client) 718 | */ 719 | client_t* clientfromwin(state_t* s, xcb_window_t win); 720 | 721 | client_t* clientfromframe(state_t* s, xcb_window_t frame); 722 | 723 | client_t* clientfromedgewindow(state_t* s, xcb_window_t win); 724 | 725 | 726 | /** 727 | * @brief Returns a filtered linked list of all clients 728 | * that are currently visible on the given monitor 729 | * 730 | * @param s The window manager's state 731 | * @param mon The monitor to get visible clients off 732 | * @param tiled Whether or not to ignore floating clients 733 | * 734 | * @return A filtered linked list with all visible clients on 735 | * the given monitor 736 | */ 737 | client_t* visibleclients(state_t* s, monitor_t* mon, bool tiled); 738 | 739 | /** 740 | ** 741 | * @brief Adds a monitor area to the linked list of monitors 742 | * 743 | * @param s The window manager's state 744 | * @param a The area of the monitor to create 745 | * 746 | * @return The newly created monitor 747 | */ 748 | monitor_t* addmon(state_t* s, area_t a, uint32_t idx); 749 | 750 | /** 751 | * @brief Returns the monitor within the list of monitors 752 | * that matches the given area. 753 | * Returns NULL if there is no monitor associated with the area. 754 | * 755 | * @param s The window manager's state 756 | * @param a The area to find within the monitors 757 | * 758 | * @return The monitor associated with the given area (NULL if no associated monitor) 759 | */ 760 | monitor_t* monbyarea(state_t* s, area_t a); 761 | 762 | 763 | /** 764 | * @brief Returns the monitor in which a given client is contained in. 765 | * any registered monitor. 766 | * 767 | * @param s The window manager's state 768 | * @param cl The client to get the monitor of 769 | * 770 | * @return The monitor that the given client is contained in. 771 | * Returns the first monitor if the given client is not contained within 772 | */ 773 | monitor_t* clientmon(state_t* s, client_t* cl); 774 | 775 | 776 | void updateclienthints(state_t* s, client_t* cl); 777 | 778 | /** 779 | * @brief Returns the currently selected virtual desktop on 780 | * a given monitor 781 | * 782 | * @param s The window manager's state 783 | * @param mon The monitor to get the selected desktop of 784 | * 785 | * @return The selected virtual desktop on the given monitor 786 | */ 787 | desktop_t* mondesktop(state_t* s, monitor_t* mon); 788 | 789 | /** 790 | * @brief Returns the monitor under the cursor 791 | * Returns the first monitor if there is no monitor under the cursor. 792 | * 793 | * @param s The window manager's state 794 | * 795 | * @return The monitor that is under the cursor. 796 | */ 797 | monitor_t* cursormon(state_t* s); 798 | 799 | /** 800 | * @brief Gets all screens registed by xrandr and adds newly registered 801 | * monitors to the linked list of monitors in the window manager. 802 | * 803 | * @param s The window manager's state 804 | * 805 | * @return The number of newly registered monitors 806 | */ 807 | uint32_t updatemons(state_t* s); 808 | 809 | /** 810 | * @brief Returns the keysym of a given key code. 811 | * Returns 0 if there is no keysym for the given keycode. 812 | * 813 | * @param s The window manager's state 814 | * @param keycode The keycode to get the keysym from 815 | * 816 | * @return The keysym of the given keycode (0 if no keysym associated) 817 | */ 818 | xcb_keysym_t getkeysym(state_t* s, xcb_keycode_t keycode); 819 | 820 | /** 821 | * @brief Returns the keycode of a given keysym. 822 | * Returns NULL if there is no keycode for the given keysym. 823 | * 824 | * @param s The window manager's state 825 | * @param keysym The keysym to get the keycode from 826 | * 827 | * @return The keycode of the given keysym (NULL if no keysym associated) 828 | */ 829 | xcb_keycode_t* getkeycodes(state_t* s, xcb_keysym_t keysym); 830 | 831 | /** 832 | * @brief Reads the _NET_WM_STRUT_PARTIAL atom of a given window and 833 | * uses the gathered strut information to populate a strut_t. 834 | * 835 | * @param s The window manager's state 836 | * @param win The window to retrieve the strut of 837 | * 838 | * @return The strut of the window 839 | */ 840 | strut_t readstrut(state_t* s, xcb_window_t win); 841 | 842 | /** 843 | * @brief Gathers the struts of all subwindows of a given window 844 | * 845 | * @param s The window manager's state 846 | * @param win The window to use as a root 847 | */ 848 | void getwinstruts(state_t* s, xcb_window_t win); 849 | 850 | /** 851 | * @brief Retrieves an intern X atom by name. 852 | * 853 | * @param s The window manager's state 854 | * @param atomstr The string (name) of the atom to retrieve 855 | * 856 | * @return The XCB atom associated with the given atom name. 857 | * Returns XCB_ATOM_NONE if the given name is not associated with 858 | * any atom. 859 | * */ 860 | xcb_atom_t getatom(state_t* s, const char* atomstr); 861 | 862 | 863 | /** 864 | * 865 | * @brief Updates the client list EWMH atom tothe current list of clients 866 | * 867 | * @param s The window manager's state 868 | * */ 869 | void ewmh_updateclients(state_t* s); 870 | 871 | /** 872 | * @brief Signal handler for SIGCHLD to avoid zombie processes 873 | * */ 874 | void sigchld_handler(int32_t signum); 875 | 876 | /** 877 | * @brief Checks if a given string is within a given 878 | * array of strings 879 | * @param array Haystack 880 | * @param count Number of items within the haystack 881 | * @param target Needle 882 | * 883 | * @return Wether or not the given string was found in the 884 | * given array 885 | * */ 886 | bool strinarr(char* array[], int count, const char* target); 887 | 888 | /* 889 | * @brief Compares two strings 890 | * @param a The first string to compare 891 | * @param b The second string to compare 892 | * @return See 'man strcmp' */ 893 | int32_t compstrs(const void* a, const void* b); 894 | 895 | 896 | /** 897 | * @brief Logs a given message to stdout/stderr that differs based 898 | * on the log level given. This function also logs the given message to 899 | * the logfile specified in the config if 'shouldlogtofile' is enabled. 900 | * config. 901 | * @param lvl The log level 902 | * @param fmt The format string 903 | * @param ... The variadic arguments */ 904 | void logmsg(state_t* s, log_level_t lvl, const char* fmt, ...); 905 | 906 | /** 907 | * @brief Logs a given formatted message to the log file specified in the 908 | * config. 909 | * @param fmt The format string 910 | * @param ... The variadic arguments */ 911 | void logtofile(log_level_t lvl, state_t* s, const char* fmt, va_list args); 912 | 913 | 914 | /** 915 | * @brief Returns the output of a given command 916 | * 917 | * @param cmd The command to get the output of 918 | * 919 | * @return The output of the given command */ 920 | char* cmdoutput(const char* cmd); 921 | 922 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------