kranewm

An ICCCM & EWMH compliant X11 reparenting, dynamic window manager, written in C++
git clone git clone git://git.deurzen.net/kranewm.git
Log | Files | Refs | LICENSE

commit 864d116b90407682a6a906e918ace9d2d979a200
Author: deurzen <m.deurzen@tum.de>
Date:   Mon, 21 Jun 2021 09:38:33 +0200

initial commit

Diffstat:
A.gitignore | 8++++++++
ALICENSE | 27+++++++++++++++++++++++++++
AMakefile | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aconfig.mk | 45+++++++++++++++++++++++++++++++++++++++++++++
Alaunch | 4++++
Asrc/bar/main.cc | 5+++++
Asrc/client/main.cc | 5+++++
Asrc/core/bindings.hh | 28++++++++++++++++++++++++++++
Asrc/core/client.cc | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/client.hh | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/context.cc | 0
Asrc/core/context.hh | 30++++++++++++++++++++++++++++++
Asrc/core/contrib/actions.cc | 0
Asrc/core/contrib/actions.hh | 0
Asrc/core/contrib/extensions/ipc.cc | 0
Asrc/core/contrib/extensions/ipc.hh | 0
Asrc/core/contrib/extensions/scratchpad.cc | 0
Asrc/core/contrib/extensions/scratchpad.hh | 0
Asrc/core/contrib/hooks.cc | 0
Asrc/core/contrib/hooks.hh | 0
Asrc/core/contrib/layouts.cc | 0
Asrc/core/contrib/layouts.hh | 0
Asrc/core/cycle.hh | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/cycle.t.hh | 609+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/defaults.hh | 8++++++++
Asrc/core/jump.hh | 144+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/layout.cc | 1327+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/layout.hh | 174+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/main.cc | 13+++++++++++++
Asrc/core/model.cc | 3819+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/model.hh | 324+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/partition.cc | 2++
Asrc/core/partition.hh | 41+++++++++++++++++++++++++++++++++++++++++
Asrc/core/placement.hh | 43+++++++++++++++++++++++++++++++++++++++++++
Asrc/core/rules.hh | 30++++++++++++++++++++++++++++++
Asrc/core/stack.cc | 120+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/stack.hh | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/workspace.cc | 575+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/core/workspace.hh | 235+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/common.hh | 23+++++++++++++++++++++++
Asrc/winsys/connection.cc | 0
Asrc/winsys/connection.hh | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/decoration.cc | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/decoration.hh | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/event.hh | 184+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/geometry.cc | 30++++++++++++++++++++++++++++++
Asrc/winsys/geometry.hh | 117+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/hints.cc | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/hints.hh | 42++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/input.hh | 394+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/screen.cc | 141+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/screen.hh | 109+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/util.cc | 16++++++++++++++++
Asrc/winsys/util.hh | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/window.hh | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/xdata/xconnection.cc | 3395+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/winsys/xdata/xconnection.hh | 319+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Axinitrc | 7+++++++
58 files changed, 13544 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,8 @@ +/release +/bin +/obj +/tags +/debug +/test +/spdlog +/TODO diff --git a/LICENSE b/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2021, Max van Deurzen <max@vandeurzen.com> +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Makefile b/Makefile @@ -0,0 +1,91 @@ +include config.mk + +all: quick_debug + +quick_build: + $(MAKE) -j$$(( 10 * $(shell nproc) )) build + +quick_debug: + $(MAKE) -j$$(( 10 * $(shell nproc) )) debug + +debug: CXXFLAGS += $(DEBUG_CXXFLAGS) +debug: LDFLAGS += $(DEBUG_LDFLAGS) +debug: winsys +debug: core +debug: client +debug: bar + +$(MAKE) run tags + +release: CXXFLAGS += $(RELEASE_CXXFLAGS) +release: LDFLAGS += $(RELEASE_LDFLAGS) +release: winsys +release: core +release: client +release: bar + +install: + install -m0755 $(BINDIR)/$(PROJECT) $(DESTDIR)$(INSTALLDIR)/$(PROJECT) + install -m0755 $(BINDIR)/$(CLIENT) $(DESTDIR)$(INSTALLDIR)/$(CLIENT) + install -m0755 $(BINDIR)/$(BAR) $(DESTDIR)$(INSTALLDIR)/$(BAR) + +winsys: bin obj ${WINSYS_LINK_FILES} + +core: bin obj ${CORE_LINK_FILES} + ${CC} ${CXXFLAGS} ${CORE_LINK_FILES} ${LDFLAGS} -o $(BINDIR)/$(PROJECT) + +client: bin obj ${CLIENT_LINK_FILES} + ${CC} ${CXXFLAGS} ${CLIENT_LINK_FILES} ${LDFLAGS} -o $(BINDIR)/$(CLIENT) + +bar: bin obj ${BAR_LINK_FILES} + ${CC} ${CXXFLAGS} ${BAR_LINK_FILES} ${LDFLAGS} -o $(BINDIR)/$(BAR) + +-include $(DEPS) + +obj/%.o: obj + +obj/winsys/%.o: src/winsys/%.cc + ${CC} ${CXXFLAGS} -MMD -c $< -o $@ + +obj/winsys/xdata/%.o: src/winsys/xdata/%.cc + ${CC} ${CXXFLAGS} -MMD -c $< -o $@ + +obj/core/%.o: src/core/%.cc + ${CC} ${CXXFLAGS} -MMD -c $< -o $@ + +obj/client/%.o: src/client/%.cc + ${CC} ${CXXFLAGS} -MMD -c $< -o $@ + +obj/bar/%.o: src/bar/%.cc + ${CC} ${CXXFLAGS} -MMD -c $< -o $@ + +run: + @echo [running] + @./launch + +bin: + @[ -d bin ] || mkdir bin + +obj: + @[ -d obj ] || mkdir -p obj/{winsys/xdata,core,client,bar} + +notify-core: + @echo [building core] + +notify-client: + @echo [building client] + +notify-bar: + @echo [building bar] + +notify-link: + @echo [linking] + +.PHONY: tags +tags: + @echo [generating tags] + @git ls-files | ctags -R --exclude=.git --c++-kinds=+p --links=no --fields=+iaS --extras=+q -L- + +.PHONY: clean +clean: + @echo [cleaning] + @rm -rf ./bin ./obj diff --git a/config.mk b/config.mk @@ -0,0 +1,45 @@ +PROJECT = kranewm +BAR = kranebar +CLIENT = kranec + +OBJDIR = obj +SRCDIR = src + +BINDIR = bin +INSTALLDIR = /usr/local/bin + +BAR_SRC_FILES := $(wildcard src/bar/*.cc) +BAR_OBJ_FILES := $(patsubst src/bar/%.cc,obj/bar/%.o,${BAR_SRC_FILES}) + +CLIENT_SRC_FILES := $(wildcard src/client/*.cc) +CLIENT_OBJ_FILES := $(patsubst src/client/%.cc,obj/client/%.o,${CLIENT_SRC_FILES}) + +CORE_SRC_FILES := $(wildcard src/core/*.cc) +CORE_OBJ_FILES := $(patsubst src/core/%.cc,obj/core/%.o,${CORE_SRC_FILES}) + +WINSYS_SRC_FILES := $(wildcard src/winsys/*.cc) +WINSYS_OBJ_FILES := $(patsubst src/winsys/%.cc,obj/winsys/%.o,${WINSYS_SRC_FILES}) + +X_DATA_SRC_FILES := $(wildcard src/winsys/xdata/*.cc) +X_DATA_OBJ_FILES := $(patsubst src/winsys/xdata/%.cc,obj/winsys/xdata/%.o,${X_DATA_SRC_FILES}) + +WINSYS_LINK_FILES := ${WINSYS_OBJ_FILES} ${X_DATA_OBJ_FILES} +BAR_LINK_FILES := ${WINSYS_OBJ_FILES} ${X_DATA_OBJ_FILES} ${BAR_OBJ_FILES} +CLIENT_LINK_FILES := ${WINSYS_OBJ_FILES} ${X_DATA_OBJ_FILES} ${CLIENT_OBJ_FILES} +CORE_LINK_FILES := ${WINSYS_OBJ_FILES} ${X_DATA_OBJ_FILES} ${CORE_OBJ_FILES} + +H_FILES := $(shell find $(SRCDIR) -name '*.hh') +SRC_FILES := $(shell find $(SRCDIR) -name '*.cc') +OBJ_FILES := ${WINSYS_OBJ_FILES} ${X_DATA_OBJ_FILES} ${CORE_OBJ_FILES} +DEPS = $(OBJ_FILES:%.o=%.d) + +SANFLAGS = -fsanitize=undefined -fsanitize=address -fsanitize-address-use-after-scope +CXXFLAGS = -std=c++20 -I./spdlog/include +LDFLAGS = `pkg-config --libs x11 xrandr xres libprocps` + +DEBUG_CXXFLAGS = -Wall -Wpedantic -Wextra -Wold-style-cast -g -DDEBUG ${SANFLAGS} +DEBUG_LDFLAGS = ${SANFLAGS} +RELEASE_CXXFLAGS = -march=native -mtune=native -O3 -flto +RELEASE_LDFLAGS = -flto + +CC = g++ diff --git a/launch b/launch @@ -0,0 +1,4 @@ +#!/bin/bash +set -e +XEPHYR=$(which Xephyr) +xinit ./xinitrc -- "$XEPHYR" :102 -ac -screen 800x600 -host-cursor diff --git a/src/bar/main.cc b/src/bar/main.cc @@ -0,0 +1,5 @@ +int +main(int, char **) +{ + return 0; +} diff --git a/src/client/main.cc b/src/client/main.cc @@ -0,0 +1,5 @@ +int +main(int, char **) +{ + return 0; +} diff --git a/src/core/bindings.hh b/src/core/bindings.hh @@ -0,0 +1,28 @@ +#ifndef __BINDINGS_H_GUARD__ +#define __BINDINGS_H_GUARD__ + +#include "../winsys/input.hh" +#include "client.hh" + +#include <functional> +#include <optional> + +class Model; + +typedef + std::function<void(Model&)> + KeyAction; + +typedef + std::function<bool(Model&, Client_ptr)> + MouseAction; + +typedef + std::unordered_map<winsys::KeyInput, KeyAction> + KeyBindings; + +typedef + std::unordered_map<winsys::MouseInput, MouseAction> + MouseBindings; + +#endif//__BINDINGS_H_GUARD__ diff --git a/src/core/client.cc b/src/core/client.cc @@ -0,0 +1,218 @@ +#include "client.hh" +#include <iostream> + +Client::Client( + winsys::Window window, + winsys::Window frame, + std::string name, + std::string class_, + std::string instance, + Index partition, + Index context, + Index workspace, + winsys::WindowType window_type, + std::optional<winsys::Pid> pid, + std::optional<winsys::Pid> ppid +) + : window(window), + frame(frame), + name(name), + class_(class_), + instance(instance), + partition(partition), + context(context), + workspace(workspace), + window_type(window_type), + free_region({}), + tile_region({}), + active_region({}), + previous_region({}), + inner_region({}), + tile_decoration(winsys::Decoration::FREE_DECORATION), + free_decoration(winsys::Decoration::FREE_DECORATION), + active_decoration(winsys::Decoration::FREE_DECORATION), + size_hints(std::nullopt), + warp_pos(std::nullopt), + parent(nullptr), + children({}), + leader(nullptr), + producer(nullptr), + consumers({}), + focused(false), + mapped(false), + managed(true), + urgent(false), + floating(false), + fullscreen(false), + contained(false), + invincible(false), + sticky(false), + iconifyable(true), + iconified(false), + disowned(false), + producing(true), + pid(pid), + ppid(ppid), + last_focused(std::chrono::steady_clock::now()), + managed_since(std::chrono::steady_clock::now()), + expected_unmap_count(0), + m_outside_state(OutsideState::Unfocused) +{} + +Client::~Client() +{} + +Client::OutsideState +Client::get_outside_state() const +{ + if (urgent) + return Client::OutsideState::Urgent; + + return m_outside_state; +} + +void +Client::focus() +{ + focused = true; + last_focused = std::chrono::steady_clock::now(); + + switch (m_outside_state) { + case OutsideState::Unfocused: m_outside_state = OutsideState::Focused; return; + case OutsideState::UnfocusedDisowned: m_outside_state = OutsideState::FocusedDisowned; return; + case OutsideState::UnfocusedSticky: m_outside_state = OutsideState::FocusedSticky; return; + default: return; + } +} + +void +Client::unfocus() +{ + focused = false; + + switch (m_outside_state) { + case OutsideState::Focused: m_outside_state = OutsideState::Unfocused; return; + case OutsideState::FocusedDisowned: m_outside_state = OutsideState::UnfocusedDisowned; return; + case OutsideState::FocusedSticky: m_outside_state = OutsideState::UnfocusedSticky; return; + default: return; + } +} + +void +Client::stick() +{ + sticky = true; + + switch (m_outside_state) { + case OutsideState::Focused: m_outside_state = OutsideState::FocusedSticky; return; + case OutsideState::Unfocused: m_outside_state = OutsideState::UnfocusedSticky; return; + default: return; + } +} + +void +Client::unstick() +{ + sticky = false; + + switch (m_outside_state) { + case OutsideState::FocusedSticky: m_outside_state = OutsideState::Focused; return; + case OutsideState::UnfocusedSticky: m_outside_state = OutsideState::Unfocused; return; + default: return; + } +} + +void +Client::disown() +{ + disowned = true; + + switch (m_outside_state) { + case OutsideState::Focused: m_outside_state = OutsideState::FocusedDisowned; return; + case OutsideState::Unfocused: m_outside_state = OutsideState::UnfocusedDisowned; return; + default: return; + } +} + +void +Client::reclaim() +{ + disowned = false; + + switch (m_outside_state) { + case OutsideState::FocusedDisowned: m_outside_state = OutsideState::Focused; return; + case OutsideState::UnfocusedDisowned: m_outside_state = OutsideState::Unfocused; return; + default: return; + } +} + +void +Client::expect_unmap() +{ + expected_unmap_count += 1; +} + +bool +Client::consume_unmap_if_expecting() +{ + bool expecting = expected_unmap_count > 0; + + if (expecting) + expected_unmap_count -= 1; + + return expecting; +} + +void +Client::set_tile_region(winsys::Region& region) +{ + tile_region = region; + set_active_region(region); +} + +void +Client::set_free_region(winsys::Region& region) +{ + free_region = region; + set_active_region(region); +} + +void +Client::set_active_region(winsys::Region& region) +{ + previous_region = active_region; + set_inner_region(region); + active_region = region; +} + + +void +Client::set_tile_decoration(winsys::Decoration const& decoration) +{ + tile_decoration = decoration; + active_decoration = decoration; +} + +void +Client::set_free_decoration(winsys::Decoration const& decoration) +{ + free_decoration = decoration; + active_decoration = decoration; +} + +void +Client::set_inner_region(winsys::Region& region) +{ + if (active_decoration.frame) { + winsys::Frame& frame = *active_decoration.frame; + + inner_region.pos.x = frame.extents.left; + inner_region.pos.y = frame.extents.top; + inner_region.dim.w = region.dim.w - frame.extents.left - frame.extents.right; + inner_region.dim.h = region.dim.h - frame.extents.top - frame.extents.bottom; + } else { + inner_region.pos.x = 0; + inner_region.pos.y = 0; + inner_region.dim = region.dim; + } +} diff --git a/src/core/client.hh b/src/core/client.hh @@ -0,0 +1,152 @@ +#ifndef __CLIENT_H_GUARD__ +#define __CLIENT_H_GUARD__ + +#include "../winsys/common.hh" +#include "../winsys/decoration.hh" +#include "../winsys/geometry.hh" +#include "../winsys/hints.hh" +#include "../winsys/window.hh" + +#include <chrono> +#include <optional> +#include <string> +#include <vector> + +typedef struct Client* Client_ptr; +typedef struct Client final +{ + enum class OutsideState + { + Focused, + FocusedDisowned, + FocusedSticky, + Unfocused, + UnfocusedDisowned, + UnfocusedSticky, + Urgent + }; + + static constexpr winsys::Dim MIN_CLIENT_DIM = winsys::Dim { 25, 10 }; + static constexpr winsys::Dim PREFERRED_CLIENT_DIM = winsys::Dim { 480, 260 }; + + static bool + is_free(Client_ptr client) + { + return (client->floating && (!client->fullscreen || client->contained)) + || !client->managed + || client->disowned; + } + + Client( + winsys::Window window, + winsys::Window frame, + std::string name, + std::string class_, + std::string instance, + Index partition, + Index context, + Index workspace, + winsys::WindowType window_type, + std::optional<winsys::Pid> pid, + std::optional<winsys::Pid> ppid + ); + + ~Client(); + + Client(const Client&) = default; + Client& operator=(const Client&) = default; + + OutsideState get_outside_state() const; + + void focus(); + void unfocus(); + + void stick(); + void unstick(); + + void disown(); + void reclaim(); + + void set_urgent(); + void unset_urgent(); + + void expect_unmap(); + bool consume_unmap_if_expecting(); + + void set_tile_region(winsys::Region&); + void set_free_region(winsys::Region&); + + void set_tile_decoration(winsys::Decoration const&); + void set_free_decoration(winsys::Decoration const&); + + winsys::Window window; + winsys::Window frame; + std::string name; + std::string class_; + std::string instance; + Index partition; + Index context; + Index workspace; + winsys::WindowType window_type; + winsys::Region free_region; + winsys::Region tile_region; + winsys::Region active_region; + winsys::Region previous_region; + winsys::Region inner_region; + winsys::Decoration tile_decoration; + winsys::Decoration free_decoration; + winsys::Decoration active_decoration; + std::optional<winsys::SizeHints> size_hints; + std::optional<winsys::Pos> warp_pos; + Client_ptr parent; + std::vector<Client_ptr> children; + Client_ptr leader; + Client_ptr producer; + std::vector<Client_ptr> consumers; + bool focused; + bool mapped; + bool managed; + bool urgent; + bool floating; + bool fullscreen; + bool contained; + bool invincible; + bool sticky; + bool iconifyable; + bool iconified; + bool disowned; + bool producing; + std::optional<winsys::Pid> pid; + std::optional<winsys::Pid> ppid; + std::chrono::time_point<std::chrono::steady_clock> last_focused; + std::chrono::time_point<std::chrono::steady_clock> managed_since; + std::size_t expected_unmap_count; + +private: + OutsideState m_outside_state; + + void set_inner_region(winsys::Region&); + void set_active_region(winsys::Region&); + +}* Client_ptr; + +inline bool +operator==(const Client& lhs, const Client& rhs) +{ + return lhs.window == rhs.window; +} + +namespace std +{ + template <> + struct hash<Client> + { + std::size_t + operator()(const Client& client) const + { + return std::hash<winsys::Window>{}(client.window); + } + }; +} + +#endif//__CLIENT_H_GUARD__ diff --git a/src/core/context.cc b/src/core/context.cc diff --git a/src/core/context.hh b/src/core/context.hh @@ -0,0 +1,30 @@ +#ifndef __CONTEXT_H_GUARD__ +#define __CONTEXT_H_GUARD__ + +#include "../winsys/common.hh" + +#include <string> + +typedef class Context final +{ +public: + Context(Index index, std::string name) + : m_index(index), + m_name(name) + {} + + ~Context() {} + + Index + index() const + { + return m_index; + } + +private: + Index m_index; + std::string m_name; + +}* Context_ptr; + +#endif//__CONTEXT_H_GUARD__ diff --git a/src/core/contrib/actions.cc b/src/core/contrib/actions.cc diff --git a/src/core/contrib/actions.hh b/src/core/contrib/actions.hh diff --git a/src/core/contrib/extensions/ipc.cc b/src/core/contrib/extensions/ipc.cc diff --git a/src/core/contrib/extensions/ipc.hh b/src/core/contrib/extensions/ipc.hh diff --git a/src/core/contrib/extensions/scratchpad.cc b/src/core/contrib/extensions/scratchpad.cc diff --git a/src/core/contrib/extensions/scratchpad.hh b/src/core/contrib/extensions/scratchpad.hh diff --git a/src/core/contrib/hooks.cc b/src/core/contrib/hooks.cc diff --git a/src/core/contrib/hooks.hh b/src/core/contrib/hooks.hh diff --git a/src/core/contrib/layouts.cc b/src/core/contrib/layouts.cc diff --git a/src/core/contrib/layouts.hh b/src/core/contrib/layouts.hh diff --git a/src/core/cycle.hh b/src/core/cycle.hh @@ -0,0 +1,149 @@ +#ifndef __CYCLE_H_GUARD__ +#define __CYCLE_H_GUARD__ + +#include "../winsys/common.hh" +#include "../winsys/geometry.hh" + +#include <cstdlib> +#include <deque> +#include <vector> +#include <optional> +#include <unordered_map> +#include <variant> + +enum class StackAction +{ + Insert, + Remove +}; + +template <typename T> +class HistoryStack final +{ + static_assert(std::is_pointer<T>::value, + "Only pointer types may be stored in a history stack."); + +public: + HistoryStack(); + ~HistoryStack(); + + void clear(); + void push_back(T); + void replace(T, T); + std::optional<T> peek_back() const; + std::optional<T> pop_back(); + void remove(T); + + std::vector<T> const& as_vector() const; + +private: + std::vector<T> m_stack; + +}; + +template <typename T> +class Cycle final +{ + static_assert(std::is_pointer<T>::value, + "Only pointer types may be stored in a cycle."); + +public: + Cycle(std::vector<T>, bool); + Cycle(std::initializer_list<T>, bool); + ~Cycle(); + + bool next_will_wrap(winsys::Direction) const; + bool empty() const; + bool contains(T) const; + bool is_active_element(T) const; + bool is_active_index(Index) const; + + std::size_t size() const; + std::size_t length() const; + + std::optional<Index> index() const; + Index active_index() const; + Index last_index() const; + Index next_index(winsys::Direction) const; + Index next_index_from(Index, winsys::Direction) const; + + std::optional<Index> index_of_element(const T) const; + + std::optional<T> next_element(winsys::Direction) const; + std::optional<T> active_element() const; + std::optional<T> prev_active_element() const; + std::optional<T> element_at_index(Index) const; + std::optional<T> element_at_front(T) const; + std::optional<T> element_at_back(T) const; + + void activate_first(); + void activate_last(); + void activate_at_index(Index); + void activate_element(T); + + bool remove_first(); + bool remove_last(); + bool remove_at_index(Index); + bool remove_element(T); + + std::optional<T> pop_back(); + + void replace_element(T, T); + void swap_elements(T, T); + void swap_indices(Index, Index); + + void rotate(winsys::Direction); + std::optional<T> cycle_active(winsys::Direction); + std::optional<T> drag_active(winsys::Direction); + + void insert_at_front(T); + void insert_at_back(T); + void insert_before_index(Index, T); + void insert_after_index(Index, T); + void insert_before_element(T, T); + void insert_after_element(T, T); + + void clear(); + + std::deque<T> const& as_deque() const; + std::vector<T> const& stack() const; + + std::deque<T>::iterator + begin() + { + return m_elements.begin(); + } + + std::deque<T>::iterator + end() + { + return m_elements.end(); + } + + T operator[](std::size_t index) + { + return m_elements[index]; + } + + const T operator[](std::size_t index) const + { + return m_elements[index]; + } + +private: + Index m_index; + + std::deque<T> m_elements; + + bool m_unwindable; + HistoryStack<T> m_stack; + + void sync_active(); + + void push_index_to_stack(std::optional<Index>); + void push_active_to_stack(); + std::optional<T> get_active_from_stack(); + +}; + +#endif//__CYCLE_H_GUARD__ diff --git a/src/core/cycle.t.hh b/src/core/cycle.t.hh @@ -0,0 +1,609 @@ +#ifndef __CYCLE_T_H_GUARD__ +#define __CYCLE_T_H_GUARD__ + +#include "../winsys/util.hh" +#include "cycle.hh" + +template <typename T> +HistoryStack<T>::HistoryStack() + : m_stack({}) +{} + +template <typename T> +HistoryStack<T>::~HistoryStack() +{} + +template <typename T> +void +HistoryStack<T>::clear() +{ + m_stack.clear(); +} + +template <typename T> +void +HistoryStack<T>::push_back(T element) +{ + m_stack.push_back(element); +} + +template <typename T> +void +HistoryStack<T>::replace(T element, T replacement) +{ + std::optional<Index> index = Util::index_of(m_stack, element); + + if (index) + m_stack[*index] = replacement; +} + +template <typename T> +std::optional<T> +HistoryStack<T>::peek_back() const +{ + if (!m_stack.empty()) + return m_stack.back(); + + return std::nullopt; +} + +template <typename T> +std::optional<T> +HistoryStack<T>::pop_back() +{ + if (!m_stack.empty()) { + T element = m_stack.back(); + m_stack.pop_back(); + return element; + } + + return std::nullopt; +} + +template <typename T> +void +HistoryStack<T>::remove(T element) +{ + Util::erase_remove(m_stack, element); +} + +template <typename T> +std::vector<T> const& +HistoryStack<T>::as_vector() const +{ + return m_stack; +} + + +template <typename T> +Cycle<T>::Cycle(std::vector<T> elements, bool unwindable) + : m_index(Util::last_index(elements)), + m_elements({}), + m_unwindable(unwindable), + m_stack(HistoryStack<T>()) +{ + m_elements.resize(elements.size()); + std::copy(elements.begin(), elements.end(), m_elements.begin()); +} + +template <typename T> +Cycle<T>::Cycle(std::initializer_list<T> elements, bool unwindable) + : m_index(0), + m_elements({}), + m_unwindable(unwindable), + m_stack(HistoryStack<T>()) +{ + std::copy(elements.begin(), elements.end(), m_elements.begin()); + m_index = Util::last_index(m_elements); +} + +template <typename T> +Cycle<T>::~Cycle() +{} + +template <typename T> +bool +Cycle<T>::next_will_wrap(winsys::Direction direction) const +{ + switch (direction) { + case winsys::Direction::Backward: return m_index == 0; + case winsys::Direction::Forward: return m_index == Util::last_index(m_elements); + } +} + +template <typename T> +bool +Cycle<T>::empty() const +{ + return m_elements.empty(); +} + +template <typename T> +bool +Cycle<T>::contains(T element) const +{ + return Util::contains(m_elements, element); +} + +template <typename T> +bool +Cycle<T>::is_active_element(T element) const +{ + std::optional<Index> current = this->index(); + return current && *current == index_of_element(element); +} + +template <typename T> +bool +Cycle<T>::is_active_index(Index index) const +{ + std::optional<Index> current = this->index(); + return current && *current == index; +} + + +template <typename T> +std::size_t +Cycle<T>::size() const +{ + return m_elements.size(); +} + +template <typename T> +std::size_t +Cycle<T>::length() const +{ + return m_elements.size(); +} + + +template <typename T> +std::optional<Index> +Cycle<T>::index() const +{ + if (m_index < m_elements.size()) + return m_index; + + return std::nullopt; +} + +template <typename T> +Index +Cycle<T>::active_index() const +{ + return m_index; +} + +template <typename T> +Index +Cycle<T>::last_index() const +{ + return Util::last_index(m_elements); +} + +template <typename T> +Index +Cycle<T>::next_index(winsys::Direction direction) const +{ + return next_index_from(m_index, direction); +} + +template <typename T> +Index +Cycle<T>::next_index_from(Index index, winsys::Direction direction) const +{ + Index end = Util::last_index(m_elements); + + switch (direction) { + case winsys::Direction::Backward: return index == 0 ? end : index - 1; + case winsys::Direction::Forward: return index == end ? 0 : index + 1; + default: return index; + } +} + + +template <typename T> +std::optional<Index> +Cycle<T>::index_of_element(const T element) const +{ + return Util::index_of(m_elements, element); +} + + +template <typename T> +std::optional<T> +Cycle<T>::next_element(winsys::Direction direction) const +{ + std::optional<Index> index = next_index(direction); + if (index && *index < m_elements.size()) + return m_elements[*index]; + + return std::nullopt; +} + +template <typename T> +std::optional<T> +Cycle<T>::active_element() const +{ + if (m_index < m_elements.size()) + return m_elements[m_index]; + + return std::nullopt; +} + +template <typename T> +std::optional<T> +Cycle<T>::prev_active_element() const +{ + return m_stack.peek_back(); +} + +template <typename T> +std::optional<T> +Cycle<T>::element_at_index(Index index) const +{ + if (index < m_elements.size()) + return m_elements[index]; + + return std::nullopt; +} + +template <typename T> +std::optional<T> +Cycle<T>::element_at_front(T) const +{ + if (!m_elements.empty()) + return m_elements[0]; + + return std::nullopt; +} + +template <typename T> +std::optional<T> +Cycle<T>::element_at_back(T) const +{ + if (!m_elements.empty()) + return m_elements[Util::last_index(m_elements)]; + + return std::nullopt; +} + + +template <typename T> +void +Cycle<T>::activate_first() +{ + activate_at_index(0); +} + +template <typename T> +void +Cycle<T>::activate_last() +{ + activate_at_index(Util::last_index(m_elements)); +} + +template <typename T> +void +Cycle<T>::activate_at_index(Index index) +{ + if (index != m_index) { + push_active_to_stack(); + m_index = index; + } +} + +template <typename T> +void +Cycle<T>::activate_element(T element) +{ + std::optional<Index> index = Util::index_of(m_elements, element); + if (index) + activate_at_index(*index); +} + + +template <typename T> +bool +Cycle<T>::remove_first() +{ + bool must_resync = is_active_index(0); + + std::size_t size_before = m_elements.size(); + Util::erase_at_index(m_elements, 0); + + if (must_resync) + sync_active(); + + return size_before != m_elements.size(); +} + +template <typename T> +bool +Cycle<T>::remove_last() +{ + Index end = Util::last_index(m_elements); + bool must_resync = is_active_index(end); + + std::size_t size_before = m_elements.size(); + Util::erase_at_index(m_elements, end); + + if (must_resync) + sync_active(); + + return size_before != m_elements.size(); +} + +template <typename T> +bool +Cycle<T>::remove_at_index(Index index) +{ + bool must_resync = is_active_index(index); + + std::size_t size_before = m_elements.size(); + Util::erase_at_index(m_elements, index); + + if (must_resync) + sync_active(); + + return size_before != m_elements.size(); +} + +template <typename T> +bool +Cycle<T>::remove_element(T element) +{ + bool must_resync = is_active_element(element); + + std::size_t size_before = m_elements.size(); + Util::erase_remove(m_elements, element); + + m_stack.remove(element); + + if (must_resync) + sync_active(); + + return size_before != m_elements.size(); +} + + +template <typename T> +std::optional<T> +Cycle<T>::pop_back() +{ + std::optional<T> value = std::nullopt; + + if (!m_elements.empty()) { + bool must_resync = is_active_element(m_elements.back()); + + value = std::optional(m_elements.back()); + m_elements.pop_back(); + + if (must_resync) + sync_active(); + } + + return value; +} + + +template <typename T> +void +Cycle<T>::replace_element(T element, T replacement) +{ + if (contains(replacement)) + return; + + std::optional<Index> index = index_of_element(element); + + if (index) { + m_elements[*index] = replacement; + m_stack.replace(element, replacement); + } +} + +template <typename T> +void +Cycle<T>::swap_elements(T element1, T element2) +{ + std::optional<Index> index1 = index_of_element(element1); + std::optional<Index> index2 = index_of_element(element2); + + if (index1 && index2) + std::iter_swap(m_elements.begin() + *index1, m_elements.begin() + *index2); +} + +template <typename T> +void +Cycle<T>::swap_indices(Index index1, Index index2) +{ + if (index1 < m_elements.size() && index2 < m_elements.size()) + std::iter_swap(m_elements.begin() + index1, m_elements.begin() + index2); +} + + +template <typename T> +void +Cycle<T>::rotate(winsys::Direction direction) +{ + switch (direction) { + case winsys::Direction::Backward: + { + std::rotate( + m_elements.rbegin(), + std::next(m_elements.rbegin()), + m_elements.rend() + ); + + return; + } + case winsys::Direction::Forward: + { + std::rotate( + m_elements.begin(), + std::next(m_elements.begin()), + m_elements.end() + ); + + return; + } + default: return; + } +} + +template <typename T> +std::optional<T> +Cycle<T>::cycle_active(winsys::Direction direction) +{ + push_active_to_stack(); + m_index = next_index(direction); + return active_element(); +} + +template <typename T> +std::optional<T> +Cycle<T>::drag_active(winsys::Direction direction) +{ + Index index = next_index(direction); + + if (m_index != index && m_index < m_elements.size() && index < m_elements.size()) + std::iter_swap(m_elements.begin() + m_index, m_elements.begin() + index); + + return cycle_active(direction); +} + + +template <typename T> +void +Cycle<T>::insert_at_front(T element) +{ + push_active_to_stack(); + m_elements.push_front(element); + m_index = 0; +} + +template <typename T> +void +Cycle<T>::insert_at_back(T element) +{ + push_active_to_stack(); + m_elements.push_back(element); + m_index = Util::last_index(m_elements); +} + +template <typename T> +void +Cycle<T>::insert_before_index(Index index, T element) +{ + if (index >= m_elements.size()) + index = Util::last_index(m_elements); + + push_active_to_stack(); + m_elements.insert(m_elements.begin() + index, element); +} + +template <typename T> +void +Cycle<T>::insert_after_index(Index index, T element) +{ + if (m_elements.empty() || index >= m_elements.size() - 1) { + insert_at_back(element); + return; + } + + push_active_to_stack(); + m_elements.insert(m_elements.begin() + index + 1, element); +} + +template <typename T> +void +Cycle<T>::insert_before_element(T other, T element) +{ + std::optional<Index> index = index_of_element(other); + + if (index) + insert_before_index(*index, element); + else + insert_at_back(element); +} + +template <typename T> +void +Cycle<T>::insert_after_element(T other, T element) +{ + std::optional<Index> index = index_of_element(other); + + if (index) + insert_after_index(*index, element); + else + insert_at_back(element); +} + + +template <typename T> +void +Cycle<T>::clear() +{ + m_elements.clear(); + m_stack.clear(); +} + + +template <typename T> +void +Cycle<T>::sync_active() +{ + std::optional<T> element = get_active_from_stack(); + for (; element && !contains(*element); element = get_active_from_stack()); + if (element) + m_index = *index_of_element(*element); +} + + +template <typename T> +void +Cycle<T>::push_index_to_stack(std::optional<Index> index) +{ + if (!m_unwindable || !index) + return; + + std::optional<T> element = element_at_index(*index); + if (element) { + m_stack.remove(*element); + m_stack.push_back(*element); + } +} + +template <typename T> +void +Cycle<T>::push_active_to_stack() +{ + if (!m_unwindable) + return; + + push_index_to_stack(index()); +} + +template <typename T> +std::optional<T> +Cycle<T>::get_active_from_stack() +{ + return m_stack.pop_back(); +} + + +template <typename T> +std::deque<T> const& +Cycle<T>::as_deque() const +{ + return m_elements; +} + +template <typename T> +std::vector<T> const& +Cycle<T>::stack() const +{ + return m_stack.as_vector(); +} + +#endif//__CYCLE_T_H_GUARD__ diff --git a/src/core/defaults.hh b/src/core/defaults.hh @@ -0,0 +1,8 @@ +#ifndef __DEFAULTS_H_GUARD__ +#define __DEFAULTS_H_GUARD__ + +#include <string> + +const std::string WM_NAME = "kranewm"; + +#endif//__DEFAULTS_H_GUARD__ diff --git a/src/core/jump.hh b/src/core/jump.hh @@ -0,0 +1,144 @@ +#ifndef __JUMP_H_GUARD__ +#define __JUMP_H_GUARD__ + +#include "../winsys/common.hh" +#include "client.hh" +#include "workspace.hh" + +#include <functional> +#include <string> +#include <utility> + +class JumpSelector final +{ +public: + enum class SelectionCriterium + { + OnWorkspaceBySelector, + ByNameEquals, + ByClassEquals, + ByInstanceEquals, + ByNameContains, + ByClassContains, + ByInstanceContains, + ForCondition, + }; + + JumpSelector(Index index, Workspace::ClientSelector::SelectionCriterium criterium) + : m_tag(JumpSelectorTag::OnWorkspaceBySelector), + m_workspace_selector(std::pair(index, criterium)) + {} + + JumpSelector(SelectionCriterium criterium, std::string&& str_) + : m_string(str_) + { + switch (criterium) { + case SelectionCriterium::ByNameEquals: m_tag = JumpSelectorTag::ByNameEquals; return; + case SelectionCriterium::ByClassEquals: m_tag = JumpSelectorTag::ByClassEquals; return; + case SelectionCriterium::ByInstanceEquals: m_tag = JumpSelectorTag::ByInstanceEquals; return; + case SelectionCriterium::ByNameContains: m_tag = JumpSelectorTag::ByNameContains; return; + case SelectionCriterium::ByClassContains: m_tag = JumpSelectorTag::ByClassContains; return; + case SelectionCriterium::ByInstanceContains: m_tag = JumpSelectorTag::ByInstanceContains; return; + default: return; + } + } + + JumpSelector(std::function<bool(const Client_ptr)>&& filter) + : m_tag(JumpSelectorTag::ForCondition), + m_filter(filter) + {} + + ~JumpSelector() + { + switch (m_tag) { + case JumpSelectorTag::OnWorkspaceBySelector: + { + (&m_workspace_selector)->std::pair< + Index, + Workspace::ClientSelector::SelectionCriterium + >::~pair(); + + return; + } + case JumpSelectorTag::ByNameEquals: // fallthrough + case JumpSelectorTag::ByClassEquals: // fallthrough + case JumpSelectorTag::ByInstanceEquals: // fallthrough + case JumpSelectorTag::ByNameContains: // fallthrough + case JumpSelectorTag::ByClassContains: // fallthrough + case JumpSelectorTag::ByInstanceContains: + { + (&m_string)->std::string::~string(); + + return; + } + case JumpSelectorTag::ForCondition: + { + (&m_filter)->std::function<bool(const Client_ptr)>::~function(); + + return; + } + default: return; + } + } + + SelectionCriterium + criterium() const + { + switch (m_tag) { + case JumpSelectorTag::OnWorkspaceBySelector: return SelectionCriterium::OnWorkspaceBySelector; + case JumpSelectorTag::ByNameEquals: return SelectionCriterium::ByNameEquals; + case JumpSelectorTag::ByClassEquals: return SelectionCriterium::ByClassEquals; + case JumpSelectorTag::ByInstanceEquals: return SelectionCriterium::ByInstanceEquals; + case JumpSelectorTag::ByNameContains: return SelectionCriterium::ByNameContains; + case JumpSelectorTag::ByClassContains: return SelectionCriterium::ByClassContains; + case JumpSelectorTag::ByInstanceContains: return SelectionCriterium::ByInstanceContains; + case JumpSelectorTag::ForCondition: return SelectionCriterium::ForCondition; + default: Util::die("no associated criterium"); + } + + return {}; + } + + std::pair<Index, Workspace::ClientSelector::SelectionCriterium> const& + workspace_selector() const + { + return m_workspace_selector; + } + + std::string const& + string_value() const + { + return m_string; + } + + std::function<bool(const Client_ptr)> const& + filter() const + { + return m_filter; + } + +private: + enum class JumpSelectorTag + { + OnWorkspaceBySelector, + ByNameEquals, + ByClassEquals, + ByInstanceEquals, + ByNameContains, + ByClassContains, + ByInstanceContains, + ForCondition, + }; + + JumpSelectorTag m_tag; + + union + { + std::pair<Index, Workspace::ClientSelector::SelectionCriterium> m_workspace_selector; + std::string m_string; + std::function<bool(const Client_ptr)> m_filter; + }; + +}; + +#endif//__JUMP_H_GUARD__ diff --git a/src/core/layout.cc b/src/core/layout.cc @@ -0,0 +1,1327 @@ +#include "../winsys/util.hh" +#include "client.hh" +#include "cycle.t.hh" +#include "defaults.hh" +#include "layout.hh" + +#include <algorithm> +#include <cmath> +#include <sstream> +#include <fstream> + +using namespace winsys; + +LayoutHandler::Layout::Layout(LayoutKind kind) + : kind(kind), + config(kind_to_config(kind)), + default_data(kind_to_default_data(kind)), + data({}, true) +{ + data.insert_at_back(new LayoutData(default_data)); + data.insert_at_back(new LayoutData(default_data)); + data.insert_at_back(new LayoutData(default_data)); +} + +LayoutHandler::Layout::~Layout() +{ + for (LayoutData_ptr data : data) + delete data; +} + + +LayoutHandler::LayoutHandler() + : m_kind(LayoutKind::Float), + m_prev_kind(LayoutKind::Float), + m_layouts({ + { LayoutKind::Float, new Layout(LayoutKind::Float) }, + { LayoutKind::BLFloat, new Layout(LayoutKind::BLFloat) }, + { LayoutKind::SingleFloat, new Layout(LayoutKind::SingleFloat) }, + { LayoutKind::BLSingleFloat, new Layout(LayoutKind::BLSingleFloat) }, + { LayoutKind::Center, new Layout(LayoutKind::Center) }, + { LayoutKind::Monocle, new Layout(LayoutKind::Monocle) }, + { LayoutKind::Paper, new Layout(LayoutKind::Paper) }, + { LayoutKind::SPaper, new Layout(LayoutKind::SPaper) }, + { LayoutKind::Stack, new Layout(LayoutKind::Stack) }, + { LayoutKind::SStack, new Layout(LayoutKind::SStack) }, + { LayoutKind::BStack, new Layout(LayoutKind::BStack) }, + { LayoutKind::SBStack, new Layout(LayoutKind::SBStack) }, + { LayoutKind::Horz, new Layout(LayoutKind::Horz) }, + { LayoutKind::SHorz, new Layout(LayoutKind::SHorz) }, + { LayoutKind::Vert, new Layout(LayoutKind::Vert) }, + { LayoutKind::SVert, new Layout(LayoutKind::SVert) } + }), + mp_layout(m_layouts.at(m_kind)), + mp_prev_layout(m_layouts.at(m_kind)) +{} + +LayoutHandler::~LayoutHandler() +{ + for (auto [_,layout] : m_layouts) + delete layout; +} + + +void +LayoutHandler::arrange( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + if (mp_layout->config.margin) { + Layout::LayoutData_ptr data = *mp_layout->data.active_element(); + + screen_region.pos.x += data->margin.left; + screen_region.pos.y += data->margin.top; + + screen_region.dim.w -= data->margin.left + data->margin.right; + screen_region.dim.h -= data->margin.top + data->margin.bottom; + } + + switch (m_kind) { + case LayoutKind::Float: arrange_float(screen_region, placements, begin, end); break; + case LayoutKind::BLFloat: arrange_blfloat(screen_region, placements, begin, end); break; + case LayoutKind::SingleFloat: arrange_singlefloat(screen_region, placements, begin, end); break; + case LayoutKind::BLSingleFloat: arrange_blsinglefloat(screen_region, placements, begin, end); break; + case LayoutKind::Center: arrange_center(screen_region, placements, begin, end); break; + case LayoutKind::Monocle: arrange_monocle(screen_region, placements, begin, end); break; + case LayoutKind::Paper: arrange_paper(screen_region, placements, begin, end); break; + case LayoutKind::SPaper: arrange_spaper(screen_region, placements, begin, end); break; + case LayoutKind::Stack: arrange_stack(screen_region, placements, begin, end); break; + case LayoutKind::SStack: arrange_sstack(screen_region, placements, begin, end); break; + case LayoutKind::BStack: arrange_bstack(screen_region, placements, begin, end); break; + case LayoutKind::SBStack: arrange_sbstack(screen_region, placements, begin, end); break; + case LayoutKind::Horz: arrange_horz(screen_region, placements, begin, end); break; + case LayoutKind::SHorz: arrange_shorz(screen_region, placements, begin, end); break; + case LayoutKind::Vert: arrange_vert(screen_region, placements, begin, end); break; + case LayoutKind::SVert: arrange_svert(screen_region, placements, begin, end); break; + } + + if (mp_layout->config.gap) { + Layout::LayoutData_ptr data = *mp_layout->data.active_element(); + + std::for_each( + placements.begin(), + placements.end(), + [data](Placement& placement) { + if (placement.region && !Client::is_free(placement.client)) { + placement.region->pos.x += data->gap_size; + placement.region->pos.y += data->gap_size; + placement.region->dim.w -= 2 * data->gap_size; + placement.region->dim.h -= 2 * data->gap_size; + + placement.region->apply_minimum_dim(Client::MIN_CLIENT_DIM); + } + } + ); + } +} + + +LayoutHandler::LayoutKind +LayoutHandler::kind() const +{ + return m_kind; +} + +void +LayoutHandler::set_kind(LayoutKind kind) +{ + if (kind == m_kind) + return; + + m_prev_kind = m_kind; + m_kind = kind; + + mp_prev_layout = mp_layout; + mp_layout = *Util::const_retrieve(m_layouts, m_kind); +} + +void +LayoutHandler::set_prev_kind() +{ + if (m_prev_kind == m_kind) + return; + + std::swap(m_kind, m_prev_kind); + std::swap(mp_layout, mp_prev_layout); +} + + +bool +LayoutHandler::layout_is_free() const +{ + return mp_layout->config.method == Placement::PlacementMethod::Free; +} + +bool +LayoutHandler::layout_has_margin() const +{ + return mp_layout->config.margin; +} + +bool +LayoutHandler::layout_has_gap() const +{ + return mp_layout->config.gap; +} + +bool +LayoutHandler::layout_is_persistent() const +{ + return mp_layout->config.persistent; +} + +bool +LayoutHandler::layout_is_single() const +{ + return mp_layout->config.single; +} + +bool +LayoutHandler::layout_wraps() const +{ + return mp_layout->config.wraps; +} + + +std::size_t +LayoutHandler::gap_size() const +{ + return (*mp_layout->data.active_element())->gap_size; +} + +std::size_t +LayoutHandler::main_count() const +{ + return (*mp_layout->data.active_element())->main_count; +} + +float +LayoutHandler::main_factor() const +{ + return (*mp_layout->data.active_element())->main_factor; +} + +Extents +LayoutHandler::margin() const +{ + return (*mp_layout->data.active_element())->margin; +} + + +void +LayoutHandler::copy_data_from_prev_layout() +{ + *(*mp_layout->data.active_element()) + = *(*mp_prev_layout->data.active_element()); +} + +void +LayoutHandler::set_prev_layout_data() +{ + std::optional<Layout::LayoutData_ptr> prev_data + = mp_layout->data.prev_active_element(); + + if (prev_data) + mp_layout->data.activate_element(*prev_data); +} + + +void +LayoutHandler::change_gap_size(Util::Change<int> change) +{ + Layout::LayoutData_ptr data = *mp_layout->data.active_element(); + int value = static_cast<int>(data->gap_size) + change; + + if (value <= 0) + data->gap_size = 0; + else if (static_cast<std::size_t>(value) >= Layout::LayoutData::MAX_GAP_SIZE) + data->gap_size = Layout::LayoutData::MAX_GAP_SIZE; + else + data->gap_size = value; +} + +void +LayoutHandler::change_main_count(Util::Change<int> change) +{ + Layout::LayoutData_ptr data = *mp_layout->data.active_element(); + int value = static_cast<int>(data->main_count) + change; + + if (value <= 0) + data->main_count = 0; + else if (static_cast<std::size_t>(value) >= Layout::LayoutData::MAX_MAIN_COUNT) + data->main_count = Layout::LayoutData::MAX_MAIN_COUNT; + else + data->main_count = value; +} + +void +LayoutHandler::change_main_factor(Util::Change<float> change) +{ + Layout::LayoutData_ptr data = *mp_layout->data.active_element(); + float value = data->main_factor + change; + + if (value <= 0.05f) + data->main_factor = 0.05f; + else if (value >= 0.95f) + data->main_factor = 0.95f; + else + data->main_factor = value; +} + +void +LayoutHandler::change_margin(Util::Change<int> change) +{ + change_margin(Edge::Left, change); + change_margin(Edge::Top, change); + change_margin(Edge::Right, change); + change_margin(Edge::Bottom, change); +} + +void +LayoutHandler::change_margin(Edge edge, Util::Change<int> change) +{ + Layout::LayoutData_ptr data = *mp_layout->data.active_element(); + int* margin; + const int* max_value; + + switch (edge) { + case Edge::Left: + { + margin = &data->margin.left; + max_value = &Layout::LayoutData::MAX_MARGIN.left; + break; + } + case Edge::Top: + { + margin = &data->margin.top; + max_value = &Layout::LayoutData::MAX_MARGIN.top; + break; + } + case Edge::Right: + { + margin = &data->margin.right; + max_value = &Layout::LayoutData::MAX_MARGIN.right; + break; + } + case Edge::Bottom: + { + margin = &data->margin.bottom; + max_value = &Layout::LayoutData::MAX_MARGIN.bottom; + break; + } + } + + int value = *margin + change; + + if (value <= 0) + *margin = 0; + else if (value >= *max_value) + *margin = *max_value; + else + *margin = value; +} + +void +LayoutHandler::reset_gap_size() +{ + (*mp_layout->data.active_element())->gap_size + = mp_layout->default_data.gap_size; +} + +void +LayoutHandler::reset_margin() +{ + (*mp_layout->data.active_element())->margin + = mp_layout->default_data.margin; +} + +void +LayoutHandler::reset_layout_data() +{ + *(*mp_layout->data.active_element()) + = mp_layout->default_data; +} + +void +LayoutHandler::cycle_layout_data(Direction direction) +{ + mp_layout->data.cycle_active(direction); +} + + +void +LayoutHandler::save_layout(std::size_t number) const +{ + std::stringstream datadir_ss; + std::string home_path = std::getenv("HOME"); + + if (const char* env_xdgdata = std::getenv("XDG_DATA_HOME")) + datadir_ss << env_xdgdata << "/" << WM_NAME << "/"; + else + datadir_ss << home_path << "/.local/share/" << WM_NAME << "/" + << "layout_" << number; + + std::string file_path = datadir_ss.str(); + + std::vector<Layout::LayoutData> data; + data.reserve(mp_layout->data.size()); + for (Layout::LayoutData_ptr data_ptr : mp_layout->data.as_deque()) + data.push_back(*data_ptr); + + typename std::vector<Layout::LayoutData>::size_type size + = data.size(); + + std::ofstream out(file_path, std::ios::out | std::ios::binary); + out.write(reinterpret_cast<const char*>(&m_kind), sizeof(LayoutKind)); + out.write(reinterpret_cast<const char*>(&size), sizeof(size)); + out.write(reinterpret_cast<const char*>(&data[0]), + data.size() * sizeof(Layout::LayoutData)); + out.close(); +} + +void +LayoutHandler::load_layout(std::size_t number) +{ + std::stringstream datadir_ss; + std::string home_path = std::getenv("HOME"); + + if (const char* env_xdgdata = std::getenv("XDG_DATA_HOME")) + datadir_ss << env_xdgdata << "/" << WM_NAME << "/"; + else + datadir_ss << home_path << "/.local/share/" << WM_NAME << "/" + << "layout_" << number; + + std::string file_path = datadir_ss.str(); + + LayoutKind kind; + std::vector<Layout::LayoutData> data; + typename std::vector<Layout::LayoutData>::size_type size = 0; + + std::ifstream in(file_path, std::ios::in | std::ios::binary); + if (in.good()) { + in.read(reinterpret_cast<char*>(&kind), sizeof(LayoutKind)); + in.read(reinterpret_cast<char*>(&size), sizeof(size)); + + data.resize(size, mp_layout->default_data); + in.read(reinterpret_cast<char*>(&data[0]), + data.size() * sizeof(Layout::LayoutData)); + + set_kind(kind); + for (Layout::LayoutData_ptr data : mp_layout->data) + delete data; + + mp_layout->data.clear(); + for (auto data_ : data) + mp_layout->data.insert_at_back(new Layout::LayoutData(data_)); + } + in.close(); +} + + +void +LayoutHandler::arrange_float( + Region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + std::transform( + begin, + end, + std::back_inserter(placements), + [this](Client_ptr client) -> Placement { + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + client->free_region + }; + } + ); +} + +void +LayoutHandler::arrange_blfloat( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_float(screen_region, placements, begin, end); +} + +void +LayoutHandler::arrange_singlefloat( + Region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + std::transform( + begin, + end, + std::back_inserter(placements), + [this](Client_ptr client) -> Placement { + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + client->focused ? std::optional(client->free_region) : std::nullopt + }; + } + ); +} + +void +LayoutHandler::arrange_blsinglefloat( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_singlefloat(screen_region, placements, begin, end); +} + +void +LayoutHandler::arrange_center( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + const Layout::LayoutData_ptr data = *mp_layout->data.active_element(); + + std::size_t h_comp = Layout::LayoutData::MAX_MAIN_COUNT; + float w_ratio = data->main_factor / 0.95; + float h_ratio = static_cast<float>((h_comp - data->main_count)) + / static_cast<float>(h_comp); + + std::transform( + begin, + end, + std::back_inserter(placements), + [=,this](Client_ptr client) -> Placement { + Region region = screen_region; + + int w = region.dim.w * w_ratio; + int h = region.dim.h * h_ratio; + + if (w <= region.dim.w) + region.pos.x += (region.dim.w - w) / 2.f; + + if (h <= region.dim.h) + region.pos.y += (region.dim.h - h) / 2.f; + + region.dim = { w, h }; + + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + region + }; + } + ); +} + +void +LayoutHandler::arrange_monocle( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + std::transform( + begin, + end, + std::back_inserter(placements), + [=,this](Client_ptr client) { + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + screen_region + }; + } + ); +} + +void +LayoutHandler::arrange_paper( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + static const float MIN_W_RATIO = 0.5; + + const Layout::LayoutData_ptr data = *mp_layout->data.active_element(); + std::size_t n = end - begin; + + if (n == 1) { + placements.emplace_back(Placement { + mp_layout->config.method, + *begin, + Decoration::NO_DECORATION, + screen_region + }); + + return; + } + + int cw; + if (data->main_factor > MIN_W_RATIO) { + cw = screen_region.dim.w * data->main_factor; + } else { + cw = screen_region.dim.w * MIN_W_RATIO; + } + + int w = static_cast<float>(screen_region.dim.w - cw) + / static_cast<float>(n - 1); + + bool contains_active = false; + const auto last_active = std::max_element( + begin, + end, + [&contains_active](const Client_ptr lhs, const Client_ptr rhs) { + if (lhs->focused) { + contains_active = true; + return false; + } else if (rhs->focused) { + contains_active = true; + return true; + } + + return lhs->last_focused < rhs->last_focused; + } + ); + + bool after_active = false; + std::size_t i = 0; + + std::transform( + begin, + end, + std::back_inserter(placements), + [=,this,&after_active,&i](Client_ptr client) -> Placement { + int x = screen_region.pos.x + static_cast<int>(i++ * w); + + if ((!contains_active && *last_active == client) || client->focused) { + after_active = true; + + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + Region { + Pos { + x, + screen_region.pos.y + }, + Dim { + cw, + screen_region.dim.h + } + } + }; + } else { + if (after_active) + x += cw - w; + + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + Region { + Pos { + x, + screen_region.pos.y + }, + Dim { + w, + screen_region.dim.h + } + } + }; + } + } + ); +} + +void +LayoutHandler::arrange_spaper( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_paper(screen_region, placements, begin, end); +} + +void +LayoutHandler::arrange_stack( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + const Layout::LayoutData_ptr data = *mp_layout->data.active_element(); + std::size_t n = end - begin; + + if (n == 1) { + placements.emplace_back(Placement { + mp_layout->config.method, + *begin, + Decoration::NO_DECORATION, + screen_region + }); + + return; + } + + std::size_t n_main; + std::size_t n_stack; + + if (n <= data->main_count) { + n_main = n; + n_stack = 0; + } else { + n_main = data->main_count; + n_stack = n - n_main; + } + + int w_main + = data->main_count > 0 + ? static_cast<float>(screen_region.dim.w) * data->main_factor + : 0; + + int x_stack = screen_region.pos.x + w_main; + int w_stack = screen_region.dim.w - w_main; + int h_main = n_main > 0 ? screen_region.dim.h / n_main : 0; + int h_stack = n_stack > 0 ? screen_region.dim.h / n_stack : 0; + + std::size_t i = 0; + + std::transform( + begin, + end, + std::back_inserter(placements), + [=,this,&i](Client_ptr client) -> Placement { + if (i < data->main_count) { + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + Region { + Pos { + screen_region.pos.x, + screen_region.pos.y + + static_cast<int>(i++) * h_main + }, + Dim { + n_stack == 0 ? screen_region.dim.w : w_main, + h_main + } + } + }; + } else { + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + Region { + Pos { + x_stack, + screen_region.pos.y + + static_cast<int>((i++ - data->main_count) * h_stack) + }, + Dim { + w_stack, + h_stack + } + } + }; + } + } + ); +} + +void +LayoutHandler::arrange_sstack( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_stack(screen_region, placements, begin, end); +} + +void +LayoutHandler::arrange_bstack( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_stack(screen_region, placements, begin, end); +} + +void +LayoutHandler::arrange_sbstack( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_bstack(screen_region, placements, begin, end); +} + +void +LayoutHandler::arrange_horz( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + std::size_t n = end - begin; + + if (n == 1) { + placements.emplace_back(Placement { + mp_layout->config.method, + *begin, + Decoration::NO_DECORATION, + screen_region + }); + + return; + } + + int w = std::lround(static_cast<float>(screen_region.dim.w) / n); + std::size_t i = 0; + + std::transform( + begin, + end, + std::back_inserter(placements), + [=,this,&i](Client_ptr client) -> Placement { + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + Region { + Pos { + screen_region.pos.x + static_cast<int>(i++ * w), + screen_region.pos.y + }, + Dim { + w, + screen_region.dim.h + } + } + }; + } + ); +} + +void +LayoutHandler::arrange_shorz( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_horz(screen_region, placements, begin, end); +} + +void +LayoutHandler::arrange_vert( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + std::size_t n = end - begin; + + if (n == 1) { + placements.emplace_back(Placement { + mp_layout->config.method, + *begin, + Decoration::NO_DECORATION, + screen_region + }); + + return; + } + + int h = std::lround(static_cast<float>(screen_region.dim.h) / n); + std::size_t i = 0; + + std::transform( + begin, + end, + std::back_inserter(placements), + [=,this,&i](Client_ptr client) -> Placement { + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + Region { + Pos { + screen_region.pos.x, + screen_region.pos.y + static_cast<int>(i++ * h) + }, + Dim { + screen_region.dim.w, + h + } + } + }; + } + ); +} + +void +LayoutHandler::arrange_svert( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_vert(screen_region, placements, begin, end); +} + + +LayoutHandler::Layout::LayoutConfig +LayoutHandler::Layout::kind_to_config(LayoutKind kind) +{ + switch (kind) { + case LayoutKind::Float: + { + return LayoutConfig { + Placement::PlacementMethod::Free, + Decoration::FREE_DECORATION, + false, + false, + false, + false, + true + }; + } + case LayoutKind::BLFloat: + { + return LayoutConfig { + Placement::PlacementMethod::Free, + Decoration::NO_DECORATION, + false, + false, + false, + false, + true + }; + } + case LayoutKind::SingleFloat: + { + return LayoutConfig { + Placement::PlacementMethod::Free, + Decoration::FREE_DECORATION, + false, + false, + true, + true, + true + }; + } + case LayoutKind::BLSingleFloat: + { + return LayoutConfig { + Placement::PlacementMethod::Free, + Decoration::NO_DECORATION, + false, + false, + true, + true, + true + }; + } + case LayoutKind::Center: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration::NO_DECORATION, + true, + true, + false, + false, + true + }; + } + case LayoutKind::Monocle: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration::NO_DECORATION, + true, + true, + false, + false, + true + }; + } + case LayoutKind::Paper: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration { + std::nullopt, + Frame { + Extents { 1, 1, 0, 0 }, + ColorScheme::DEFAULT_COLOR_SCHEME + } + }, + true, + true, + true, + false, + false + }; + } + case LayoutKind::SPaper: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration { + std::nullopt, + Frame { + Extents { 1, 1, 0, 0 }, + ColorScheme::DEFAULT_COLOR_SCHEME + } + }, + true, + false, + true, + false, + false + }; + } + case LayoutKind::Stack: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration { + std::nullopt, + Frame { + Extents { 0, 0, 3, 0 }, + ColorScheme::DEFAULT_COLOR_SCHEME + } + }, + true, + true, + false, + false, + true + }; + } + case LayoutKind::SStack: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration { + std::nullopt, + Frame { + Extents { 0, 0, 3, 0 }, + ColorScheme::DEFAULT_COLOR_SCHEME + } + }, + true, + false, + false, + false, + true + }; + } + case LayoutKind::BStack: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration { + std::nullopt, + Frame { + Extents { 0, 0, 3, 0 }, + ColorScheme::DEFAULT_COLOR_SCHEME + } + }, + true, + true, + false, + false, + true + }; + } + case LayoutKind::SBStack: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration { + std::nullopt, + Frame { + Extents { 0, 0, 3, 0 }, + ColorScheme::DEFAULT_COLOR_SCHEME + } + }, + true, + false, + false, + false, + true + }; + } + case LayoutKind::Horz: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration { + std::nullopt, + Frame { + Extents { 0, 0, 3, 0 }, + ColorScheme::DEFAULT_COLOR_SCHEME + } + }, + true, + true, + false, + false, + true + }; + } + case LayoutKind::SHorz: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration { + std::nullopt, + Frame { + Extents { 0, 0, 3, 0 }, + ColorScheme::DEFAULT_COLOR_SCHEME + } + }, + true, + false, + false, + false, + true + }; + } + case LayoutKind::Vert: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration { + std::nullopt, + Frame { + Extents { 3, 0, 0, 0 }, + ColorScheme::DEFAULT_COLOR_SCHEME + } + }, + true, + true, + false, + false, + true + }; + } + case LayoutKind::SVert: + { + return LayoutConfig { + Placement::PlacementMethod::Tile, + Decoration { + std::nullopt, + Frame { + Extents { 3, 0, 0, 0 }, + ColorScheme::DEFAULT_COLOR_SCHEME + } + }, + true, + false, + false, + false, + true + }; + } + default: Util::die("no associated configuration defined"); + } + + return kind_to_config(LayoutKind::Float); +} + +LayoutHandler::Layout::LayoutData +LayoutHandler::Layout::kind_to_default_data(LayoutKind kind) +{ + switch (kind) { + case LayoutKind::Float: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::BLFloat: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::SingleFloat: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::BLSingleFloat: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::Center: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 5, + .40f + }; + } + case LayoutKind::Monocle: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::Paper: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::SPaper: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::Stack: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::SStack: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::BStack: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::SBStack: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::Horz: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::SHorz: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::Vert: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + case LayoutKind::SVert: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 1, + .50f + }; + } + default: Util::die("no associated default data defined"); + } + + return kind_to_default_data(LayoutKind::Float); +} diff --git a/src/core/layout.hh b/src/core/layout.hh @@ -0,0 +1,174 @@ +#ifndef __LAYOUT_H_GUARD__ +#define __LAYOUT_H_GUARD__ + +#include "cycle.hh" +#include "placement.hh" +#include "../winsys/decoration.hh" +#include "../winsys/util.hh" + +#include <vector> +#include <deque> +#include <unordered_map> + +class LayoutHandler final +{ + typedef std::deque<Client_ptr>::const_iterator client_iter; + typedef std::vector<Placement>& placement_vector; + +public: + enum class LayoutKind + { + /// free layouts + Float, + BLFloat, + SingleFloat, + BLSingleFloat, + + // overlapping tiled layouts + Center, + Monocle, + + // non-overlapping tiled layouts + Paper, + SPaper, + Stack, + SStack, + BStack, + SBStack, + Horz, + SHorz, + Vert, + SVert, + }; + +private: + typedef struct Layout final + { + typedef struct LayoutData final + { + constexpr static std::size_t MAX_MAIN_COUNT = 16; + constexpr static std::size_t MAX_GAP_SIZE = 300; + constexpr static winsys::Extents MAX_MARGIN + = winsys::Extents { 700, 700, 400, 400 }; + + LayoutData( + winsys::Extents margin, + std::size_t gap_size, + std::size_t main_count, + float main_factor + ) + : margin(margin), + gap_size(gap_size), + main_count(main_count), + main_factor(main_factor) + {} + + // generic layout data + winsys::Extents margin; + std::size_t gap_size; + + // tiled layout data + std::size_t main_count; + float main_factor; + }* LayoutData_ptr; + + private: + struct LayoutConfig final + { + Placement::PlacementMethod method; + winsys::Decoration decoration; + bool margin; + bool gap; + bool persistent; + bool single; + bool wraps; + }; + + public: + Layout(LayoutKind); + ~Layout(); + + inline bool + operator==(const Layout& other) const + { + return other.kind == kind; + } + + const LayoutKind kind; + const LayoutConfig config; + + const LayoutData default_data; + Cycle<LayoutData_ptr> data; + + static LayoutConfig kind_to_config(LayoutKind kind); + static LayoutData kind_to_default_data(LayoutKind kind); + + }* Layout_ptr; + +public: + LayoutHandler(); + ~LayoutHandler(); + + void arrange(winsys::Region, placement_vector, client_iter, client_iter) const; + + LayoutKind kind() const; + void set_kind(LayoutKind); + void set_prev_kind(); + + bool layout_is_free() const; + bool layout_has_margin() const; + bool layout_has_gap() const; + bool layout_is_persistent() const; + bool layout_is_single() const; + bool layout_wraps() const; + + std::size_t gap_size() const; + std::size_t main_count() const; + float main_factor() const; + winsys::Extents margin() const; + + void copy_data_from_prev_layout(); + void set_prev_layout_data(); + + void change_gap_size(Util::Change<int>); + void change_main_count(Util::Change<int>); + void change_main_factor(Util::Change<float>); + void change_margin(Util::Change<int>); + void change_margin(winsys::Edge, Util::Change<int>); + void reset_gap_size(); + void reset_margin(); + void reset_layout_data(); + void cycle_layout_data(winsys::Direction); + + void save_layout(std::size_t) const; + void load_layout(std::size_t); + +private: + LayoutKind m_kind; + LayoutKind m_prev_kind; + + std::unordered_map<LayoutKind, Layout_ptr> m_layouts; + + Layout_ptr mp_layout; + Layout_ptr mp_prev_layout; + + void arrange_float(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_blfloat(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_singlefloat(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_blsinglefloat(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_center(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_monocle(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_paper(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_spaper(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_stack(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_sstack(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_bstack(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_sbstack(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_horz(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_shorz(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_vert(winsys::Region, placement_vector, client_iter, client_iter) const; + void arrange_svert(winsys::Region, placement_vector, client_iter, client_iter) const; + +}; + +#endif//__LAYOUT_H_GUARD__ diff --git a/src/core/main.cc b/src/core/main.cc @@ -0,0 +1,13 @@ +#include <iostream> + +#include "../winsys/xdata/xconnection.hh" +#include "model.hh" + +int +main(int, char **) +{ + XConnection conn = {}; + Model model(conn); + model.run(); + return 0; +} diff --git a/src/core/model.cc b/src/core/model.cc @@ -0,0 +1,3819 @@ +#include "../winsys/common.hh" +#include "../winsys/util.hh" +#include "defaults.hh" +#include "model.hh" + +#include <algorithm> +#include <cstdlib> +#include <cstring> +#include <iostream> +#include <set> +#include <sstream> +#include <sys/wait.h> +#include <unistd.h> +#include <vector> + +#ifdef DEBUG +#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG +#endif + +#include "spdlog/spdlog.h" + +using namespace winsys; + +static Model* g_instance; + +Model::Model(Connection& conn) + : m_conn(conn), + m_running(true), + m_partitions({}, true), + m_contexts({}, true), + m_workspaces({}, true), + mp_partition(nullptr), + mp_context(nullptr), + mp_workspace(nullptr), + mp_prev_partition(nullptr), + mp_prev_context(nullptr), + mp_prev_workspace(nullptr), + m_move_buffer(Buffer::BufferKind::Move), + m_resize_buffer(Buffer::BufferKind::Resize), + m_stack({}), + m_order({}), + m_client_map({}), + m_pid_map({}), + m_fullscreen_map({}), + m_sticky_clients({}), + m_unmanaged_windows({}), + mp_focus(nullptr), + mp_jumped_from(nullptr), + m_key_bindings({ +#define CALL(args) [](Model& model) {model.args;} + { { Key::Q, { Main, Ctrl, Shift } }, + CALL(exit()) + }, + + // client state modifiers + { { Key::C, { Main } }, + CALL(kill_focus()) + }, + { { Key::Space, { Main, Shift } }, + CALL(set_floating_focus(Toggle::Reverse)) + }, + { { Key::F, { Main } }, + CALL(set_fullscreen_focus(Toggle::Reverse)) + }, + { { Key::X, { Main } }, + CALL(set_sticky_focus(Toggle::Reverse)) + }, + { { Key::F, { Main, Sec, Ctrl } }, + CALL(set_contained_focus(Toggle::Reverse)) + }, + { { Key::I, { Main, Sec, Ctrl } }, + CALL(set_invincible_focus(Toggle::Reverse)) + }, + { { Key::P, { Main, Sec, Ctrl } }, + CALL(set_producing_focus(Toggle::Reverse)) + }, + { { Key::Y, { Main } }, + CALL(set_iconify_focus(Toggle::Reverse)) + }, + { { Key::U, { Main } }, + CALL(pop_deiconify()) + }, + { { Key::U, { Main, Sec } }, + CALL(deiconify_all()) + }, + + // free client arrangers + { { Key::Space, { Main, Ctrl } }, + CALL(center_focus()) + }, + { { Key::H, { Main, Ctrl } }, + CALL(nudge_focus(Edge::Left, 15)) + }, + { { Key::J, { Main, Ctrl } }, + CALL(nudge_focus(Edge::Bottom, 15)) + }, + { { Key::K, { Main, Ctrl } }, + CALL(nudge_focus(Edge::Top, 15)) + }, + { { Key::L, { Main, Ctrl } }, + CALL(nudge_focus(Edge::Right, 15)) + }, + { { Key::H, { Main, Ctrl, Shift } }, + CALL(stretch_focus(Edge::Left, 15)) + }, + { { Key::J, { Main, Ctrl, Shift } }, + CALL(stretch_focus(Edge::Bottom, 15)) + }, + { { Key::K, { Main, Ctrl, Shift } }, + CALL(stretch_focus(Edge::Top, 15)) + }, + { { Key::L, { Main, Ctrl, Shift } }, + CALL(stretch_focus(Edge::Right, 15)) + }, + { { Key::Y, { Main, Ctrl, Shift } }, + CALL(stretch_focus(Edge::Left, -15)) + }, + { { Key::U, { Main, Ctrl, Shift } }, + CALL(stretch_focus(Edge::Bottom, -15)) + }, + { { Key::I, { Main, Ctrl, Shift } }, + CALL(stretch_focus(Edge::Top, -15)) + }, + { { Key::O, { Main, Ctrl, Shift } }, + CALL(stretch_focus(Edge::Right, -15)) + }, + { { Key::Left, { Main, Ctrl } }, + CALL(snap_focus(Edge::Left)) + }, + { { Key::Down, { Main, Ctrl } }, + CALL(snap_focus(Edge::Bottom)) + }, + { { Key::Up, { Main, Ctrl } }, + CALL(snap_focus(Edge::Top)) + }, + { { Key::Right, { Main, Ctrl } }, + CALL(snap_focus(Edge::Right)) + }, + + // client order modifiers + { { Key::J, { Main } }, + CALL(cycle_focus(Direction::Forward)) + }, + { { Key::K, { Main } }, + CALL(cycle_focus(Direction::Backward)) + }, + { { Key::J, { Main, Shift } }, + CALL(drag_focus(Direction::Forward)) + }, + { { Key::K, { Main, Shift } }, + CALL(drag_focus(Direction::Backward)) + }, + { { Key::SemiColon, { Main, Shift } }, + CALL(rotate_clients(Direction::Forward)) + }, + { { Key::Comma, { Main, Shift } }, + CALL(rotate_clients(Direction::Backward)) + }, + + // client jump criteria + { { Key::B, { Main } }, + CALL(jump_client({ + JumpSelector::SelectionCriterium::ByClassEquals, + "qutebrowser" + })) + }, + { { Key::B, { Main, Shift } }, + CALL(jump_client({ + JumpSelector::SelectionCriterium::ByClassEquals, + "Firefox" + })) + }, + { { Key::B, { Main, Ctrl } }, + CALL(jump_client({ + JumpSelector::SelectionCriterium::ByClassEquals, + "Chromium" + })) + }, + { { Key::Space, { Main, Sec } }, + CALL(jump_client({ + JumpSelector::SelectionCriterium::ByClassEquals, + "Spotify" + })) + }, + { { Key::E, { Main } }, + CALL(jump_client({ + JumpSelector::SelectionCriterium::ByNameContains, + "[vim]" + })) + }, + { { Key::Comma, { Main } }, + CALL(jump_client({ + model.active_workspace(), + Workspace::ClientSelector::SelectionCriterium::AtFirst + })) + }, + { { Key::Period, { Main } }, + CALL(jump_client({ + model.active_workspace(), + Workspace::ClientSelector::SelectionCriterium::AtMain + })) + }, + { { Key::Slash, { Main } }, + CALL(jump_client({ + model.active_workspace(), + Workspace::ClientSelector::SelectionCriterium::AtLast + })) + }, + + // workspace layout modifiers + { { Key::F, { Main, Shift } }, + CALL(set_layout(LayoutHandler::LayoutKind::Float)) + }, + { { Key::L, { Main, Shift } }, + CALL(set_layout(LayoutHandler::LayoutKind::BLFloat)) + }, + { { Key::Z, { Main } }, + CALL(set_layout(LayoutHandler::LayoutKind::SingleFloat)) + }, + { { Key::Z, { Main, Shift } }, + CALL(set_layout(LayoutHandler::LayoutKind::BLSingleFloat)) + }, + { { Key::M, { Main } }, + CALL(set_layout(LayoutHandler::LayoutKind::Monocle)) + }, + { { Key::G, { Main } }, + CALL(set_layout(LayoutHandler::LayoutKind::Center)) + }, + { { Key::T, { Main } }, + CALL(set_layout(LayoutHandler::LayoutKind::Stack)) + }, + { { Key::T, { Main, Shift } }, + CALL(set_layout(LayoutHandler::LayoutKind::SStack)) + }, + { { Key::P, { Main, Ctrl, Shift } }, + CALL(set_layout(LayoutHandler::LayoutKind::Paper)) + }, + { { Key::P, { Main, Sec, Ctrl, Shift } }, + CALL(set_layout(LayoutHandler::LayoutKind::SPaper)) + }, + { { Key::B, { Main, Ctrl, Shift } }, + CALL(set_layout(LayoutHandler::LayoutKind::BStack)) + }, + { { Key::B, { Main, Sec, Ctrl, Shift } }, + CALL(set_layout(LayoutHandler::LayoutKind::SBStack)) + }, + { { Key::Y, { Main, Shift } }, + CALL(set_layout(LayoutHandler::LayoutKind::Horz)) + }, + { { Key::Y, { Main, Ctrl } }, + CALL(set_layout(LayoutHandler::LayoutKind::SHorz)) + }, + { { Key::V, { Main, Shift } }, + CALL(set_layout(LayoutHandler::LayoutKind::Vert)) + }, + { { Key::V, { Main, Ctrl } }, + CALL(set_layout(LayoutHandler::LayoutKind::SVert)) + }, + { { Key::F, { Main, Ctrl, Shift } }, + CALL(set_layout_retain_region(LayoutHandler::LayoutKind::Float)) + }, + { { Key::Space, { Main } }, + CALL(toggle_layout()) + }, + + // workspace layout data modifiers + { { Key::Equal, { Main } }, + CALL(change_gap_size(2)) + }, + { { Key::Minus, { Main } }, + CALL(change_gap_size(-2)) + }, + { { Key::Equal, { Main, Shift } }, + CALL(reset_gap_size()) + }, + { { Key::I, { Main } }, + CALL(change_main_count(1)) + }, + { { Key::D, { Main } }, + CALL(change_main_count(-1)) + }, + { { Key::L, { Main } }, + CALL(change_main_factor(.05f)) + }, + { { Key::H, { Main } }, + CALL(change_main_factor(-.05f)) + }, + { { Key::PageUp, { Main, Shift } }, + CALL(change_margin(5)) + }, + { { Key::PageDown, { Main, Shift } }, + CALL(change_margin(-5)) + }, + { { Key::Left, { Main, Shift } }, + CALL(change_margin(Edge::Left, 5)) + }, + { { Key::Left, { Main, Ctrl, Shift } }, + CALL(change_margin(Edge::Left, -5)) + }, + { { Key::Up, { Main, Shift } }, + CALL(change_margin(Edge::Top, 5)) + }, + { { Key::Up, { Main, Ctrl, Shift } }, + CALL(change_margin(Edge::Top, -5)) + }, + { { Key::Right, { Main, Shift } }, + CALL(change_margin(Edge::Right, 5)) + }, + { { Key::Right, { Main, Ctrl, Shift } }, + CALL(change_margin(Edge::Right, -5)) + }, + { { Key::Down, { Main, Shift } }, + CALL(change_margin(Edge::Bottom, 5)) + }, + { { Key::Down, { Main, Ctrl, Shift } }, + CALL(change_margin(Edge::Bottom, -5)) + }, + { { Key::Comma, { Main, Ctrl, Shift } }, + CALL(cycle_layout_data(Direction::Backward)) + }, + { { Key::Period, { Main, Ctrl, Shift } }, + CALL(cycle_layout_data(Direction::Forward)) + }, + { { Key::Slash, { Main, Ctrl, Shift } }, + CALL(toggle_layout_data()) + }, + { { Key::Delete, { Main, Ctrl, Shift } }, + CALL(copy_data_from_prev_layout()) + }, + { { Key::Equal, { Main, Ctrl, Shift } }, + CALL(reset_margin()) + }, + { { Key::Equal, { Main, Sec, Ctrl, Shift } }, + CALL(reset_layout_data()) + }, + + // workspace layout storage and retrieval + { { Key::F1, { Main, Shift } }, + CALL(save_layout(0)) + }, + { { Key::F2, { Main, Shift } }, + CALL(save_layout(1)) + }, + { { Key::F3, { Main, Shift } }, + CALL(save_layout(2)) + }, + { { Key::F4, { Main, Shift } }, + CALL(save_layout(3)) + }, + { { Key::F5, { Main, Shift } }, + CALL(save_layout(4)) + }, + { { Key::F6, { Main, Shift } }, + CALL(save_layout(5)) + }, + { { Key::F7, { Main, Shift } }, + CALL(save_layout(6)) + }, + { { Key::F8, { Main, Shift } }, + CALL(save_layout(7)) + }, + { { Key::F9, { Main, Shift } }, + CALL(save_layout(8)) + }, + { { Key::F10, { Main, Shift } }, + CALL(save_layout(9)) + }, + { { Key::F11, { Main, Shift } }, + CALL(save_layout(10)) + }, + { { Key::F12, { Main, Shift } }, + CALL(save_layout(11)) + }, + { { Key::F1, { Main } }, + CALL(load_layout(0)) + }, + { { Key::F2, { Main } }, + CALL(load_layout(1)) + }, + { { Key::F3, { Main } }, + CALL(load_layout(2)) + }, + { { Key::F4, { Main } }, + CALL(load_layout(3)) + }, + { { Key::F5, { Main } }, + CALL(load_layout(4)) + }, + { { Key::F6, { Main } }, + CALL(load_layout(5)) + }, + { { Key::F7, { Main } }, + CALL(load_layout(6)) + }, + { { Key::F8, { Main } }, + CALL(load_layout(7)) + }, + { { Key::F9, { Main } }, + CALL(load_layout(8)) + }, + { { Key::F10, { Main } }, + CALL(load_layout(9)) + }, + { { Key::F11, { Main } }, + CALL(load_layout(10)) + }, + { { Key::F12, { Main } }, + CALL(load_layout(11)) + }, + + // workspace activators + { { Key::Escape, { Main } }, + CALL(toggle_workspace()) + }, + { { Key::RightBracket, { Main } }, + CALL(activate_next_workspace(Direction::Forward)) + }, + { { Key::LeftBracket, { Main } }, + CALL(activate_next_workspace(Direction::Backward)) + }, + { { Key::One, { Main } }, + CALL(activate_workspace(Util::Change<Index> { 0 })) + }, + { { Key::Two, { Main } }, + CALL(activate_workspace(1)) + }, + { { Key::Three, { Main } }, + CALL(activate_workspace(2)) + }, + { { Key::Four, { Main } }, + CALL(activate_workspace(3)) + }, + { { Key::Five, { Main } }, + CALL(activate_workspace(4)) + }, + { { Key::Six, { Main } }, + CALL(activate_workspace(5)) + }, + { { Key::Seven, { Main } }, + CALL(activate_workspace(6)) + }, + { { Key::Eight, { Main } }, + CALL(activate_workspace(7)) + }, + { { Key::Nine, { Main } }, + CALL(activate_workspace(8)) + }, + { { Key::Zero, { Main } }, + CALL(activate_workspace(9)) + }, + + // workspace client movers + { { Key::RightBracket, { Main, Shift } }, + CALL(move_focus_to_next_workspace(Direction::Forward)) + }, + { { Key::LeftBracket, { Main, Shift } }, + CALL(move_focus_to_next_workspace(Direction::Backward)) + }, + { { Key::One, { Main, Shift } }, + CALL(move_focus_to_workspace(0)) + }, + { { Key::Two, { Main, Shift } }, + CALL(move_focus_to_workspace(1)) + }, + { { Key::Three, { Main, Shift } }, + CALL(move_focus_to_workspace(2)) + }, + { { Key::Four, { Main, Shift } }, + CALL(move_focus_to_workspace(3)) + }, + { { Key::Five, { Main, Shift } }, + CALL(move_focus_to_workspace(4)) + }, + { { Key::Six, { Main, Shift } }, + CALL(move_focus_to_workspace(5)) + }, + { { Key::Seven, { Main, Shift } }, + CALL(move_focus_to_workspace(6)) + }, + { { Key::Eight, { Main, Shift } }, + CALL(move_focus_to_workspace(7)) + }, + { { Key::Nine, { Main, Shift } }, + CALL(move_focus_to_workspace(8)) + }, + { { Key::Zero, { Main, Shift } }, + CALL(move_focus_to_workspace(9)) + }, + + // screen region modifiers + { { Key::V, { Main } }, + CALL(activate_screen_struts(Toggle::Reverse)) + }, + + // external commands +#define CALL_EXTERNAL(command) CALL(spawn_external(#command)) + { { Key::PlayPause, {} }, + CALL_EXTERNAL(playerctl play-pause) + }, + { { Key::P, { Main, Sec } }, + CALL_EXTERNAL(playerctl play-pause) + }, + { { Key::PreviousTrack, {} }, + CALL_EXTERNAL(playerctl previous) + }, + { { Key::K, { Main, Sec } }, + CALL_EXTERNAL(playerctl previous) + }, + { { Key::NextTrack, {} }, + CALL_EXTERNAL(playerctl next) + }, + { { Key::J, { Main, Sec } }, + CALL_EXTERNAL(playerctl next) + }, + { { Key::StopMedia, {} }, + CALL_EXTERNAL(playerctl stop) + }, + { { Key::BackSpace, { Main, Sec } }, + CALL_EXTERNAL(playerctl stop) + }, + { { Key::VolumeMute, {} }, + CALL_EXTERNAL(amixer -D pulse sset Master toggle) + }, + { { Key::VolumeDown, {} }, + CALL_EXTERNAL(amixer -D pulse sset Master 5%-) + }, + { { Key::VolumeUp, {} }, + CALL_EXTERNAL(amixer -D pulse sset Master 5%+) + }, + { { Key::VolumeMute, { Shift } }, + CALL_EXTERNAL(amixer -D pulse sset Capture toggle) + }, + { { Key::MicMute, {} }, + CALL_EXTERNAL(amixer -D pulse sset Capture toggle) + }, + { { Key::Return, { Main } }, + CALL_EXTERNAL(st) + }, + { { Key::Return, { Main, Shift } }, + CALL(spawn_external("st -n " + WM_NAME + ":cf")) + }, + { { Key::P, { Main } }, + CALL_EXTERNAL(dmenu_run) + }, + { { Key::Q, { Main } }, + CALL_EXTERNAL(qutebrowser) + }, + { { Key::Q, { Main, Shift } }, + CALL_EXTERNAL(firefox) + }, + { { Key::Q, { Main, Ctrl } }, + CALL_EXTERNAL(chromium) + }, + { { Key::P, { Main, Shift } }, + CALL_EXTERNAL($HOME/bin/dmenupass) + }, + { { Key::P, { Main, Ctrl } }, + CALL_EXTERNAL($HOME/bin/dmenupass --copy) + }, + { { Key::O, { Main, Shift } }, + CALL_EXTERNAL($HOME/bin/dmenunotify) + }, + { { Key::PrintScreen, { Main } }, + CALL_EXTERNAL($HOME/bin/screenshot -s) + }, + { { Key::PrintScreen, { Main, Shift } }, + CALL_EXTERNAL($HOME/bin/screenshot) + }, +#undef CALL_EXTERNAL +#undef CALL + }), + m_mouse_bindings({ + { { MouseInput::MouseInputTarget::Client, Button::Right, { Main, Ctrl } }, + [](Model& model, Client_ptr client) { + if (client) + model.set_floating_client(Toggle::Reverse, client); + + return true; + } + }, + { { MouseInput::MouseInputTarget::Client, Button::Middle, { Main, Ctrl, Shift } }, + [](Model& model, Client_ptr client) { + if (client) + model.set_fullscreen_client(Toggle::Reverse, client); + + return true; + } + }, + { { MouseInput::MouseInputTarget::Client, Button::Middle, { Main } }, + [](Model& model, Client_ptr client) { + if (client) + model.center_client(client); + + return true; + } + }, + { { MouseInput::MouseInputTarget::Client, Button::ScrollDown, { Main, Ctrl, Shift } }, + [](Model& model, Client_ptr client) { + if (client) + model.inflate_client(-16, client); + + return true; + } + }, + { { MouseInput::MouseInputTarget::Client, Button::ScrollUp, { Main, Ctrl, Shift } }, + [](Model& model, Client_ptr client) { + if (client) + model.inflate_client(16, client); + + return true; + } + }, + { { MouseInput::MouseInputTarget::Client, Button::Left, { Main } }, + [](Model& model, Client_ptr client) { + if (client) + model.start_moving(client); + + return true; + } + }, + { { MouseInput::MouseInputTarget::Client, Button::Right, { Main } }, + [](Model& model, Client_ptr client) { + if (client) + model.start_resizing(client); + + return true; + } + }, + { { MouseInput::MouseInputTarget::Global, Button::ScrollDown, { Main } }, + [](Model& model, Client_ptr) { + model.cycle_focus(Direction::Forward); + return false; + } + }, + { { MouseInput::MouseInputTarget::Global, Button::ScrollUp, { Main } }, + [](Model& model, Client_ptr) { + model.cycle_focus(Direction::Backward); + return false; + } + }, + { { MouseInput::MouseInputTarget::Global, Button::ScrollDown, { Main, Shift } }, + [](Model& model, Client_ptr) { + model.activate_next_workspace(Direction::Forward); + return false; + } + }, + { { MouseInput::MouseInputTarget::Global, Button::ScrollUp, { Main, Shift } }, + [](Model& model, Client_ptr) { + model.activate_next_workspace(Direction::Backward); + return false; + } + }, + { { MouseInput::MouseInputTarget::Client, Button::Forward, { Main } }, + [](Model& model, Client_ptr client) { + if (client) + model.move_client_to_next_workspace(Direction::Forward, client); + + return false; + } + }, + { { MouseInput::MouseInputTarget::Client, Button::Backward, { Main } }, + [](Model& model, Client_ptr client) { + if (client) + model.move_client_to_next_workspace(Direction::Backward, client); + + return false; + } + }, + { { MouseInput::MouseInputTarget::Client, Button::Right, { Main, Ctrl, Shift } }, + [](Model& model, Client_ptr client) { + if (client) + model.kill_client(client); + + return false; + } + }, + { { MouseInput::MouseInputTarget::Global, Button::Left, { Main, Sec, Ctrl } }, + [](Model& model, Client_ptr) { + model.spawn_external("st -n kranewm:cf"); + return false; + } + }, + }) +{ +#ifdef DEBUG + spdlog::set_level(spdlog::level::debug); +#endif + + g_instance = this; + + static const std::vector<std::string> context_names = { + "a", "b", "c", "d", "e", "f", "g", "h", "i", "j" + }; + + static const std::vector<std::string> workspace_names = { + "1:main", "2:web", "3:term", "4", "5", "6", "7", "8", "9", "10" + }; + + for (std::size_t i = 0; i < context_names.size(); ++i) + m_contexts.insert_at_back(new Context(i, context_names[i])); + + for (std::size_t i = 0; i < workspace_names.size(); ++i) + m_workspaces.insert_at_back(new Workspace(i, workspace_names[i])); + + acquire_partitions(); + + m_conn.init_for_wm(WM_NAME, workspace_names); + + m_contexts.activate_at_index(0); + m_workspaces.activate_at_index(0); + + mp_context = *m_contexts.active_element(); + mp_workspace = *m_workspaces.active_element(); + + m_conn.set_current_desktop(0); + + std::vector<KeyInput> key_inputs(m_key_bindings.size()); + std::vector<MouseInput> mouse_inputs(m_mouse_bindings.size()); + + std::transform( + m_key_bindings.begin(), + m_key_bindings.end(), + key_inputs.begin(), + [](auto kv) -> KeyInput { return kv.first; } + ); + + std::transform( + m_mouse_bindings.begin(), + m_mouse_bindings.end(), + mouse_inputs.begin(), + [](auto kv) -> MouseInput { return kv.first; } + ); + + m_conn.grab_bindings(key_inputs, mouse_inputs); + + for (auto& window : m_conn.top_level_windows()) + manage(window, !m_conn.must_manage_window(window)); + +#ifndef DEBUG + { // run user-configured autostart programs + std::stringstream configdir_ss; + + if (const char* env_xdgconf = std::getenv("XDG_CONFIG_HOME")) + configdir_ss << env_xdgconf << "/" << WM_NAME << "/"; + else + configdir_ss << "$HOME/.config/" << WM_NAME << "/"; + + spawn_external(configdir_ss.str() + std::string("blocking_autostart")); + spawn_external(configdir_ss.str() + std::string("nonblocking_autostart &")); + spdlog::info("ran autostart scripts"); + } +#endif + + spdlog::info("initialized " + WM_NAME); +} + +Model::~Model() +{ + for (std::size_t i = 0; i < m_partitions.size(); ++i) + delete m_partitions[i]; + + for (std::size_t i = 0; i < m_contexts.size(); ++i) + delete m_contexts[i]; + + for (std::size_t i = 0; i < m_workspaces.size(); ++i) + delete m_workspaces[i]; + + std::unordered_set<Client_ptr> clients = {}; + + for (auto [_,client] : m_client_map) + clients.insert(client); + + for (Client_ptr client : clients) + delete client; + + m_partitions.clear(); + m_contexts.clear(); + m_workspaces.clear(); + m_client_map.clear(); +} + +void +Model::run() +{ + while (m_running) + std::visit(event_visitor, m_conn.step()); +} + +void +Model::init_signals() const +{ + struct sigaction child_sa, exit_sa, ignore_sa; + + std::memset(&child_sa, 0, sizeof(child_sa)); + child_sa.sa_handler = &Model::wait_children; + + std::memset(&exit_sa, 0, sizeof(exit_sa)); + exit_sa.sa_handler = &Model::handle_signal; + + std::memset(&ignore_sa, 0, sizeof(ignore_sa)); + ignore_sa.sa_handler = SIG_IGN; + + sigaction(SIGCHLD, &child_sa, NULL); + sigaction(SIGINT, &exit_sa, NULL); + sigaction(SIGHUP, &exit_sa, NULL); + sigaction(SIGINT, &exit_sa, NULL); + sigaction(SIGTERM, &exit_sa, NULL); + sigaction(SIGPIPE, &ignore_sa, NULL); +} + +// window management actions + +void +Model::acquire_partitions() +{ + std::size_t index = m_partitions.active_index(); + + for (std::size_t i = 0; i < m_partitions.size(); ++i) + delete m_partitions[i]; + + m_partitions.clear(); + + std::vector<Screen> connected_outputs = m_conn.connected_outputs(); + + for (std::size_t i = 0; i < connected_outputs.size(); ++i) + m_partitions.insert_at_back(new Partition( + connected_outputs[i], + i + )); + + if (m_partitions.empty()) { + spdlog::warn("could not acquire any partitions"); + return; + } + + if (index < m_partitions.size()) + m_partitions.activate_at_index(index); + else + m_partitions.activate_at_index(0); + + mp_partition = *m_partitions.active_element(); + Screen& screen = mp_partition->screen(); + + screen.compute_placeable_region(); + + std::vector<Region> workspace_regions + = { m_workspaces.size(), screen.full_region() }; + + m_conn.set_desktop_geometry(workspace_regions); + m_conn.set_desktop_viewport(workspace_regions); + m_conn.set_workarea(workspace_regions); + + for (auto& [_,client] : m_client_map) { + Region screen_region = screen.full_region(); + Region client_region = client->free_region; + + if (client_region.pos.x >= screen_region.dim.w) + client_region.pos.x = screen_region.dim.w - client_region.dim.w; + + if (client_region.pos.y >= screen_region.dim.h) + client_region.pos.y = screen_region.dim.h - client_region.dim.h; + + client->set_free_region(client_region); + } + + spdlog::debug("acquired {} partitions", m_partitions.size()); +} + +const Screen& +Model::active_screen() const +{ + return mp_partition->const_screen(); +} + +Client_ptr +Model::get_client(Window window) +{ + return Util::retrieve(m_client_map, window).value_or(nullptr); +} + +Client_ptr +Model::get_const_client(Window window) const +{ + return Util::const_retrieve(m_client_map, window).value_or(nullptr); +} + + +Index +Model::active_partition() const +{ + return mp_partition->index(); +} + +Partition_ptr +Model::get_partition(Index index) const +{ + if (index < m_partitions.size()) + return m_partitions[index]; + + return nullptr; +} + + +Index +Model::active_context() const +{ + return mp_context->index(); +} + +Context_ptr +Model::get_context(Index index) const +{ + if (index < m_contexts.size()) + return m_contexts[index]; + + return nullptr; +} + + +Index +Model::active_workspace() const +{ + return mp_workspace->index(); +} + +Workspace_ptr +Model::get_workspace(Index index) const +{ + if (index < m_workspaces.size()) + return m_workspaces[index]; + + return nullptr; +} + + +bool +Model::is_free(Client_ptr client) const +{ + return Client::is_free(client) + || ((!client->fullscreen || client->contained) + && (client->sticky + ? mp_workspace + : get_workspace(client->workspace) + )->layout_is_free()); +} + +void +Model::place_client(Placement& placement) +{ + Client_ptr client = placement.client; + + if (!placement.region) { + switch (placement.method) { + case Placement::PlacementMethod::Free: + { + client->set_free_decoration(placement.decoration); + break; + } + case Placement::PlacementMethod::Tile: + { + client->free_decoration = Decoration::FREE_DECORATION; + client->set_tile_decoration(placement.decoration); + break; + } + } + + unmap_client(client); + return; + } + + switch (placement.method) { + case Placement::PlacementMethod::Free: + { + client->set_free_decoration(placement.decoration); + client->set_free_region(*placement.region); + break; + } + case Placement::PlacementMethod::Tile: + { + client->free_decoration = Decoration::FREE_DECORATION; + client->set_tile_decoration(placement.decoration); + client->set_tile_region(*placement.region); + break; + } + } + + map_client(client); + m_conn.place_window(client->window, client->inner_region); + m_conn.place_window(client->frame, client->active_region); + + render_decoration(client); + m_conn.update_window_offset(client->window, client->frame); +} + +void +Model::map_client(Client_ptr client) +{ + if (!client->mapped) { + m_conn.map_window(client->window); + m_conn.map_window(client->frame); + render_decoration(client); + client->mapped = true; + } +} + +void +Model::unmap_client(Client_ptr client) +{ + if (client->mapped) { + m_conn.unmap_window(client->frame); + client->expect_unmap(); + client->mapped = false; + } +} + +void +Model::focus_client(Client_ptr client) +{ + if (!client->sticky) { + activate_workspace(client->workspace); + mp_workspace->activate_client(client); + } + + unfocus_client(mp_focus); + m_conn.ungrab_buttons(client->frame); + + client->focus(); + client->urgent = false; + + mp_focus = client; + + if (mp_workspace->layout_is_persistent() || mp_workspace->layout_is_single()) + apply_layout(mp_workspace); + + if (m_conn.get_focused_window() != client->window) + m_conn.focus_window(client->window); + + render_decoration(client); + apply_stack(mp_workspace); +} + +void +Model::unfocus_client(Client_ptr client) +{ + if (!client) + return; + + client->unfocus(); + m_conn.regrab_buttons(client->frame); + render_decoration(client); +} + +void +Model::sync_focus() +{ + Client_ptr active = mp_workspace->active(); + + if (active && active != mp_focus) + focus_client(active); + else if (mp_workspace->empty()) { + m_conn.unfocus(); + mp_focus = nullptr; + } +} + + +void +Model::toggle_partition() +{ + if (mp_prev_partition) + activate_partition(mp_prev_partition); +} + +void +Model::activate_next_partition(winsys::Direction direction) +{ + activate_partition(m_partitions.next_index(direction));; +} + +void +Model::activate_partition(Util::Change<Index> index) +{ + if (index >= m_partitions.size()) + return; + + activate_partition(get_partition(index)); +} + + +void +Model::activate_partition(Partition_ptr next_partition) +{ + if (next_partition == mp_partition) + return; + + stop_moving(); + stop_resizing(); + + // TODO +} + + +void +Model::toggle_context() +{ + if (mp_prev_context) + activate_context(mp_prev_context); +} + +void +Model::activate_next_context(winsys::Direction direction) +{ + activate_context(m_contexts.next_index(direction));; +} + +void +Model::activate_context(Util::Change<Index> index) +{ + if (index >= m_contexts.size()) + return; + + activate_context(get_context(index)); +} + +void +Model::activate_context(Context_ptr next_context) +{ + if (next_context == mp_context) + return; + + stop_moving(); + stop_resizing(); + + // TODO +} + + +void +Model::toggle_workspace() +{ + if (mp_prev_workspace) + activate_workspace(mp_prev_workspace); +} + +void +Model::activate_next_workspace(Direction direction) +{ + activate_workspace(m_workspaces.next_index(direction)); +} + +void +Model::activate_workspace(Util::Change<Index> index) +{ + if (index >= m_workspaces.size()) + return; + + activate_workspace(get_workspace(index)); +} + +void +Model::activate_workspace(Workspace_ptr next_workspace) +{ + if (next_workspace == mp_workspace) + return; + + stop_moving(); + stop_resizing(); + + m_conn.set_current_desktop(next_workspace->index()); + + Workspace_ptr prev_workspace = mp_workspace; + mp_prev_workspace = prev_workspace; + + for (auto& client : next_workspace->clients()) + if (!client->mapped) + map_client(client); + + for (auto& client : mp_workspace->clients()) + if (client->mapped && !client->sticky) + unmap_client(client); + + for (auto& client : m_sticky_clients) + client->workspace = next_workspace->index(); + + m_workspaces.activate_element(next_workspace); + mp_workspace = next_workspace; + + apply_layout(next_workspace); + apply_stack(next_workspace); + + sync_focus(); +} + +void +Model::render_decoration(Client_ptr client) +{ + Decoration& decoration = client->active_decoration; + + std::optional<std::size_t> border_width = std::nullopt; + std::optional<Color> border_color = std::nullopt; + std::optional<Color> frame_color = std::nullopt; + + switch (client->get_outside_state()) { + case Client::OutsideState::Focused: + { + if (decoration.border) { + border_width = decoration.border->width; + border_color = decoration.border->colors.focused; + } + + if (decoration.frame) + frame_color = decoration.frame->colors.focused; + + break; + } + case Client::OutsideState::FocusedDisowned: + { + if (decoration.border) { + border_width = decoration.border->width; + border_color = decoration.border->colors.fdisowned; + } + + if (decoration.frame) + frame_color = decoration.frame->colors.fdisowned; + + break; + } + case Client::OutsideState::FocusedSticky: + { + if (decoration.border) { + border_width = decoration.border->width; + border_color = decoration.border->colors.fsticky; + } + + if (decoration.frame) + frame_color = decoration.frame->colors.fsticky; + + break; + } + case Client::OutsideState::Unfocused: + { + if (decoration.border) { + border_width = decoration.border->width; + border_color = decoration.border->colors.unfocused; + } + + if (decoration.frame) + frame_color = decoration.frame->colors.unfocused; + + break; + } + case Client::OutsideState::UnfocusedDisowned: + { + if (decoration.border) { + border_width = decoration.border->width; + border_color = decoration.border->colors.udisowned; + } + + if (decoration.frame) + frame_color = decoration.frame->colors.udisowned; + + break; + } + case Client::OutsideState::UnfocusedSticky: + { + if (decoration.border) { + border_width = decoration.border->width; + border_color = decoration.border->colors.usticky; + } + + if (decoration.frame) + frame_color = decoration.frame->colors.usticky; + + break; + } + case Client::OutsideState::Urgent: + { + if (decoration.border) { + border_width = decoration.border->width; + border_color = decoration.border->colors.urgent; + } + + if (decoration.frame) + frame_color = decoration.frame->colors.urgent; + + break; + } + } + + if (border_width) { + m_conn.set_window_border_width(client->frame, *border_width); + m_conn.set_window_border_color(client->frame, *border_color); + } + + if (frame_color) + m_conn.set_window_background_color(client->frame, *frame_color); +} + + +Rules +Model::retrieve_rules(Client_ptr client) const +{ + static std::string prefix = WM_NAME + ":"; + Rules rules = {}; + + if (client->instance.size() <= prefix.size()) + return rules; + + auto res = std::mismatch( + prefix.begin(), + prefix.end(), + client->instance.begin() + ); + + if (res.first == prefix.end()) { + bool invert = false; + bool next_partition = false; + bool next_context = false; + bool next_workspace = false; + + for (auto iter = res.second; iter != client->instance.end(); ++iter) { + if (*iter == '!') { + invert = true; + continue; + } + + if (*iter == 'P') { + next_partition = true; + continue; + } + + if (*iter == 'C') { + next_context = true; + continue; + } + + if (*iter == 'W') { + next_workspace = true; + continue; + } + + if (*iter == 'f') + rules.do_float = !invert; + + if (*iter == 'F') + rules.do_fullscreen = !invert; + + if (*iter == 'c') + rules.do_center = !invert; + + if (*iter >= '0' && *iter <= '9') { + if (next_partition) + rules.to_partition = *iter - '0'; + + if (next_context) + rules.to_context = *iter - '0'; + + if (next_workspace) + rules.to_workspace = *iter - '0'; + } + + invert = false; + next_partition = false; + next_context = false; + next_workspace = false; + } + } + + return rules; +} + + +void +Model::manage(const Window window, const bool ignore) +{ + std::optional<Region> window_geometry = m_conn.get_window_geometry(window); + + if (ignore || !window_geometry) { + if (m_conn.window_is_mappable(window)) + m_conn.map_window(window); + + m_conn.init_unmanaged(window); + m_unmanaged_windows.push_back(window); + + return; + } + + std::optional<Pid> pid = m_conn.get_window_pid(window); + std::optional<Pid> ppid = m_conn.get_ppid(pid); + + while (ppid && m_pid_map.count(*ppid) == 0) + ppid = m_conn.get_ppid(ppid); + + Client_ptr producer = nullptr; + + if (ppid) { + std::optional<Client_ptr> ppid_client = Util::retrieve(m_pid_map, *ppid); + + if (ppid_client) + producer = *ppid_client; + } + + std::string name = m_conn.get_icccm_window_name(window); + std::string class_ = m_conn.get_icccm_window_class(window); + std::string instance = m_conn.get_icccm_window_instance(window); + + WindowType preferred_type = m_conn.get_window_preferred_type(window); + std::optional<WindowState> preferred_state = m_conn.get_window_preferred_state(window); + + Region geometry = *window_geometry; + + Window frame = m_conn.create_frame(geometry); + + bool center = false; + bool floating = m_conn.must_free_window(window); + bool fullscreen = m_conn.window_is_fullscreen(window); + bool sticky = m_conn.window_is_sticky(window); + + Index workspace = m_conn.get_window_desktop(window).value_or(mp_workspace->index()); + Index context = workspace / m_workspaces.size(); + workspace %= m_workspaces.size(); + + std::optional<Hints> hints = m_conn.get_icccm_window_hints(window); + std::optional<SizeHints> size_hints + = m_conn.get_icccm_window_size_hints(window, std::nullopt); + + if (size_hints) { + size_hints->apply(geometry.dim); + center = !size_hints->by_user; + } else { + geometry.apply_minimum_dim(Client::MIN_CLIENT_DIM); + center = true; + } + + center &= Pos::is_at_origin(geometry.pos); + + Extents extents = Decoration::FREE_DECORATION.extents(); + geometry.apply_extents(extents); + + std::optional<Window> parent = m_conn.get_icccm_window_transient_for(window); + std::optional<Window> leader = m_conn.get_icccm_window_client_leader(window); + + Client_ptr client = new Client( + window, + frame, + name, + class_, + instance, + mp_partition->index(), + context, + workspace, + preferred_type, + pid, + ppid + ); + + Rules rules = retrieve_rules(client); + + if (rules.to_context && *rules.to_context <= m_contexts.size()) + client->context = *rules.to_context; + + if (rules.to_workspace && *rules.to_workspace <= m_workspaces.size()) + client->workspace = *rules.to_workspace; + + if (parent) { + Client_ptr parent_client = get_client(*parent); + + if (parent_client) { + client->parent = parent_client; + parent_client->children.push_back(client); + floating = true; + } + + m_stack.add_above_other(frame, + parent_client ? parent_client->frame : *parent); + } + + if (leader) { + Client_ptr leader_client = get_client(*leader); + + if (leader_client) { + client->leader = get_client(*leader); + floating = true; + } + } + + if (center || (rules.do_center && *rules.do_center)) { + const Region screen_region = active_screen().placeable_region(); + + geometry.pos.x = screen_region.pos.x + + (screen_region.dim.w - geometry.dim.w) / 2; + + geometry.pos.y = screen_region.pos.y + + (screen_region.dim.h - geometry.dim.h) / 2; + } + + client->set_free_region(geometry); + client->floating = floating; + client->fullscreen = fullscreen; + client->urgent = hints ? hints->urgent : false; + client->partition = m_partitions.active_index(); + client->context = m_contexts.active_index(); + client->workspace = m_workspaces.active_index(); + client->size_hints = size_hints; + + if (rules.do_float) + client->floating = *rules.do_float; + + if (rules.do_fullscreen) + client->fullscreen = *rules.do_fullscreen; + + if (rules.to_partition) + client->partition = *rules.to_partition; + + if (rules.to_context) + client->context = *rules.to_context; + + if (rules.to_workspace) + client->workspace = *rules.to_workspace; + + if (pid) + m_pid_map[*pid] = client; + + m_conn.place_window(client->frame, client->free_region); + m_conn.unmap_window(window); + m_conn.unmap_window(frame); + m_conn.reparent_window(window, frame, Pos { extents.left, extents.top }); + + m_client_map[window] = client; + m_client_map[frame] = client; + + m_conn.insert_window_in_save_set(window); + m_conn.init_window(window, false); + m_conn.init_frame(frame, false); + m_conn.set_window_border_width(window, 0); + m_conn.set_window_desktop(window, workspace); + m_conn.set_icccm_window_state(window, IcccmWindowState::Normal); + + mp_workspace->add_client(client); + + if (client->size_hints) + client->size_hints->apply(client->free_region.dim); + + client->free_region.apply_minimum_dim(Client::MIN_CLIENT_DIM); + + apply_layout(client->workspace); + + if (!m_move_buffer.is_occupied() + && !m_resize_buffer.is_occupied() + && !producer + ) { + focus_client(client); + } + + if (preferred_state && *preferred_state == WindowState::DemandsAttention) + handle_state_request({ + window, + *preferred_state, + Toggle::On, + false + }); + + if (sticky) + set_sticky_client(Toggle::On, client); + + if (fullscreen) + set_fullscreen_client(Toggle::On, client); + + if (producer && producer->producing) + consume_client(producer, client); +} + +void +Model::unmanage(Client_ptr client) +{ + for (auto& consumer : client->consumers) + check_unconsume_client(consumer); + + check_unconsume_client(client); + + set_sticky_client(winsys::Toggle::Off, client); + + Workspace_ptr workspace = get_workspace(client->workspace); + + m_conn.unparent_window(client->window, client->active_region.pos); + + m_conn.cleanup_window(client->window); + m_conn.destroy_window(client->frame); + m_conn.destroy_window(client->window); + + workspace->remove_client(client); + workspace->remove_icon(client); + workspace->remove_disowned(client); + + if (client->pid) + m_pid_map.erase(*client->pid); + + if (client->producer) + Util::erase_remove(client->producer->consumers, client); + + if (client->parent) + Util::erase_remove(client->parent->children, client); + + for (auto child : client->children) { + unmap_client(child); + unmanage(child); + } + + m_client_map.erase(client->window); + m_client_map.erase(client->frame); + + m_fullscreen_map.erase(client); + + Util::erase_remove(m_sticky_clients, client); + Util::erase_remove(m_order, client->frame); + + m_stack.remove_window(client->frame); + + if (client == mp_jumped_from) + mp_jumped_from = nullptr; + + sync_focus(); + apply_layout(workspace); +} + +void +Model::start_moving(Client_ptr client) +{ + if (m_move_buffer.is_occupied() || m_resize_buffer.is_occupied()) + return; + + m_move_buffer.set( + client, + Grip::Top | Grip::Left, + m_conn.get_pointer_position(), + client->free_region + ); + + m_conn.init_move(client->frame); +} + +void +Model::start_resizing(Client_ptr client) +{ + if (m_move_buffer.is_occupied() || m_resize_buffer.is_occupied()) + return; + + Region region = client->free_region; + Pos pos = m_conn.get_pointer_position(); + Pos center = Pos { + region.pos.x + static_cast<int>(static_cast<float>(region.dim.w) / 2.f), + region.pos.y + static_cast<int>(static_cast<double>(region.dim.h) / 2.f) + }; + + Grip grip = static_cast<Grip>(0); + + if (pos.x >= center.x) + grip |= Grip::Right; + else + grip |= Grip::Left; + + if (pos.y >= center.y) + grip |= Grip::Bottom; + else + grip |= Grip::Top; + + m_resize_buffer.set( + client, + grip, + pos, + region + ); + + m_conn.init_resize(client->frame); +} + +void +Model::stop_moving() +{ + if (m_move_buffer.is_occupied()) { + m_conn.release_pointer(); + m_move_buffer.unset(); + } +} + +void +Model::stop_resizing() +{ + if (m_resize_buffer.is_occupied()) { + m_conn.release_pointer(); + m_resize_buffer.unset(); + } +} + + +void +Model::perform_move(Pos& pos) +{ + if (!m_move_buffer.is_occupied()) + return; + + Client_ptr client = m_move_buffer.client(); + + if (!client || !is_free(client)) { + stop_moving(); + return; + } + + Region client_region = *m_move_buffer.client_region(); + Pos grip_pos = *m_move_buffer.grip_pos(); + + Region region = Region { + Pos { + client_region.pos.x + pos.x - grip_pos.x, + client_region.pos.y + pos.y - grip_pos.y, + }, + client->free_region.dim, + }; + + client->set_free_region(region); + + Placement placement = Placement { + Placement::PlacementMethod::Free, + client, + client->free_decoration, + region + }; + + place_client(placement); +} + +void +Model::perform_resize(Pos& pos) +{ + if (!m_resize_buffer.is_occupied()) + return; + + Client_ptr client = m_resize_buffer.client(); + + if (!client || !is_free(client)) { + stop_resizing(); + return; + } + + Region client_region = *m_resize_buffer.client_region(); + Pos grip_pos = *m_resize_buffer.grip_pos(); + Grip grip = *m_resize_buffer.grip(); + + Region region = client->free_region; + + Decoration decoration = client->free_decoration; + Extents extents = Extents { 0, 0, 0, 0 }; + + if (decoration.border) { + extents.left += decoration.border->width; + extents.top += decoration.border->width; + extents.right += decoration.border->width; + extents.bottom += decoration.border->width; + } + + if (decoration.frame) { + extents.left += decoration.frame->extents.left; + extents.top += decoration.frame->extents.top; + extents.right += decoration.frame->extents.right; + extents.bottom += decoration.frame->extents.bottom; + } + + region.remove_extents(extents); + + int dx = pos.x - grip_pos.x; + int dy = pos.y - grip_pos.y; + + int dest_w; + int dest_h; + + if ((grip & Grip::Left) != 0) + dest_w = client_region.dim.w - dx; + else + dest_w = client_region.dim.w + dx; + + if ((grip & Grip::Top) != 0) + dest_h = client_region.dim.h - dy; + else + dest_h = client_region.dim.h + dy; + + region.dim.w = std::max(0, dest_w); + region.dim.h = std::max(0, dest_h); + + if (client->size_hints) + client->size_hints->apply(region.dim); + + region.apply_extents(extents); + + if ((grip & Grip::Top) != 0) + region.pos.y + = client_region.pos.y + (client_region.dim.h - region.dim.h); + + if ((grip & Grip::Left) != 0) + region.pos.x + = client_region.pos.x + (client_region.dim.w - region.dim.w); + + if (region == client->previous_region) + return; + + Placement placement = Placement { + Placement::PlacementMethod::Free, + client, + client->free_decoration, + region + }; + + place_client(placement); +} + + +void +Model::apply_layout(Index index) +{ + if (index < m_workspaces.size()) + apply_layout(m_workspaces[index]); +} + +void +Model::apply_layout(Workspace_ptr workspace) +{ + if (workspace != mp_workspace) + return; + + for (auto& placement : workspace->arrange(active_screen().placeable_region())) + place_client(placement); +} + + +void +Model::apply_stack(Index index) +{ + if (index < m_workspaces.size()) + apply_stack(m_workspaces[index]); +} + +void +Model::apply_stack(Workspace_ptr workspace) +{ + static std::vector<Window> stack; + + if (workspace != mp_workspace) + return; + + std::vector<Client_ptr> clients = workspace->stack_after_focus(); + + auto fullscreen_iter = std::stable_partition( + clients.begin(), + clients.end(), + [](Client_ptr client) -> bool { + return client->fullscreen && !client->contained; + } + ); + + auto free_iter = std::stable_partition( + fullscreen_iter, + clients.end(), + [=,this](Client_ptr client) -> bool { + return is_free(client); + } + ); + + stack.reserve(3 * clients.size()); + stack.clear(); + + Util::append(stack, m_stack.get_layer(StackHandler::StackLayer::Desktop)); + Util::append(stack, m_stack.get_layer(StackHandler::StackLayer::Below_)); + Util::append(stack, m_stack.get_layer(StackHandler::StackLayer::Dock)); + + std::transform( + free_iter, + clients.end(), + std::back_inserter(stack), + [](Client_ptr client) -> Window { + return client->frame; + } + ); + + std::transform( + clients.begin(), + fullscreen_iter, + std::back_inserter(stack), + [](Client_ptr client) -> Window { + return client->frame; + } + ); + + std::transform( + fullscreen_iter, + free_iter, + std::back_inserter(stack), + [](Client_ptr client) -> Window { + return client->frame; + } + ); + + Util::append(stack, m_stack.get_layer(StackHandler::StackLayer::Above_)); + Util::append(stack, m_stack.get_layer(StackHandler::StackLayer::Notification)); + + bool order_changed = false; + std::optional<Window> prev_window + = stack.empty() ? std::nullopt : std::optional(stack[0]); + + for (std::size_t i = 1; i < stack.size(); ++i) { + if (!order_changed) { + if (i < m_order.size()) + order_changed |= m_order[i] != stack[i]; + else + order_changed = true; + } + + if (order_changed) + m_conn.stack_window_above(stack[i], prev_window); + + prev_window = stack[i]; + } + + if (!order_changed) + return; + + m_order = stack; + + static constexpr struct ManagedSinceComparer final { + bool + operator()(const Client_ptr lhs, const Client_ptr rhs) const + { + return lhs->managed_since < rhs->managed_since; + } + } managed_since_comparer = {}; + + static std::set<Client_ptr, ManagedSinceComparer> managed_since_clients + = {{}, managed_since_comparer}; + + managed_since_clients.clear(); + + std::for_each( + m_client_map.begin(), + m_client_map.end(), + [](auto kv) { + managed_since_clients.insert(kv.second); + } + ); + + static std::vector<Window> order_list; + order_list.reserve(managed_since_clients.size()); + order_list.clear(); + + std::transform( + managed_since_clients.begin(), + managed_since_clients.end(), + std::back_inserter(order_list), + [](Client_ptr client) -> Window { + return client->window; + } + ); + + m_conn.update_client_list(order_list); + + static constexpr struct LastFocusedComparer final { + bool + operator()(const Client_ptr lhs, const Client_ptr rhs) const + { + return lhs->last_focused < rhs->last_focused; + } + } last_focused_comparer = {}; + + static std::set<Client_ptr, LastFocusedComparer> last_focused_clients + = {{}, last_focused_comparer}; + + last_focused_clients.clear(); + + std::for_each( + m_client_map.begin(), + m_client_map.end(), + [](auto kv) { + last_focused_clients.insert(kv.second); + } + ); + + order_list.reserve(last_focused_clients.size()); + order_list.clear(); + + std::transform( + last_focused_clients.begin(), + last_focused_clients.end(), + std::back_inserter(order_list), + [](Client_ptr client) -> Window { + return client->window; + } + ); + + m_conn.update_client_list_stacking(order_list); +} + + +void +Model::cycle_focus(Direction direction) +{ + if (mp_workspace->size() <= 1) + return; + + mp_workspace->cycle(direction); + focus_client(mp_workspace->active()); + sync_focus(); +} + +void +Model::drag_focus(Direction direction) +{ + if (mp_workspace->size() <= 1) + return; + + mp_workspace->drag(direction); + focus_client(mp_workspace->active()); + sync_focus(); + + apply_layout(mp_workspace); +} + + +void +Model::rotate_clients(Direction direction) +{ + if (mp_workspace->size() <= 1) + return; + + mp_workspace->rotate(direction); + focus_client(mp_workspace->active()); + sync_focus(); + + apply_layout(mp_workspace); +} + + +void +Model::move_focus_to_next_workspace(Direction direction) +{ + if (mp_focus) + move_client_to_next_workspace(direction, mp_focus); +} + +void +Model::move_client_to_next_workspace(Direction direction, Client_ptr client) +{ + Index index = mp_workspace->index(); + + switch (direction) { + case Direction::Forward: + { + index = (index + 1) % m_workspaces.size(); + break; + } + case Direction::Backward: + { + index = (index == 0 ? m_workspaces.size() : index) - 1; + break; + } + default: return; + } + + move_client_to_workspace(index, client); +} + +void +Model::move_focus_to_workspace(Index index) +{ + if (mp_focus) + move_client_to_workspace(index, mp_focus); +} + +void +Model::move_client_to_workspace(Index index, Client_ptr client) +{ + if (index >= m_workspaces.size() || index == mp_workspace->index() || client->sticky) + return; + + client->workspace = index; + Workspace_ptr workspace = get_workspace(index); + + unmap_client(client); + + workspace->add_client(client); + apply_layout(workspace); + apply_stack(workspace); + + mp_workspace->remove_client(client); + apply_layout(mp_workspace); + + sync_focus(); +} + + +void +Model::toggle_layout() +{ + mp_workspace->toggle_layout(); + apply_layout(mp_workspace); + apply_stack(mp_workspace); +} + +void +Model::set_layout(LayoutHandler::LayoutKind layout) +{ + mp_workspace->set_layout(layout); + apply_layout(mp_workspace); + apply_stack(mp_workspace); +} + +void +Model::set_layout_retain_region(LayoutHandler::LayoutKind layout) +{ + const std::deque<Client_ptr>& clients = mp_workspace->clients(); + std::vector<Region> regions; + + bool was_tiled = !mp_workspace->layout_is_free(); + + if (was_tiled) { + regions.reserve(clients.size()); + + std::transform( + clients.begin(), + clients.end(), + std::back_inserter(regions), + [=,this](Client_ptr client) -> Region { + if (is_free(client)) + return client->free_region; + else + return client->tile_region; + } + ); + } + + mp_workspace->set_layout(layout); + + if (was_tiled && mp_workspace->layout_is_free()) + for (std::size_t i = 0; i < clients.size(); ++i) + clients[i]->set_free_region(regions[i]); + + apply_layout(mp_workspace); + apply_stack(mp_workspace); +} + + +void +Model::toggle_layout_data() +{ + mp_workspace->toggle_layout_data(); + apply_layout(mp_workspace); +} + +void +Model::cycle_layout_data(Direction direction) +{ + mp_workspace->cycle_layout_data(direction); + apply_layout(mp_workspace); +} + +void +Model::copy_data_from_prev_layout() +{ + mp_workspace->copy_data_from_prev_layout(); + apply_layout(mp_workspace); +} + + +void +Model::change_gap_size(Util::Change<int> change) +{ + mp_workspace->change_gap_size(change); + apply_layout(mp_workspace); +} + +void +Model::change_main_count(Util::Change<int> change) +{ + mp_workspace->change_main_count(change); + apply_layout(mp_workspace); +} + +void +Model::change_main_factor(Util::Change<float> change) +{ + mp_workspace->change_main_factor(change); + apply_layout(mp_workspace); +} + +void +Model::change_margin(Util::Change<int> change) +{ + mp_workspace->change_margin(change); + apply_layout(mp_workspace); +} + +void +Model::change_margin(Edge edge, Util::Change<int> change) +{ + mp_workspace->change_margin(edge, change); + apply_layout(mp_workspace); +} + +void +Model::reset_gap_size() +{ + mp_workspace->reset_gap_size(); + apply_layout(mp_workspace); +} + +void +Model::reset_margin() +{ + mp_workspace->reset_margin(); + apply_layout(mp_workspace); +} + +void +Model::reset_layout_data() +{ + mp_workspace->reset_layout_data(); + apply_layout(mp_workspace); +} + + +void +Model::save_layout(std::size_t number) const +{ + mp_workspace->save_layout(number); +} + +void +Model::load_layout(std::size_t number) +{ + mp_workspace->load_layout(number); + apply_layout(mp_workspace); + apply_stack(mp_workspace); +} + + +void +Model::kill_focus() +{ + if (mp_focus) + kill_client(mp_focus); +} + +void +Model::kill_client(Client_ptr client) +{ + if (!client->invincible) { + m_conn.unmap_window(client->frame); + m_conn.kill_window(client->window); + m_conn.flush(); + } +} + + +void +Model::jump_client(JumpSelector const& selector) +{ + static constexpr struct LastFocusedComparer final { + bool + operator()(const Client_ptr lhs, const Client_ptr rhs) const + { + return lhs->last_focused < rhs->last_focused; + } + } last_focused_comparer = {}; + + static std::set<Client_ptr, LastFocusedComparer> clients + = {{}, last_focused_comparer}; + + clients.clear(); + + switch (selector.criterium()) { + case JumpSelector::SelectionCriterium::OnWorkspaceBySelector: + { + auto const& [index,selector_] = selector.workspace_selector(); + + if (index <= m_workspaces.size()) { + Workspace_ptr workspace = m_workspaces[index]; + std::optional<Client_ptr> client = workspace->find_client(selector_); + + if (client && (*client)->managed) + clients.insert(*client); + } + + break; + } + case JumpSelector::SelectionCriterium::ByNameEquals: + { + std::string const& name = selector.string_value(); + + for (auto const&[_,client] : m_client_map) + if (client->managed && client->name == name) + clients.insert(client); + + break; + } + case JumpSelector::SelectionCriterium::ByClassEquals: + { + std::string const& class_ = selector.string_value(); + + for (auto const&[_,client] : m_client_map) + if (client->managed && client->class_ == class_) + clients.insert(client); + + break; + } + case JumpSelector::SelectionCriterium::ByInstanceEquals: + { + std::string const& instance = selector.string_value(); + + for (auto const&[_,client] : m_client_map) + if (client->managed && client->instance == instance) + clients.insert(client); + + break; + } + case JumpSelector::SelectionCriterium::ByNameContains: + { + std::string const& name = selector.string_value(); + + for (auto const&[_,client] : m_client_map) + if (client->managed && client->name.find(name) != std::string::npos) + clients.insert(client); + + break; + } + case JumpSelector::SelectionCriterium::ByClassContains: + { + std::string const& class_ = selector.string_value(); + + for (auto const&[_,client] : m_client_map) + if (client->managed && client->class_.find(class_) != std::string::npos) + clients.insert(client); + + break; + } + case JumpSelector::SelectionCriterium::ByInstanceContains: + { + std::string const& instance = selector.string_value(); + + for (auto const&[_,client] : m_client_map) + if (client->managed && client->instance.find(instance) != std::string::npos) + clients.insert(client); + + break; + } + case JumpSelector::SelectionCriterium::ForCondition: + { + std::function<bool(const Client_ptr)> const& filter = selector.filter(); + + for (auto const&[_,client] : m_client_map) + if (client->managed && filter(client)) + clients.insert(client); + + break; + } + default: return; + } + + if (!clients.empty()) { + Client_ptr client = *clients.rbegin(); + + if (client == mp_focus) { + if (mp_jumped_from && client != mp_jumped_from) + client = mp_jumped_from; + } + + if (mp_focus) + mp_jumped_from = mp_focus; + + focus_client(client); + } +} + + +void +Model::set_floating_focus(Toggle toggle) +{ + if (mp_focus) + set_floating_client(toggle, mp_focus); +} + +void +Model::set_floating_client(Toggle toggle, Client_ptr client) +{ + switch (toggle) { + case Toggle::On: client->floating = true; break; + case Toggle::Off: client->floating = false; break; + case Toggle::Reverse: client->floating = !client->floating; break; + default: return; + } + + apply_layout(client->workspace); + apply_stack(client->workspace); +} + +void +Model::set_fullscreen_focus(Toggle toggle) +{ + if (mp_focus) + set_fullscreen_client(toggle, mp_focus); +} + +void +Model::set_fullscreen_client(Toggle toggle, Client_ptr client) +{ + switch (toggle) { + case Toggle::On: + { + if (client->fullscreen) + return; + + client->fullscreen = true; + + m_conn.set_window_state( + client->window, + WindowState::Fullscreen, + true + ); + + Workspace_ptr workspace = get_workspace(client->workspace); + + apply_layout(workspace); + apply_stack(workspace); + render_decoration(client); + + m_fullscreen_map[client] = client->free_region; + + return; + } + case Toggle::Off: + { + if (!client->fullscreen) + return; + + client->set_free_region(m_fullscreen_map.at(client)); + client->fullscreen = false; + + m_conn.set_window_state( + client->window, + WindowState::Fullscreen, + false + ); + + Workspace_ptr workspace = get_workspace(client->workspace); + + apply_layout(workspace); + apply_stack(workspace); + render_decoration(client); + + m_fullscreen_map.erase(client); + + return; + } + case Toggle::Reverse: + { + set_fullscreen_client( + client->fullscreen + ? Toggle::Off + : Toggle::On, + client + ); + + return; + } + default: return; + } +} + +void +Model::set_sticky_focus(Toggle toggle) +{ + if (mp_focus) + set_sticky_client(toggle, mp_focus); +} + +void +Model::set_sticky_client(Toggle toggle, Client_ptr client) +{ + switch (toggle) { + case Toggle::On: + { + if (client->sticky) + return; + + std::for_each( + m_workspaces.begin(), + m_workspaces.end(), + [client](Workspace_ptr workspace) { + if (workspace->index() != client->workspace) + workspace->add_client(client); + } + ); + + m_conn.set_window_state( + client->window, + WindowState::Sticky, + true + ); + + Workspace_ptr workspace = get_workspace(client->workspace); + + client->stick(); + + apply_layout(workspace); + render_decoration(client); + + return; + } + case Toggle::Off: + { + if (!client->sticky) + return; + + std::for_each( + m_workspaces.begin(), + m_workspaces.end(), + [=,this](Workspace_ptr workspace) { + if (workspace->index() != mp_workspace->index()) { + workspace->remove_client(client); + workspace->remove_icon(client); + workspace->remove_disowned(client); + } else { + client->workspace = workspace->index(); + } + } + ); + + m_conn.set_window_state( + client->window, + WindowState::Sticky, + false + ); + + client->unstick(); + + apply_layout(mp_workspace); + render_decoration(client); + + return; + } + case Toggle::Reverse: + { + set_sticky_client( + client->sticky + ? Toggle::Off + : Toggle::On, + client + ); + + return; + } + default: return; + } +} + +void +Model::set_contained_focus(Toggle toggle) +{ + if (mp_focus) + set_contained_client(toggle, mp_focus); +} + +void +Model::set_contained_client(Toggle toggle, Client_ptr client) +{ + switch (toggle) { + case Toggle::On: + { + client->contained = true; + set_fullscreen_client(toggle, client); + + return; + } + case Toggle::Off: + { + client->contained = false; + set_fullscreen_client(toggle, client); + + return; + } + case Toggle::Reverse: + { + set_contained_client( + client->contained + ? Toggle::Off + : Toggle::On, + client + ); + + return; + } + default: return; + } +} + +void +Model::set_invincible_focus(Toggle toggle) +{ + if (mp_focus) + set_invincible_client(toggle, mp_focus); +} + +void +Model::set_invincible_client(Toggle toggle, Client_ptr client) +{ + if (toggle == Toggle::Reverse) + set_invincible_client( + client->invincible + ? Toggle::Off + : Toggle::On, + client + ); + else + client->invincible + = toggle == Toggle::On ? true : false; +} + +void +Model::set_producing_focus(Toggle toggle) +{ + if (mp_focus) + set_producing_client(toggle, mp_focus); +} + +void +Model::set_producing_client(Toggle toggle, Client_ptr client) +{ + if (toggle == Toggle::Reverse) + set_producing_client( + client->producing + ? Toggle::Off + : Toggle::On, + client + ); + else + client->producing + = toggle == Toggle::On ? true : false; +} + +void +Model::set_iconifyable_focus(Toggle toggle) +{ + if (mp_focus) + set_iconifyable_client(toggle, mp_focus); +} + +void +Model::set_iconifyable_client(Toggle toggle, Client_ptr client) +{ + if (toggle == Toggle::Reverse) + set_iconifyable_client( + client->iconifyable + ? Toggle::Off + : Toggle::On, + client + ); + else + client->iconifyable + = toggle == Toggle::On ? true : false; +} + +void +Model::set_iconify_focus(Toggle toggle) +{ + if (mp_focus) + set_iconify_client(toggle, mp_focus); +} + +void +Model::set_iconify_client(Toggle toggle, Client_ptr client) +{ + switch (toggle) { + case Toggle::On: + { + if (client->iconified) + return; + + Workspace_ptr workspace = get_workspace(client->workspace); + workspace->client_to_icon(client); + + m_conn.set_icccm_window_state( + client->window, + IcccmWindowState::Iconic + ); + + unmap_client(client); + + apply_layout(workspace); + sync_focus(); + + client->iconified = true; + + return; + } + case Toggle::Off: + { + if (!client->iconified) + return; + + Workspace_ptr workspace = get_workspace(client->workspace); + workspace->icon_to_client(client); + + m_conn.set_icccm_window_state( + client->window, + IcccmWindowState::Normal + ); + + client->iconified = false; + + apply_layout(workspace); + sync_focus(); + + return; + } + case Toggle::Reverse: + { + set_iconify_client( + client->iconified + ? Toggle::Off + : Toggle::On, + client + ); + + return; + } + default: return; + } +} + + +void +Model::consume_client(Client_ptr producer, Client_ptr client) +{ + Workspace_ptr pworkspace = get_workspace(producer->workspace); + Workspace_ptr cworkspace = get_workspace(client->workspace); + + if (client->producer + || !cworkspace->contains(client) + || !pworkspace->contains(producer) + ) { + return; + } + + if (m_move_buffer.client() == producer) + stop_moving(); + + if (m_resize_buffer.client() == producer) + stop_resizing(); + + unmap_client(producer); + + if (producer->consumers.size() == 0) { + if (pworkspace == cworkspace) { + cworkspace->remove_client(client); + pworkspace->replace_client(producer, client); + } else + pworkspace->remove_client(producer); + + apply_layout(pworkspace); + apply_layout(cworkspace); + } + + client->producer = producer; + producer->managed = false; + producer->consumers.push_back(client); + + sync_focus(); + apply_stack(pworkspace); + apply_stack(cworkspace); +} + +void +Model::check_unconsume_client(Client_ptr client) +{ + Client_ptr producer = client->producer; + + if (!producer || producer->managed) + return; + + Workspace_ptr cworkspace = get_workspace(client->workspace); + + client->producer = nullptr; + producer->managed = true; + Util::erase_remove(producer->consumers, client); + + if (producer->consumers.size() == 0) { + if (cworkspace->contains(client)) { + cworkspace->replace_client(client, producer); + producer->workspace = cworkspace->index(); + } else { + mp_workspace->add_client(producer); + producer->workspace = mp_workspace->index(); + } + + apply_layout(mp_workspace); + apply_layout(cworkspace); + } + + sync_focus(); + apply_stack(mp_workspace); + apply_stack(cworkspace); +} + + +void +Model::center_focus() +{ + if (mp_focus) + center_client(mp_focus); +} + +void +Model::center_client(Client_ptr client) +{ + if (!is_free(client)) + return; + + const Region screen_region = active_screen().placeable_region(); + Region region = client->free_region; + + region.pos.x = screen_region.pos.x + + (screen_region.dim.w - region.dim.w) / 2; + + region.pos.y = screen_region.pos.y + + (screen_region.dim.h - region.dim.h) / 2; + + Placement placement = Placement { + Placement::PlacementMethod::Free, + client, + client->free_decoration, + region + }; + + place_client(placement); +} + +void +Model::nudge_focus(Edge edge, Util::Change<std::size_t> change) +{ + if (mp_focus) + nudge_client(edge, change, mp_focus); +} + +void +Model::nudge_client(Edge edge, Util::Change<std::size_t> change, Client_ptr client) +{ + if (!is_free(client)) + return; + + Region region = client->free_region; + + switch (edge) { + case Edge::Left: + { + region.pos.x -= change; + break; + } + case Edge::Top: + { + region.pos.y -= change; + break; + } + case Edge::Right: + { + region.pos.x += change; + break; + } + case Edge::Bottom: + { + region.pos.y += change; + break; + } + default: return; + } + + Placement placement = Placement { + Placement::PlacementMethod::Free, + client, + client->free_decoration, + region + }; + + place_client(placement); +} + +void +Model::stretch_focus(Edge edge, Util::Change<int> change) +{ + if (mp_focus) + stretch_client(edge, change, mp_focus); +} + +void +Model::stretch_client(Edge edge, Util::Change<int> change, Client_ptr client) +{ + if (!is_free(client)) + return; + + Decoration decoration = client->free_decoration; + Extents extents = Extents { 0, 0, 0, 0 }; + + if (decoration.border) { + extents.left += decoration.border->width; + extents.top += decoration.border->width; + extents.right += decoration.border->width; + extents.bottom += decoration.border->width; + } + + if (decoration.frame) { + extents.left += decoration.frame->extents.left; + extents.top += decoration.frame->extents.top; + extents.right += decoration.frame->extents.right; + extents.bottom += decoration.frame->extents.bottom; + } + + Region region = client->free_region; + region.remove_extents(extents); + + switch (edge) { + case Edge::Left: + { + if (!(change < 0 && -change >= region.dim.h)) { + if (region.dim.w + change <= Client::MIN_CLIENT_DIM.w) { + region.pos.x -= Client::MIN_CLIENT_DIM.w - region.dim.w; + region.dim.w = Client::MIN_CLIENT_DIM.w; + } else { + region.pos.x -= change; + region.dim.w += change; + } + } + + break; + } + case Edge::Top: + { + if (!(change < 0 && -change >= region.dim.h)) { + if (region.dim.h + change <= Client::MIN_CLIENT_DIM.h) { + region.pos.y -= Client::MIN_CLIENT_DIM.h - region.dim.h; + region.dim.h = Client::MIN_CLIENT_DIM.h; + } else { + region.pos.y -= change; + region.dim.h += change; + } + } + + break; + } + case Edge::Right: + { + if (!(change < 0 && -change >= region.dim.w)) { + if (region.dim.w + change <= Client::MIN_CLIENT_DIM.w) + region.dim.w = Client::MIN_CLIENT_DIM.w; + else + region.dim.w += change; + } + + break; + } + case Edge::Bottom: + { + if (!(change < 0 && -change >= region.dim.h)) { + if (region.dim.h + change <= Client::MIN_CLIENT_DIM.h) + region.dim.h = Client::MIN_CLIENT_DIM.h; + else + region.dim.h += change; + } + + break; + } + default: return; + } + + region.apply_extents(extents); + + Placement placement = Placement { + Placement::PlacementMethod::Free, + client, + client->free_decoration, + region + }; + + place_client(placement); +} + +void +Model::inflate_focus(Util::Change<int> change) +{ + if (mp_focus) + inflate_client(change, mp_focus); +} + +void +Model::inflate_client(Util::Change<int> change, Client_ptr client) +{ + if (!is_free(client)) + return; + + Decoration decoration = client->free_decoration; + Extents extents = Extents { 0, 0, 0, 0 }; + + if (decoration.border) { + extents.left += decoration.border->width; + extents.top += decoration.border->width; + extents.right += decoration.border->width; + extents.bottom += decoration.border->width; + } + + if (decoration.frame) { + extents.left += decoration.frame->extents.left; + extents.top += decoration.frame->extents.top; + extents.right += decoration.frame->extents.right; + extents.bottom += decoration.frame->extents.bottom; + } + + Region region = client->free_region; + region.remove_extents(extents); + + double ratio = static_cast<double>(region.dim.w) + / static_cast<double>(region.dim.w + region.dim.h); + + double width_inc = ratio * change; + double height_inc = change - width_inc; + + int dw = std::lround(width_inc); + int dh = std::lround(height_inc); + + if ((dw < 0 && -dw >= region.dim.w) + || (dh < 0 && -dh >= region.dim.h) + || (region.dim.w + dw <= Client::MIN_CLIENT_DIM.w) + || (region.dim.h + dh <= Client::MIN_CLIENT_DIM.h)) + { + return; + } + + region.dim.w += dw; + region.dim.h += dh; + + region.apply_extents(extents); + + int dx = region.dim.w - client->free_region.dim.w; + int dy = region.dim.h - client->free_region.dim.h; + + dx = std::lround(dx / static_cast<double>(2)); + dy = std::lround(dy / static_cast<double>(2)); + + region.pos.x -= dx; + region.pos.y -= dy; + + client->set_free_region(region); + + Placement placement = Placement { + Placement::PlacementMethod::Free, + client, + client->free_decoration, + region + }; + + place_client(placement); +} + +void +Model::snap_focus(Edge edge) +{ + if (mp_focus) + snap_client(edge, mp_focus); +} + +void +Model::snap_client(Edge edge, Client_ptr client) +{ + if (!is_free(client)) + return; + + const Region screen_region = active_screen().placeable_region(); + Region region = client->free_region; + + switch (edge) { + case Edge::Left: + { + region.pos.x = screen_region.pos.x; + + break; + } + case Edge::Top: + { + region.pos.y = screen_region.pos.y; + + break; + } + case Edge::Right: + { + region.pos.x = std::max( + 0, + (screen_region.dim.w + screen_region.pos.x) - region.dim.w + ); + + break; + } + case Edge::Bottom: + { + region.pos.y = std::max( + 0, + (screen_region.dim.h + screen_region.pos.y) - region.dim.h + ); + + break; + } + } + + Placement placement = Placement { + Placement::PlacementMethod::Free, + client, + client->free_decoration, + region + }; + + place_client(placement); +} + + +void +Model::activate_screen_struts(winsys::Toggle toggle) +{ + Screen& screen = mp_partition->screen(); + + switch (toggle) { + case Toggle::On: + { + if (screen.showing_struts()) + return; + + for (auto& strut : screen.show_and_get_struts(true)) + m_conn.map_window(strut); + + apply_layout(mp_workspace); + + return; + } + case Toggle::Off: + { + if (!screen.showing_struts()) + return; + + for (auto& strut : screen.show_and_get_struts(false)) + m_conn.unmap_window(strut); + + apply_layout(mp_workspace); + + return; + } + case Toggle::Reverse: + { + activate_screen_struts( + screen.showing_struts() + ? Toggle::Off + : Toggle::On + ); + + return; + } + default: return; + } +} + + +void +Model::pop_deiconify() +{ + std::optional<Client_ptr> icon = mp_workspace->pop_icon(); + + if (icon) + set_iconify_client(Toggle::Off, *icon); +} + +void +Model::deiconify_all() +{ + for (std::size_t i = 0; i < mp_workspace->size(); ++i) + pop_deiconify(); +} + + +void +Model::spawn_external(std::string&& command) const +{ + spdlog::info("calling external command: " + command); + m_conn.call_external_command(command); +} + + +void +Model::exit() +{ + for (auto& [window,client] : m_client_map) + m_conn.unparent_window(client->window, client->free_region.pos); + + m_conn.cleanup(); + m_running = false; + + spdlog::info("exiting " + WM_NAME); +} + + +// handlers + +void +Model::handle_mouse(MouseEvent event) +{ + MouseInput input = event.capture.input; + + switch (event.capture.kind) { + case MouseCapture::MouseCaptureKind::Release: + { + stop_moving(); + stop_resizing(); + + return; + } + case MouseCapture::MouseCaptureKind::Motion: + { + perform_move(event.capture.root_rpos); + perform_resize(event.capture.root_rpos); + + return; + } + default: break; + } + + Client_ptr client + = event.capture.window + ? get_client(*event.capture.window) + : nullptr; + +#define CALL_MUST_FOCUS(binding) \ + ((*binding)(*this, client) && client && client != mp_focus) + { // global binding + input.target = MouseInput::MouseInputTarget::Global; + auto binding = Util::retrieve(m_mouse_bindings, input); + + if (binding) { + if (CALL_MUST_FOCUS(binding)) + focus_client(client); + + return; + } + } + + if (event.on_root) { // root binding + input.target = MouseInput::MouseInputTarget::Root; + auto binding = Util::retrieve(m_mouse_bindings, input); + + if (binding) { + if (CALL_MUST_FOCUS(binding)) + focus_client(client); + + return; + } + } + + { // client binding + input.target = MouseInput::MouseInputTarget::Client; + auto binding = Util::retrieve(m_mouse_bindings, input); + + if (binding) { + if (CALL_MUST_FOCUS(binding)) + focus_client(client); + + return; + } + } +#undef CALL_MUST_FOCUS + + if (client && client != mp_focus) + focus_client(client); +} + +void +Model::handle_key(KeyEvent event) +{ + auto binding = Util::retrieve(m_key_bindings, event.capture.input); + + if (binding) + (*binding)(*this); +} + +void +Model::handle_map_request(MapRequestEvent event) +{ + bool must_restack = false; + std::optional<std::vector<std::optional<Strut>>> struts + = m_conn.get_window_strut(event.window); + + if (struts) { + Screen& screen = mp_partition->screen(); + screen.add_struts(*struts); + + if (screen.showing_struts()) { + screen.compute_placeable_region(); + + if (m_conn.window_is_mappable(event.window)) + m_conn.map_window(event.window); + + apply_layout(mp_workspace); + must_restack = true; + } else + m_conn.unmap_window(event.window); + } + + WindowType type + = m_conn.get_window_preferred_type(event.window); + + std::optional<WindowState> state + = m_conn.get_window_preferred_state(event.window); + + std::optional<Region> region + = m_conn.get_window_geometry(event.window); + + std::optional<StackHandler::StackLayer> layer = std::nullopt; + + if (state == WindowState::Below_) + layer = StackHandler::StackLayer::Below_; + else if (type == WindowType::Desktop) + layer = StackHandler::StackLayer::Desktop; + else if (type == WindowType::Dock) { + Screen& screen = mp_partition->screen(); + + if (region && !screen.contains_strut(event.window)) { + const Region screen_region = screen.full_region(); + std::optional<Edge> edge = std::nullopt; + std::optional<Strut> strut = std::nullopt; + + if (Pos::is_at_origin(region->pos)) { + if (region->dim.w == screen_region.dim.w) { + edge = Edge::Top; + strut = Strut { event.window, region->dim.h }; + } else if (region->dim.h == screen_region.dim.h) { + edge = Edge::Left; + strut = Strut { event.window, region->dim.w }; + } else if (region->dim.w > screen_region.dim.h) { + edge = Edge::Top; + strut = Strut { event.window, region->dim.h }; + } else if (region->dim.w < screen_region.dim.h) { + edge = Edge::Left; + strut = Strut { event.window, region->dim.w }; + } + } + + if (!strut) { + if (region->pos.y == region->dim.h) { + edge = Edge::Bottom; + strut = Strut { event.window, screen_region.dim.h - region->dim.h }; + } else if (region->pos.x == region->dim.w) { + edge = Edge::Right; + strut = Strut { event.window, screen_region.dim.w - region->dim.w }; + } + } + + if (strut) + screen.add_strut(*edge, *strut); + } + + layer = StackHandler::StackLayer::Dock; + } else if (type == WindowType::Notification) + layer = StackHandler::StackLayer::Notification; + else if (state == WindowState::Above_) + layer = StackHandler::StackLayer::Above_; + + if (layer) { + m_stack.add_window(event.window, *layer); + must_restack = true; + } + + Screen& screen = mp_partition->screen(); + + if (screen.showing_struts()) { + screen.compute_placeable_region(); + m_conn.map_window(event.window); + + apply_layout(mp_workspace); + } else + m_conn.unmap_window(event.window); + + if (must_restack) + apply_stack(mp_workspace); + + if (!(m_client_map.count(event.window) > 0)) + manage(event.window, event.ignore); +} + +void +Model::handle_map(MapEvent) +{} + +void +Model::handle_enter(EnterEvent event) +{ + Client_ptr client = get_client(event.window); + + if (!client || client == mp_focus) + return; + + unfocus_client(mp_focus); + focus_client(client); +} + +void +Model::handle_leave(LeaveEvent event) +{ + Client_ptr client = get_client(event.window); + + if (!client) + return; + + unfocus_client(client); +} + +void +Model::handle_destroy(DestroyEvent event) +{ + Screen& screen = mp_partition->screen(); + + if (screen.contains_strut(event.window)) { + screen.remove_strut(event.window); + screen.compute_placeable_region(); + + apply_layout(mp_workspace); + apply_stack(mp_workspace); + } + + m_stack.remove_window(event.window); + + Util::erase_remove(m_unmanaged_windows, event.window); + + Client_ptr client = get_client(event.window); + + if (!client) + return; + + unmanage(client); +} + +void +Model::handle_expose(ExposeEvent) +{} + +void +Model::handle_unmap(UnmapEvent event) +{ + if (Util::contains(m_unmanaged_windows, event.window)) + return; + + handle_destroy(DestroyEvent { event.window }); +} + +void +Model::handle_state_request(StateRequestEvent event) +{ + Client_ptr client = get_client(event.window); + + if (!client) + return; + + switch (event.state) { + case WindowState::Fullscreen: + { + set_fullscreen_client(event.action, client); + return; + } + case WindowState::Sticky: + { + set_sticky_client(event.action, client); + return; + } + case WindowState::DemandsAttention: + { + bool value; + + switch (event.action) { + case Toggle::On: value = true; break; + case Toggle::Off: value = false; break; + case Toggle::Reverse: value = !client->urgent; break; + default: return; + } + + m_conn.set_icccm_window_hints(client->window, Hints { + value, + std::nullopt, + std::nullopt, + std::nullopt + }); + + client->urgent = value; + render_decoration(client); + + return; + } + default: break; + } +} + +void +Model::handle_focus_request(FocusRequestEvent event) +{ + Client_ptr client = get_client(event.window); + + if (!client || event.on_root) + return; + + focus_client(client); +} + +void +Model::handle_close_request(CloseRequestEvent event) +{ + if (!event.on_root) + m_conn.kill_window(event.window); +} + +void +Model::handle_workspace_request(WorkspaceRequestEvent event) +{ + if (!event.on_root) + activate_workspace(event.index); +} + +void +Model::handle_placement_request(PlacementRequestEvent event) +{ + if (!event.pos && !event.dim) + return; + + Client_ptr client = get_client(event.window); + + if (!client) { + std::optional<Region> geometry = m_conn.get_window_geometry(event.window); + + if (geometry) { + if (event.pos) + geometry->pos = *event.pos; + + if (event.dim) + geometry->dim = *event.dim; + + m_conn.place_window(event.window, *geometry); + } + + return; + } + + if (!is_free(client)) + return; + + Decoration decoration = client->free_decoration; + Extents extents = Extents { 0, 0, 0, 0 }; + + if (decoration.border) { + extents.left += decoration.border->width; + extents.top += decoration.border->width; + extents.right += decoration.border->width; + extents.bottom += decoration.border->width; + } + + if (decoration.frame) { + extents.left += decoration.frame->extents.left; + extents.top += decoration.frame->extents.top; + extents.right += decoration.frame->extents.right; + extents.bottom += decoration.frame->extents.bottom; + } + + Region region; + + if (event.window == client->window) { + Pos pos; + Dim dim; + + if (event.pos) + pos = Pos { + event.pos->x - extents.left, + event.pos->y - extents.top + }; + else + pos = client->free_region.pos; + + if (event.dim) + dim = Dim { + event.dim->w + extents.left + extents.right, + event.dim->h + extents.top + extents.bottom + }; + else + dim = client->free_region.dim; + + region = Region { + pos, dim + }; + } else { + region = Region { + event.pos.value_or(client->free_region.pos), + event.dim.value_or(client->free_region.dim) + }; + } + + region.remove_extents(extents); + + if (client->size_hints) + client->size_hints->apply(region.dim); + + region.apply_minimum_dim(Client::MIN_CLIENT_DIM); + region.apply_extents(extents); + + client->set_free_region(region); + + Placement placement = { + Placement::PlacementMethod::Free, + client, + client->free_decoration, + region + }; + + place_client(placement); +} + +void +Model::handle_grip_request(GripRequestEvent event) +{ + Client_ptr client = get_client(event.window); + + if (!client) + return; + + stop_moving(); + stop_resizing(); + + if (event.grip) { + + m_resize_buffer.set( + client, + *event.grip, + m_conn.get_pointer_position(), + client->free_region + ); + + m_conn.init_resize(client->frame); + } else + start_moving(client); +} + +void +Model::handle_restack_request(RestackRequestEvent) +{} + +void +Model::handle_configure(ConfigureEvent event) +{ + if (event.on_root) { + acquire_partitions(); + apply_layout(m_workspaces.active_index()); + } +} + +void +Model::handle_property(PropertyEvent event) +{ + switch (event.kind) { + case PropertyKind::Name: + { + Client_ptr client = get_client(event.window); + + if (!client) + return; + + client->name = m_conn.get_icccm_window_name(event.window); + + return; + } + case PropertyKind::Class: + { + Client_ptr client = get_client(event.window); + + if (!client) + return; + + client->class_ = m_conn.get_icccm_window_class(event.window); + client->instance = m_conn.get_icccm_window_instance(event.window); + + return; + } + case PropertyKind::Size: + { + Client_ptr client = get_client(event.window); + + if (!client) + return; + + client->size_hints + = m_conn.get_icccm_window_size_hints(event.window, Client::MIN_CLIENT_DIM); + + std::optional<Region> geometry = m_conn.get_window_geometry(event.window); + + if (!geometry) + return; + + Region region = *geometry; + + if (client->size_hints) + client->size_hints->apply(region.dim); + + region.apply_minimum_dim(Client::MIN_CLIENT_DIM); + + Decoration decoration = client->free_decoration; + Extents extents = Extents { 0, 0, 0, 0 }; + + if (decoration.border) { + extents.left += decoration.border->width; + extents.top += decoration.border->width; + extents.right += decoration.border->width; + extents.bottom += decoration.border->width; + } + + if (decoration.frame) { + extents.left += decoration.frame->extents.left; + extents.top += decoration.frame->extents.top; + extents.right += decoration.frame->extents.right; + extents.bottom += decoration.frame->extents.bottom; + } + + region.pos = client->free_region.pos; + region.dim.w += extents.left + extents.right; + region.dim.h += extents.top + extents.bottom; + + client->set_free_region(region); + + if (client->managed) + apply_layout(client->workspace); + + return; + } + case PropertyKind::Strut: + { + std::optional<std::vector<std::optional<Strut>>> struts + = m_conn.get_window_strut(event.window); + + if (struts) { + Screen& screen = mp_partition->screen(); + + screen.remove_strut(event.window); + screen.add_struts(*struts); + screen.compute_placeable_region(); + + apply_layout(mp_workspace); + apply_stack(mp_workspace); + } + + return; + } + default: break; + } +} + +void +Model::handle_frame_extents_request(FrameExtentsRequestEvent event) +{ + Client_ptr client = get_client(event.window); + + Extents extents = Extents { 0, 0, 0, 0 }; + + if (client) { + Decoration decoration = client->active_decoration; + + if (decoration.border) { + extents.left += decoration.border->width; + extents.top += decoration.border->width; + extents.right += decoration.border->width; + extents.bottom += decoration.border->width; + } + + if (decoration.frame) { + extents.left += decoration.frame->extents.left; + extents.top += decoration.frame->extents.top; + extents.right += decoration.frame->extents.right; + extents.bottom += decoration.frame->extents.bottom; + } + } + + m_conn.set_window_frame_extents(event.window, extents); +} + +void +Model::handle_screen_change() +{ + acquire_partitions(); + apply_layout(m_workspaces.active_index()); +} + + +// static methods + +void +Model::wait_children(int) +{ + struct sigaction child_sa; + + std::memset(&child_sa, 0, sizeof(child_sa)); + child_sa.sa_handler = &Model::wait_children; + + sigaction(SIGCHLD, &child_sa, NULL); + while (waitpid(-1, 0, WNOHANG) > 0); +} + +void +Model::handle_signal(int sig) +{ + if (sig == SIGHUP || sig == SIGINT || sig == SIGTERM) + g_instance->exit(); +} diff --git a/src/core/model.hh b/src/core/model.hh @@ -0,0 +1,324 @@ +#ifndef __MODEL_H_GUARD__ +#define __MODEL_H_GUARD__ + +#include "../winsys/connection.hh" +#include "../winsys/event.hh" +#include "bindings.hh" +#include "client.hh" +#include "context.hh" +#include "cycle.hh" +#include "jump.hh" +#include "layout.hh" +#include "partition.hh" +#include "partition.hh" +#include "rules.hh" +#include "stack.hh" +#include "workspace.hh" + +#include <cstddef> +#include <vector> + +class Model; +class Model final +{ +public: + Model(winsys::Connection&); + ~Model(); + + void run(); + +private: + static void wait_children(int); + static void handle_signal(int); + + void init_signals() const; + + void handle_mouse(winsys::MouseEvent); + void handle_key(winsys::KeyEvent); + void handle_map_request(winsys::MapRequestEvent); + void handle_map(winsys::MapEvent); + void handle_enter(winsys::EnterEvent); + void handle_leave(winsys::LeaveEvent); + void handle_destroy(winsys::DestroyEvent); + void handle_expose(winsys::ExposeEvent); + void handle_unmap(winsys::UnmapEvent); + void handle_state_request(winsys::StateRequestEvent); + void handle_focus_request(winsys::FocusRequestEvent); + void handle_close_request(winsys::CloseRequestEvent); + void handle_workspace_request(winsys::WorkspaceRequestEvent); + void handle_placement_request(winsys::PlacementRequestEvent); + void handle_grip_request(winsys::GripRequestEvent); + void handle_restack_request(winsys::RestackRequestEvent); + void handle_configure(winsys::ConfigureEvent); + void handle_property(winsys::PropertyEvent); + void handle_frame_extents_request(winsys::FrameExtentsRequestEvent); + void handle_screen_change(); + + void acquire_partitions(); + + const winsys::Screen& active_screen() const; + + Client_ptr get_client(winsys::Window); + Client_ptr get_const_client(winsys::Window) const; + + Index active_partition() const; + Partition_ptr get_partition(Index) const; + + Index active_context() const; + Context_ptr get_context(Index) const; + + Index active_workspace() const; + Workspace_ptr get_workspace(Index) const; + + bool is_free(Client_ptr) const; + + void place_client(Placement&); + + void map_client(Client_ptr); + void unmap_client(Client_ptr); + + void focus_client(Client_ptr); + void unfocus_client(Client_ptr); + void sync_focus(); + + void toggle_partition(); + void activate_next_partition(winsys::Direction); + void activate_partition(Util::Change<Index>); + void activate_partition(Partition_ptr); + + void toggle_context(); + void activate_next_context(winsys::Direction); + void activate_context(Util::Change<Index>); + void activate_context(Context_ptr); + + void toggle_workspace(); + void activate_next_workspace(winsys::Direction); + void activate_workspace(Util::Change<Index>); + void activate_workspace(Workspace_ptr); + + void render_decoration(Client_ptr); + + Rules retrieve_rules(Client_ptr) const; + + void manage(const winsys::Window, const bool); + void unmanage(Client_ptr); + + void start_moving(Client_ptr); + void start_resizing(Client_ptr); + + void stop_moving(); + void stop_resizing(); + + void perform_move(winsys::Pos&); + void perform_resize(winsys::Pos&); + + void apply_layout(Index); + void apply_layout(Workspace_ptr); + + void apply_stack(Index); + void apply_stack(Workspace_ptr); + + void cycle_focus(winsys::Direction); + void drag_focus(winsys::Direction); + + void rotate_clients(winsys::Direction); + + void move_focus_to_next_workspace(winsys::Direction); + void move_client_to_next_workspace(winsys::Direction, Client_ptr); + void move_focus_to_workspace(Index); + void move_client_to_workspace(Index, Client_ptr); + + void toggle_layout(); + void set_layout(LayoutHandler::LayoutKind); + void set_layout_retain_region(LayoutHandler::LayoutKind); + + void toggle_layout_data(); + void cycle_layout_data(winsys::Direction); + void copy_data_from_prev_layout(); + + void change_gap_size(Util::Change<int>); + void change_main_count(Util::Change<int>); + void change_main_factor(Util::Change<float>); + void change_margin(Util::Change<int>); + void change_margin(winsys::Edge, Util::Change<int>); + void reset_gap_size(); + void reset_margin(); + void reset_layout_data(); + + void save_layout(std::size_t) const; + void load_layout(std::size_t); + + void kill_focus(); + void kill_client(Client_ptr); + + void jump_client(JumpSelector const&); + + void set_floating_focus(winsys::Toggle); + void set_floating_client(winsys::Toggle, Client_ptr); + void set_fullscreen_focus(winsys::Toggle); + void set_fullscreen_client(winsys::Toggle, Client_ptr); + void set_sticky_focus(winsys::Toggle); + void set_sticky_client(winsys::Toggle, Client_ptr); + void set_contained_focus(winsys::Toggle); + void set_contained_client(winsys::Toggle, Client_ptr); + void set_invincible_focus(winsys::Toggle); + void set_invincible_client(winsys::Toggle, Client_ptr); + void set_producing_focus(winsys::Toggle); + void set_producing_client(winsys::Toggle, Client_ptr); + void set_iconifyable_focus(winsys::Toggle); + void set_iconifyable_client(winsys::Toggle, Client_ptr); + void set_iconify_focus(winsys::Toggle); + void set_iconify_client(winsys::Toggle, Client_ptr); + + void consume_client(Client_ptr, Client_ptr); + void check_unconsume_client(Client_ptr); + + void center_focus(); + void center_client(Client_ptr); + void nudge_focus(winsys::Edge, Util::Change<std::size_t>); + void nudge_client(winsys::Edge, Util::Change<std::size_t>, Client_ptr); + void stretch_focus(winsys::Edge, Util::Change<int>); + void stretch_client(winsys::Edge, Util::Change<int>, Client_ptr); + void inflate_focus(Util::Change<int>); + void inflate_client(Util::Change<int>, Client_ptr); + void snap_focus(winsys::Edge); + void snap_client(winsys::Edge, Client_ptr); + + void activate_screen_struts(winsys::Toggle); + + void pop_deiconify(); + void deiconify_all(); + + void spawn_external(std::string&&) const; + + void exit(); + + winsys::Connection& m_conn; + + bool m_running; + + Cycle<Partition_ptr> m_partitions; + Cycle<Context_ptr> m_contexts; + Cycle<Workspace_ptr> m_workspaces; + + Partition_ptr mp_partition; + Context_ptr mp_context; + Workspace_ptr mp_workspace; + + Partition_ptr mp_prev_partition; + Context_ptr mp_prev_context; + Workspace_ptr mp_prev_workspace; + + Buffer m_move_buffer; + Buffer m_resize_buffer; + + StackHandler m_stack; + std::vector<winsys::Window> m_order; + + std::unordered_map<winsys::Window, Client_ptr> m_client_map; + std::unordered_map<winsys::Pid, Client_ptr> m_pid_map; + std::unordered_map<Client_ptr, winsys::Region> m_fullscreen_map; + + std::vector<Client_ptr> m_sticky_clients; + std::vector<winsys::Window> m_unmanaged_windows; + + Client_ptr mp_focus; + Client_ptr mp_jumped_from; + + KeyBindings m_key_bindings; + MouseBindings m_mouse_bindings; + + struct EventVisitor final + { + EventVisitor(Model& model): m_model(model) {} + + void operator()(std::monostate) {} + + void operator()(winsys::MouseEvent event) { + m_model.handle_mouse(event); + } + + void operator()(winsys::KeyEvent event) { + m_model.handle_key(event); + } + + void operator()(winsys::MapRequestEvent event) { + m_model.handle_map_request(event); + } + + void operator()(winsys::MapEvent event) { + m_model.handle_map(event); + } + + void operator()(winsys::EnterEvent event) { + m_model.handle_enter(event); + } + + void operator()(winsys::LeaveEvent event) { + m_model.handle_leave(event); + } + + void operator()(winsys::DestroyEvent event) { + m_model.handle_destroy(event); + } + + void operator()(winsys::ExposeEvent event) { + m_model.handle_expose(event); + } + + void operator()(winsys::UnmapEvent event) { + m_model.handle_unmap(event); + } + + void operator()(winsys::StateRequestEvent event) { + m_model.handle_state_request(event); + } + + void operator()(winsys::FocusRequestEvent event) { + m_model.handle_focus_request(event); + } + + void operator()(winsys::CloseRequestEvent event) { + m_model.handle_close_request(event); + } + + void operator()(winsys::WorkspaceRequestEvent event) { + m_model.handle_workspace_request(event); + } + + void operator()(winsys::PlacementRequestEvent event) { + m_model.handle_placement_request(event); + } + + void operator()(winsys::GripRequestEvent event) { + m_model.handle_grip_request(event); + } + + void operator()(winsys::RestackRequestEvent event) { + m_model.handle_restack_request(event); + } + + void operator()(winsys::ConfigureEvent event) { + m_model.handle_configure(event); + } + + void operator()(winsys::PropertyEvent event) { + m_model.handle_property(event); + } + + void operator()(winsys::FrameExtentsRequestEvent event) { + m_model.handle_frame_extents_request(event); + } + + void operator()(winsys::ScreenChangeEvent) { + m_model.handle_screen_change(); + } + + private: + Model& m_model; + + } event_visitor = EventVisitor(*this); + +}; + +#endif//__MODEL_H_GUARD__ diff --git a/src/core/partition.cc b/src/core/partition.cc @@ -0,0 +1,2 @@ +#include "partition.hh" + diff --git a/src/core/partition.hh b/src/core/partition.hh @@ -0,0 +1,41 @@ +#ifndef __PARTITION_H_GUARD__ +#define __PARTITION_H_GUARD__ + +#include "../winsys/common.hh" +#include "../winsys/screen.hh" + +typedef class Partition final +{ +public: + Partition(winsys::Screen screen, Index index) + : m_screen(screen), + m_index(index) + {} + + ~Partition() {} + + winsys::Screen& + screen() + { + return m_screen; + } + + const winsys::Screen& + const_screen() const + { + return m_screen; + } + + Index + index() const + { + return m_index; + } + +private: + winsys::Screen m_screen; + Index m_index; + +}* Partition_ptr; + +#endif//__PARTITION_H_GUARD__ diff --git a/src/core/placement.hh b/src/core/placement.hh @@ -0,0 +1,43 @@ +#ifndef __PLACEMENT_H_GUARD__ +#define __PLACEMENT_H_GUARD__ + +#include "../winsys/geometry.hh" +#include "../winsys/decoration.hh" + +#include <cstdlib> +#include <optional> + +typedef class Client* Client_ptr; + +struct PlacementTarget final +{ + enum class TargetType + { + Client, + Tab, + Layout + }; + + TargetType type; + union + { + Client_ptr client; + std::size_t tab; + }; +}; + +struct Placement final +{ + enum class PlacementMethod + { + Free, + Tile, + }; + + PlacementMethod method; + Client_ptr client; + winsys::Decoration decoration; + std::optional<winsys::Region> region; +}; + +#endif//__PLACEMENT_H_GUARD__ diff --git a/src/core/rules.hh b/src/core/rules.hh @@ -0,0 +1,30 @@ +#ifndef __RULES_H_GUARD__ +#define __RULES_H_GUARD__ + +#include "../winsys/common.hh" + +#include <optional> + +struct Rules +{ + Rules() + : do_float(std::nullopt), + do_center(std::nullopt), + do_fullscreen(std::nullopt), + to_partition(std::nullopt), + to_context(std::nullopt), + to_workspace(std::nullopt) + {} + + ~Rules() = default; + + std::optional<bool> do_float; + std::optional<bool> do_center; + std::optional<bool> do_fullscreen; + std::optional<Index> to_partition; + std::optional<Index> to_context; + std::optional<Index> to_workspace; + +}; + +#endif//__RULES_H_GUARD__ diff --git a/src/core/stack.cc b/src/core/stack.cc @@ -0,0 +1,120 @@ +#include "stack.hh" +#include "../winsys/util.hh" + +StackHandler::StackHandler() + : m_layers({}), + m_desktop({}), + m_below({}), + m_dock({}), + m_above({}), + m_notification({}), + m_above_other({}), + m_below_other({}) +{} + +StackHandler::~StackHandler() +{} + +void +StackHandler::add_window(winsys::Window window, StackLayer layer) +{ + if (!(m_layers.count(window) > 0)) { + std::vector<winsys::Window>* windows; + + switch (layer) { + case StackLayer::Desktop: windows = &m_desktop; break; + case StackLayer::Below_: windows = &m_below; break; + case StackLayer::Dock: windows = &m_dock; break; + case StackLayer::Above_: windows = &m_above; break; + case StackLayer::Notification: windows = &m_notification; break; + default: Util::die("no associated layer"); + } + + windows->push_back(window); + m_layers[window] = layer; + } +} + +void +StackHandler::remove_window(winsys::Window window) +{ + if (m_layers.count(window) > 0) { + StackLayer layer = m_layers.at(window); + std::vector<winsys::Window>* windows; + + switch (layer) { + case StackLayer::Desktop: windows = &m_desktop; break; + case StackLayer::Below_: windows = &m_below; break; + case StackLayer::Dock: windows = &m_dock; break; + case StackLayer::Above_: windows = &m_above; break; + case StackLayer::Notification: windows = &m_notification; break; + default: Util::die("no associated layer"); + } + + Util::erase_remove(*windows, window); + m_layers.erase(window); + } + + m_above_other.erase(window); + m_below_other.erase(window); +} + + +void +StackHandler::relayer_window(winsys::Window window, StackLayer layer) +{ + remove_window(window); + add_window(window, layer); +} + +void +StackHandler::raise_window(winsys::Window window) +{ + if (m_layers.count(window) > 0) { + StackLayer layer = m_layers.at(window); + std::vector<winsys::Window>* windows; + + switch (layer) { + case StackLayer::Desktop: windows = &m_desktop; break; + case StackLayer::Below_: windows = &m_below; break; + case StackLayer::Dock: windows = &m_dock; break; + case StackLayer::Above_: windows = &m_above; break; + case StackLayer::Notification: windows = &m_notification; break; + default: Util::die("no associated layer"); + } + + Util::erase_remove(*windows, window); + windows->push_back(window); + } +} + + +void +StackHandler::add_above_other(winsys::Window window, winsys::Window sibling) +{ + if (!(m_above_other.count(window) > 0)) + m_above_other[window] = sibling; +} + +void +StackHandler::add_below_other(winsys::Window window, winsys::Window sibling) +{ + if (!(m_below_other.count(window) > 0)) + m_below_other[window] = sibling; +} + + +std::vector<winsys::Window> const& +StackHandler::get_layer(StackLayer layer) const +{ + switch (layer) { + case StackLayer::Desktop: return m_desktop; break; + case StackLayer::Below_: return m_below; break; + case StackLayer::Dock: return m_dock; break; + case StackLayer::Above_: return m_above; break; + case StackLayer::Notification: return m_notification; break; + default: Util::die("no associated layer"); + } + + return get_layer(StackLayer::Desktop); +} diff --git a/src/core/stack.hh b/src/core/stack.hh @@ -0,0 +1,53 @@ +#ifndef __STACK_H_GUARD__ +#define __STACK_H_GUARD__ + +#include "../winsys/window.hh" + +#include <unordered_map> +#include <vector> + +class StackHandler final +{ +public: + enum class StackLayer + { + Desktop, + Below_, + Dock, + // Regular, + // Free, + // Transient, + Above_, + // Fullscreen, + Notification, + }; + + StackHandler(); + ~StackHandler(); + + void add_window(winsys::Window, StackLayer); + void remove_window(winsys::Window); + + void relayer_window(winsys::Window, StackLayer); + void raise_window(winsys::Window); + + void add_above_other(winsys::Window, winsys::Window); + void add_below_other(winsys::Window, winsys::Window); + + std::vector<winsys::Window> const& get_layer(StackLayer) const; + +private: + std::unordered_map<winsys::Window, StackLayer> m_layers; + + std::vector<winsys::Window> m_desktop; + std::vector<winsys::Window> m_below; + std::vector<winsys::Window> m_dock; + std::vector<winsys::Window> m_above; + std::vector<winsys::Window> m_notification; + + std::unordered_map<winsys::Window, winsys::Window> m_above_other; + std::unordered_map<winsys::Window, winsys::Window> m_below_other; + +}; + +#endif//__STACK_H_GUARD__ diff --git a/src/core/workspace.cc b/src/core/workspace.cc @@ -0,0 +1,575 @@ +#include "../winsys/util.hh" +#include "workspace.hh" +#include "cycle.t.hh" + +#include <algorithm> +#include <optional> + +bool +Buffer::is_occupied() const +{ + return m_client; +} + + +Client_ptr +Buffer::client() const +{ + return m_client; +} + +std::optional<winsys::Grip> +Buffer::grip() const +{ + return m_grip; +} + +std::optional<winsys::Pos> +Buffer::grip_pos() const +{ + return m_grip_pos; +} + +std::optional<winsys::Region> +Buffer::client_region() const +{ + return m_client_region; +} + + +void +Buffer::set_grip_pos(winsys::Pos pos) +{ + m_grip_pos = pos; +} + +void +Buffer::set_client_region(winsys::Region region) +{ + m_client_region = region; +} + +void +Buffer::set(Client_ptr client, winsys::Grip grip, winsys::Pos pos, winsys::Region region) +{ + m_client = client; + m_grip = grip; + m_grip_pos = pos; + m_client_region = region; +} + +void +Buffer::unset() +{ + m_client = nullptr; + m_grip.reset(); + m_grip_pos.reset(); + m_client_region.reset(); +} + + +bool +Workspace::empty() const +{ + return m_clients.empty(); +} + +bool +Workspace::contains(Client_ptr client) const +{ + return m_clients.contains(client); +} + + +bool +Workspace::layout_is_free() const +{ + return m_layout_handler.layout_is_free(); +} + +bool +Workspace::layout_has_margin() const +{ + return m_layout_handler.layout_has_margin(); +} + +bool +Workspace::layout_has_gap() const +{ + return m_layout_handler.layout_has_gap(); +} + +bool +Workspace::layout_is_persistent() const +{ + return m_layout_handler.layout_is_persistent(); +} + +bool +Workspace::layout_is_single() const +{ + return m_layout_handler.layout_is_single(); +} + +bool +Workspace::layout_wraps() const +{ + return m_layout_handler.layout_wraps(); +} + + +std::size_t +Workspace::size() const +{ + return m_clients.size(); +} + +std::size_t +Workspace::length() const +{ + return m_clients.length(); +} + + +Index +Workspace::index() const +{ + return m_index; +} + +std::string const& +Workspace::name() const +{ + return m_name; +} + +Client_ptr +Workspace::active() const +{ + return mp_active; +} + + +std::deque<Client_ptr> const& +Workspace::clients() const +{ + return m_clients.as_deque(); +} + +std::vector<Client_ptr> +Workspace::stack_after_focus() const +{ + std::vector<Client_ptr> stack = m_clients.stack(); + + if (mp_active) { + Util::erase_remove(stack, mp_active); + stack.push_back(mp_active); + } + + return stack; +} + + +Client_ptr +Workspace::next_client() const +{ + std::optional<Client_ptr> client + = m_clients.next_element(winsys::Direction::Forward); + + if (client != mp_active) + return *client; + + return nullptr; +} + +Client_ptr +Workspace::prev_client() const +{ + std::optional<Client_ptr> client + = m_clients.next_element(winsys::Direction::Backward); + + if (client != mp_active) + return *client; + + return nullptr; +} + + +std::optional<Client_ptr> +Workspace::find_client(ClientSelector const& selector) const +{ + if (m_clients.empty()) + return std::nullopt; + + switch (selector.criterium()) { + case ClientSelector::SelectionCriterium::AtFirst: + { + return m_clients[0]; + } + case ClientSelector::SelectionCriterium::AtLast: + { + return m_clients[Util::last_index(m_clients.as_deque())]; + } + case ClientSelector::SelectionCriterium::AtMain: + { + std::size_t main_count = m_layout_handler.main_count(); + + if (main_count <= m_clients.size()) + return m_clients[main_count]; + + break; + } + case ClientSelector::SelectionCriterium::AtIndex: + { + std::size_t index = selector.index(); + + if (index <= m_clients.size()) + return m_clients[index]; + + break; + } + } + + return std::nullopt; +} + + +void +Workspace::cycle(winsys::Direction direction) +{ + switch (direction) { + case winsys::Direction::Forward: + { + if (!layout_wraps() && m_clients.active_index() == m_clients.last_index()) + return; + + break; + } + case winsys::Direction::Backward: + { + if (!layout_wraps() && m_clients.active_index() == 0) + return; + + break; + } + } + + m_clients.cycle_active(direction); + mp_active = m_clients.active_element().value_or(nullptr); +} + +void +Workspace::drag(winsys::Direction direction) +{ + switch (direction) { + case winsys::Direction::Forward: + { + if (!layout_wraps() && m_clients.active_index() == m_clients.last_index()) + return; + + break; + } + case winsys::Direction::Backward: + { + if (!layout_wraps() && m_clients.active_index() == 0) + return; + + break; + } + } + + m_clients.drag_active(direction); + mp_active = m_clients.active_element().value_or(nullptr); +} + +void +Workspace::rotate(winsys::Direction direction) +{ + m_clients.rotate(direction); + mp_active = m_clients.active_element().value_or(nullptr); +} + + +void +Workspace::activate_client(Client_ptr client) +{ + if (m_clients.contains(client)) { + m_clients.activate_element(client); + mp_active = client; + } +} + + +void +Workspace::add_client(Client_ptr client) +{ + if (m_clients.contains(client)) + return; + + m_clients.insert_at_back(client); + mp_active = client; +} + +void +Workspace::remove_client(Client_ptr client) +{ + m_clients.remove_element(client); + mp_active = m_clients.active_element().value_or(nullptr); +} + +void +Workspace::replace_client(Client_ptr client, Client_ptr replacement) +{ + bool was_active + = m_clients.active_element().value_or(nullptr) == client; + + m_clients.replace_element(client, replacement); + + if (was_active) { + m_clients.activate_element(replacement); + mp_active = replacement; + } +} + + +void +Workspace::client_to_icon(Client_ptr client) +{ + if (m_clients.remove_element(client)) + m_icons.insert_at_back(client); + + mp_active = m_clients.active_element().value_or(nullptr); +} + +void +Workspace::icon_to_client(Client_ptr client) +{ + if (m_icons.remove_element(client)) + m_clients.insert_at_back(client); + + mp_active = m_clients.active_element().value_or(nullptr); +} + +void +Workspace::add_icon(Client_ptr client) +{ + if (m_icons.contains(client)) + return; + + m_icons.insert_at_back(client); +} + +void +Workspace::remove_icon(Client_ptr client) +{ + m_icons.remove_element(client); +} + +std::optional<Client_ptr> +Workspace::pop_icon() +{ + return m_icons.empty() + ? std::nullopt + : std::optional(m_icons[m_icons.size() - 1]); +} + + +void +Workspace::client_to_disowned(Client_ptr client) +{ + if (m_clients.remove_element(client)) + m_disowned.insert_at_back(client); + + mp_active = m_clients.active_element().value_or(nullptr); +} + +void +Workspace::disowned_to_client(Client_ptr client) +{ + if (m_disowned.remove_element(client)) + m_clients.insert_at_back(client); + + mp_active = m_clients.active_element().value_or(nullptr); +} + +void +Workspace::add_disowned(Client_ptr client) +{ + if (m_disowned.contains(client)) + return; + + m_disowned.insert_at_back(client); +} + +void +Workspace::remove_disowned(Client_ptr client) +{ + m_disowned.remove_element(client); +} + + +void +Workspace::save_layout(std::size_t number) const +{ + m_layout_handler.save_layout(number); +} + +void +Workspace::load_layout(std::size_t number) +{ + m_layout_handler.load_layout(number); +} + + +void +Workspace::toggle_layout_data() +{ + m_layout_handler.set_prev_layout_data(); +} + +void +Workspace::cycle_layout_data(winsys::Direction direction) +{ + m_layout_handler.cycle_layout_data(direction); +} + +void +Workspace::copy_data_from_prev_layout() +{ + m_layout_handler.copy_data_from_prev_layout(); +} + + +void +Workspace::change_gap_size(Util::Change<int> change) +{ + m_layout_handler.change_gap_size(change); +} + +void +Workspace::change_main_count(Util::Change<int> change) +{ + m_layout_handler.change_main_count(change); +} + +void +Workspace::change_main_factor(Util::Change<float> change) +{ + m_layout_handler.change_main_factor(change); +} + +void +Workspace::change_margin(Util::Change<int> change) +{ + m_layout_handler.change_margin(change); +} + +void +Workspace::change_margin(winsys::Edge edge, Util::Change<int> change) +{ + m_layout_handler.change_margin(edge, change); +} + +void +Workspace::reset_gap_size() +{ + m_layout_handler.reset_gap_size(); +} + +void +Workspace::reset_margin() +{ + m_layout_handler.reset_margin(); +} + +void +Workspace::reset_layout_data() +{ + m_layout_handler.reset_layout_data(); +} + + +void +Workspace::toggle_layout() +{ + m_layout_handler.set_prev_kind(); +} + +void +Workspace::set_layout(LayoutHandler::LayoutKind layout) +{ + m_layout_handler.set_kind(layout); +} + +std::vector<Placement> +Workspace::arrange(winsys::Region region) const +{ + std::deque<Client_ptr> clients = this->clients(); + + std::vector<Placement> placements; + placements.reserve(clients.size()); + + auto fullscreen_iter = std::stable_partition( + clients.begin(), + clients.end(), + [](const Client_ptr client) -> bool { + return client->fullscreen && !client->contained; + } + ); + + auto free_iter = std::stable_partition( + fullscreen_iter, + clients.end(), + [=,this](const Client_ptr client) -> bool { + return !layout_is_free() && Client::is_free(client); + } + ); + + std::transform( + clients.begin(), + fullscreen_iter, + std::back_inserter(placements), + [region](const Client_ptr client) -> Placement { + return Placement { + Placement::PlacementMethod::Tile, + client, + winsys::Decoration::NO_DECORATION, + region + }; + } + ); + + std::transform( + fullscreen_iter, + free_iter, + std::back_inserter(placements), + [](const Client_ptr client) -> Placement { + return Placement { + Placement::PlacementMethod::Free, + client, + winsys::Decoration::FREE_DECORATION, + client->free_region + }; + } + ); + + m_layout_handler.arrange( + region, + placements, + free_iter, + clients.end() + ); + + if (layout_is_single()) { + std::for_each( + placements.begin(), + placements.end(), + [](Placement& placement) { + if (!placement.client->focused) + placement.region = std::nullopt; + } + ); + } + + return placements; +} diff --git a/src/core/workspace.hh b/src/core/workspace.hh @@ -0,0 +1,235 @@ +#ifndef __WORKSPACE_H_GUARD__ +#define __WORKSPACE_H_GUARD__ + +#include "../winsys/geometry.hh" +#include "../winsys/input.hh" +#include "../winsys/window.hh" +#include "../winsys/util.hh" +#include "client.hh" +#include "cycle.hh" +#include "cycle.t.hh" +#include "layout.hh" +#include "placement.hh" + +#include <cstdlib> +#include <deque> +#include <string> +#include <vector> + +class Buffer final +{ +public: + enum class BufferKind + { + Move, Resize + }; + + Buffer(BufferKind kind) + : m_kind(kind), + m_client(nullptr), + m_grip(std::nullopt), + m_grip_pos(std::nullopt), + m_client_region(std::nullopt) + {} + + ~Buffer() {} + + bool is_occupied() const; + + Client_ptr client() const; + std::optional<winsys::Grip> grip() const; + std::optional<winsys::Pos> grip_pos() const; + std::optional<winsys::Region> client_region() const; + + void set_grip_pos(winsys::Pos); + void set_client_region(winsys::Region); + + void set(Client_ptr, winsys::Grip, winsys::Pos, winsys::Region); + void unset(); + +private: + BufferKind m_kind; + Client_ptr m_client; + std::optional<winsys::Grip> m_grip; + std::optional<winsys::Pos> m_grip_pos; + std::optional<winsys::Region> m_client_region; + +}; + +class Scratchpad final +{ +public: + Scratchpad() {} + ~Scratchpad() {} + +private: + +}; + +typedef class Workspace final +{ +public: + struct ClientSelector + { + enum class SelectionCriterium { + AtFirst, + AtLast, + AtMain, + AtIndex + }; + + ClientSelector(const SelectionCriterium criterium) + : m_index(std::nullopt) + { + switch (criterium) { + case SelectionCriterium::AtFirst: m_tag = ClientSelectorTag::AtFirst; return; + case SelectionCriterium::AtLast: m_tag = ClientSelectorTag::AtLast; return; + case SelectionCriterium::AtMain: m_tag = ClientSelectorTag::AtMain; return; + default: return; + } + } + + ClientSelector(const std::size_t index) + : m_tag(ClientSelectorTag::AtIndex), + m_index(index) + {} + + ~ClientSelector() = default; + + SelectionCriterium + criterium() const + { + switch (m_tag) { + case ClientSelectorTag::AtFirst: return SelectionCriterium::AtFirst; + case ClientSelectorTag::AtLast: return SelectionCriterium::AtLast; + case ClientSelectorTag::AtMain: return SelectionCriterium::AtMain; + case ClientSelectorTag::AtIndex: return SelectionCriterium::AtIndex; + default: Util::die("no associated criterium"); + } + + return {}; + } + + std::size_t + index() const + { + return *m_index; + } + + private: + enum class ClientSelectorTag { + AtFirst, + AtLast, + AtMain, + AtIndex + }; + + ClientSelectorTag m_tag; + std::optional<std::size_t> m_index; + + }; + + Workspace(std::size_t index, std::string name) + : m_index(index), + m_name(name), + m_layout_handler({}), + mp_active(nullptr), + m_clients({}, true), + m_icons({}, true), + m_disowned({}, true) + {} + + ~Workspace() {} + + bool empty() const; + bool contains(Client_ptr) const; + + bool layout_is_free() const; + bool layout_has_margin() const; + bool layout_has_gap() const; + bool layout_is_persistent() const; + bool layout_is_single() const; + bool layout_wraps() const; + + std::size_t size() const; + std::size_t length() const; + + Index index() const; + std::string const& name() const; + Client_ptr active() const; + + std::deque<Client_ptr> const& clients() const; + std::vector<Client_ptr> stack_after_focus() const; + + Client_ptr next_client() const; + Client_ptr prev_client() const; + + std::optional<Client_ptr> find_client(ClientSelector const&) const; + + void cycle(winsys::Direction); + void drag(winsys::Direction); + void rotate(winsys::Direction); + + void activate_client(Client_ptr); + + void add_client(Client_ptr); + void remove_client(Client_ptr); + void replace_client(Client_ptr, Client_ptr); + + void client_to_icon(Client_ptr); + void icon_to_client(Client_ptr); + void add_icon(Client_ptr); + void remove_icon(Client_ptr); + std::optional<Client_ptr> pop_icon(); + + void client_to_disowned(Client_ptr); + void disowned_to_client(Client_ptr); + void add_disowned(Client_ptr); + void remove_disowned(Client_ptr); + + void toggle_layout_data(); + void cycle_layout_data(winsys::Direction); + void copy_data_from_prev_layout(); + + void change_gap_size(Util::Change<int>); + void change_main_count(Util::Change<int>); + void change_main_factor(Util::Change<float>); + void change_margin(Util::Change<int>); + void change_margin(winsys::Edge, Util::Change<int>); + void reset_gap_size(); + void reset_margin(); + void reset_layout_data(); + + void save_layout(std::size_t) const; + void load_layout(std::size_t); + + void toggle_layout(); + void set_layout(LayoutHandler::LayoutKind); + std::vector<Placement> arrange(winsys::Region) const; + + std::deque<Client_ptr>::iterator + begin() + { + return m_clients.begin(); + } + + std::deque<Client_ptr>::iterator + end() + { + return m_clients.end(); + } + +private: + std::size_t m_index; + std::string m_name; + + LayoutHandler m_layout_handler; + + Client_ptr mp_active; + Cycle<Client_ptr> m_clients; + Cycle<Client_ptr> m_icons; + Cycle<Client_ptr> m_disowned; + +}* Workspace_ptr; + +#endif//__WORKSPACE_H_GUARD__ diff --git a/src/winsys/common.hh b/src/winsys/common.hh @@ -0,0 +1,23 @@ +#ifndef __WINSYS_COMMON_H_GUARD__ +#define __WINSYS_COMMON_H_GUARD__ + +#include <cstddef> + +typedef std::size_t Ident; +typedef std::size_t Index; + +namespace winsys +{ + + typedef unsigned Pid; + + enum class Toggle + { + On, + Off, + Reverse + }; + +} + +#endif//__WINSYS_COMMON_H_GUARD__ diff --git a/src/winsys/connection.cc b/src/winsys/connection.cc diff --git a/src/winsys/connection.hh b/src/winsys/connection.hh @@ -0,0 +1,114 @@ +#ifndef __WINSYS_CONNECTION_H_GUARD__ +#define __WINSYS_CONNECTION_H_GUARD__ + +#include <vector> +#include <optional> +#include <tuple> +#include <string> + +#include "common.hh" +#include "event.hh" +#include "screen.hh" +#include "window.hh" +#include "hints.hh" + +namespace winsys +{ + + class Connection + { + public: + virtual ~Connection() {}; + + virtual bool flush() = 0; + virtual Event step() = 0; + virtual std::vector<Screen> connected_outputs() = 0; + virtual std::vector<Window> top_level_windows() = 0; + virtual Pos get_pointer_position() = 0; + virtual void warp_pointer_center_of_window_or_root(std::optional<Window>, Screen&) = 0; + virtual void warp_pointer(Pos) = 0; + virtual void warp_pointer_rpos(Window, Pos) = 0; + virtual void confine_pointer(Window) = 0; + virtual bool release_pointer() = 0; + virtual void call_external_command(std::string&) = 0; + virtual void cleanup() = 0; + + // window manipulation + virtual Window create_frame(Region) = 0; + virtual void init_window(Window, bool) = 0; + virtual void init_frame(Window, bool) = 0; + virtual void init_unmanaged(Window) = 0; + virtual void init_move(Window) = 0; + virtual void init_resize(Window) = 0; + virtual void cleanup_window(Window) = 0; + virtual void map_window(Window) = 0; + virtual void unmap_window(Window) = 0; + virtual void reparent_window(Window, Window, Pos) = 0; + virtual void unparent_window(Window, Pos) = 0; + virtual void destroy_window(Window) = 0; + virtual bool close_window(Window) = 0; + virtual bool kill_window(Window) = 0; + virtual void place_window(Window, Region&) = 0; + virtual void move_window(Window, Pos) = 0; + virtual void resize_window(Window, Dim) = 0; + virtual void focus_window(Window) = 0; + virtual void stack_window_above(Window, std::optional<Window>) = 0; + virtual void stack_window_below(Window, std::optional<Window>) = 0; + virtual void insert_window_in_save_set(Window) = 0; + virtual void grab_bindings(std::vector<KeyInput>&, std::vector<MouseInput>&) = 0; + virtual void regrab_buttons(Window) = 0; + virtual void ungrab_buttons(Window) = 0; + virtual void unfocus() = 0; + virtual void set_window_border_width(Window, unsigned) = 0; + virtual void set_window_border_color(Window, unsigned) = 0; + virtual void set_window_background_color(Window, unsigned) = 0; + virtual void update_window_offset(Window, Window) = 0; + virtual Window get_focused_window() = 0; + virtual std::optional<Region> get_window_geometry(Window) = 0; + virtual std::optional<Pid> get_window_pid(Window) = 0; + virtual std::optional<Pid> get_ppid(std::optional<Pid>) = 0; + virtual bool must_manage_window(Window) = 0; + virtual bool must_free_window(Window) = 0; + virtual bool window_is_mappable(Window) = 0; + + // ICCCM + virtual void set_icccm_window_state(Window, IcccmWindowState) = 0; + virtual void set_icccm_window_hints(Window, Hints) = 0; + virtual std::string get_icccm_window_name(Window) = 0; + virtual std::string get_icccm_window_class(Window) = 0; + virtual std::string get_icccm_window_instance(Window) = 0; + virtual std::optional<Window> get_icccm_window_transient_for(Window) = 0; + virtual std::optional<Window> get_icccm_window_client_leader(Window) = 0; + virtual std::optional<Hints> get_icccm_window_hints(Window) = 0; + virtual std::optional<SizeHints> get_icccm_window_size_hints(Window, std::optional<Dim>) = 0; + + // EWMH + virtual void init_for_wm(std::string const&, std::vector<std::string> const&) = 0; + virtual void set_current_desktop(Index) = 0; + virtual void set_root_window_name(std::string const&) = 0; + virtual void set_window_desktop(Window, Index) = 0; + virtual void set_window_state(Window, WindowState, bool) = 0; + virtual void set_window_frame_extents(Window, Extents) = 0; + virtual void set_desktop_geometry(std::vector<Region> const&) = 0; + virtual void set_desktop_viewport(std::vector<Region> const&) = 0; + virtual void set_workarea(std::vector<Region> const&) = 0; + virtual void update_desktops(std::vector<std::string> const&) = 0; + virtual void update_client_list(std::vector<Window> const&) = 0; + virtual void update_client_list_stacking(std::vector<Window> const&) = 0; + virtual std::optional<std::vector<std::optional<Strut>>> get_window_strut(Window) = 0; + virtual std::optional<std::vector<std::optional<Strut>>> get_window_strut_partial(Window) = 0; + virtual std::optional<Index> get_window_desktop(Window) = 0; + virtual WindowType get_window_preferred_type(Window) = 0; + virtual std::vector<WindowType> get_window_types(Window) = 0; + virtual std::optional<WindowState> get_window_preferred_state(Window) = 0; + virtual std::vector<WindowState> get_window_states(Window) = 0; + virtual bool window_is_fullscreen(Window) = 0; + virtual bool window_is_above(Window) = 0; + virtual bool window_is_below(Window) = 0; + virtual bool window_is_sticky(Window) = 0; + + }; + +} + +#endif//__WINSYS_CONNECTION_H_GUARD__ diff --git a/src/winsys/decoration.cc b/src/winsys/decoration.cc @@ -0,0 +1,49 @@ +#include "decoration.hh" + +using namespace winsys; + +const ColorScheme ColorScheme::DEFAULT_COLOR_SCHEME = ColorScheme { + .focused = 0x8181A6, + .fdisowned = 0xc1c1c1, + .fsticky = 0x5F8787, + .unfocused = 0x333333, + .udisowned = 0x999999, + .usticky = 0x444444, + .urgent = 0x87875F +}; + +const Decoration Decoration::NO_DECORATION = Decoration { + std::nullopt, + std::nullopt +}; + +const Decoration Decoration::FREE_DECORATION = Decoration { + std::nullopt, + Frame { + Extents { 3, 1, 1, 1 }, + ColorScheme::DEFAULT_COLOR_SCHEME + } +}; + + +const Extents +Decoration::extents() const +{ + Extents extents = Extents { 0, 0, 0, 0 }; + + if (border) { + extents.left += 1; + extents.right += 1; + extents.top += 1; + extents.bottom += 1; + } + + if (frame) { + extents.left += frame->extents.left; + extents.right += frame->extents.right; + extents.top += frame->extents.top; + extents.bottom += frame->extents.bottom; + } + + return extents; +} diff --git a/src/winsys/decoration.hh b/src/winsys/decoration.hh @@ -0,0 +1,51 @@ +#ifndef __WINSYS_DECORATION_H_GUARD__ +#define __WINSYS_DECORATION_H_GUARD__ + +#include "geometry.hh" + +#include <optional> + +namespace winsys +{ + + typedef unsigned Color; + + struct ColorScheme final + { + static const ColorScheme DEFAULT_COLOR_SCHEME; + + Color focused; + Color fdisowned; + Color fsticky; + Color unfocused; + Color udisowned; + Color usticky; + Color urgent; + }; + + struct Border final + { + unsigned width; + ColorScheme colors; + }; + + struct Frame final + { + Extents extents; + ColorScheme colors; + }; + + struct Decoration final + { + static const Decoration NO_DECORATION; + static const Decoration FREE_DECORATION; + + std::optional<Border> border; + std::optional<Frame> frame; + + const Extents extents() const; + }; + +} + +#endif//__WINSYS_DECORATION_H_GUARD__ diff --git a/src/winsys/event.hh b/src/winsys/event.hh @@ -0,0 +1,184 @@ +#ifndef __WINSYS_EVENT_H_GUARD__ +#define __WINSYS_EVENT_H_GUARD__ + +#include "common.hh" +#include "geometry.hh" +#include "input.hh" +#include "window.hh" + +#include <cstdlib> +#include <optional> +#include <variant> + +#include "spdlog/spdlog.h" + +namespace winsys +{ + + enum class StackMode + { + Above_, + Below_ + }; + + enum class PropertyKind + { + Name, + Class, + Size, + Strut + }; + + struct MouseEvent final + { + MouseCapture capture; + bool on_root; + }; + + struct KeyEvent final + { + KeyCapture capture; + }; + + struct MapRequestEvent final + { + Window window; + bool ignore; + }; + + struct MapEvent final + { + Window window; + bool ignore; + }; + + struct EnterEvent final + { + Window window; + Pos root_rpos; + Pos window_rpos; + }; + + struct LeaveEvent final + { + Window window; + Pos root_rpos; + Pos window_rpos; + }; + + struct DestroyEvent final + { + Window window; + }; + + struct ExposeEvent final + { + Window window; + }; + + struct UnmapEvent final + { + Window window; + bool ignore; + }; + + struct StateRequestEvent final + { + Window window; + WindowState state; + Toggle action; + bool on_root; + }; + + struct FocusRequestEvent final + { + Window window; + bool on_root; + }; + + struct CloseRequestEvent final + { + Window window; + bool on_root; + }; + + struct WorkspaceRequestEvent final + { + std::optional<Window> window; + std::size_t index; + bool on_root; + }; + + struct PlacementRequestEvent final + { + Window window; + std::optional<Pos> pos; + std::optional<Dim> dim; + bool on_root; + }; + + struct GripRequestEvent final + { + Window window; + Pos pos; + std::optional<Grip> grip; + bool on_root; + }; + + struct RestackRequestEvent final + { + Window window; + Window sibling; + StackMode mode; + bool on_root; + }; + + struct ConfigureEvent final + { + Window window; + Region region; + bool on_root; + }; + + struct PropertyEvent final + { + Window window; + PropertyKind kind; + bool on_root; + }; + + struct FrameExtentsRequestEvent final + { + Window window; + bool on_root; + }; + + struct ScreenChangeEvent final {}; + + typedef std::variant< + std::monostate, + MouseEvent, + KeyEvent, + MapRequestEvent, + MapEvent, + EnterEvent, + LeaveEvent, + DestroyEvent, + ExposeEvent, + UnmapEvent, + StateRequestEvent, + FocusRequestEvent, + CloseRequestEvent, + WorkspaceRequestEvent, + PlacementRequestEvent, + GripRequestEvent, + RestackRequestEvent, + ConfigureEvent, + PropertyEvent, + FrameExtentsRequestEvent, + ScreenChangeEvent + > Event; + +} + +#endif//__WINSYS_EVENT_H_GUARD__ diff --git a/src/winsys/geometry.cc b/src/winsys/geometry.cc @@ -0,0 +1,30 @@ +#include "geometry.hh" + +#include <algorithm> + +using namespace winsys; + +void +Region::apply_minimum_dim(const Dim& dim) +{ + this->dim.w = std::max(this->dim.w, dim.w); + this->dim.h = std::max(this->dim.h, dim.h); +} + +void +Region::apply_extents(const Extents& extents) +{ + pos.x -= extents.left; + pos.y -= extents.top; + dim.w += extents.left + extents.right; + dim.h += extents.top + extents.bottom; +} + +void +Region::remove_extents(const Extents& extents) +{ + pos.x += extents.left; + pos.y += extents.top; + dim.w -= extents.left + extents.right; + dim.h -= extents.top + extents.bottom; +} diff --git a/src/winsys/geometry.hh b/src/winsys/geometry.hh @@ -0,0 +1,117 @@ +#ifndef __WINSYS_GEOMETRY_H_GUARD__ +#define __WINSYS_GEOMETRY_H_GUARD__ + +#include "common.hh" +#include "window.hh" + +namespace winsys +{ + + enum class Edge + { + Left, + Right, + Top, + Bottom + }; + + enum class Corner + { + TopLeft, + TopRight, + BottomLeft, + BottomRight + }; + + enum class Direction + { + Forward, + Backward + }; + + struct Dim final + { + int w; + int h; + }; + + inline bool + operator==(Dim& lhs, Dim& rhs) + { + return lhs.w == rhs.w && lhs.h == rhs.h; + } + + struct Pos final + { + int x; + int y; + + static Pos + from_center_of_dim(Dim dim) + { + return Pos { + static_cast<int>(dim.w / 2.f), + static_cast<int>(dim.h / 2.f) + }; + } + + static bool + is_at_origin(Pos& pos) + { + return pos.x == 0 && pos.y == 0; + } + }; + + inline bool + operator==(Pos& lhs, Pos& rhs) + { + return lhs.x == rhs.x && lhs.y == rhs.y; + } + + struct Padding final + { + int left; + int right; + int top; + int bottom; + }; + + typedef Padding Extents; + + struct Region final + { + Pos pos; + Dim dim; + + void apply_minimum_dim(const Dim&); + void apply_extents(const Extents&); + void remove_extents(const Extents&); + }; + + inline bool + operator==(Region& lhs, Region& rhs) + { + return lhs.pos == rhs.pos && lhs.dim == rhs.dim; + } + + struct Distance final + { + int dx; + int dy; + }; + + struct Ratio final + { + int numerator; + int denominator; + }; + + struct Strut final + { + Window window; + int width; + }; + +} + +#endif//__WINSYS_GEOMETRY_H_GUARD__ diff --git a/src/winsys/hints.cc b/src/winsys/hints.cc @@ -0,0 +1,80 @@ +#include "hints.hh" + +#include <algorithm> +#include <cmath> + +using namespace winsys; + +void +SizeHints::apply(Dim& dim) +{ + int dest_width = dim.w; + int dest_height = dim.h; + + if (min_width) + dest_width = std::max(dest_width, *min_width); + + if (min_height) + dest_height = std::max(dest_height, *min_height); + + if (max_width) + dest_width = std::min(dest_width, *max_width); + + if (max_height) + dest_height = std::min(dest_height, *max_height); + + int base_width = this->base_width + ? *this->base_width + : 0; + + int base_height = this->base_height + ? *this->base_height + : 0; + + int width = base_width < dest_width + ? dest_width - base_width + : dest_width; + + int height = base_height < dest_height + ? dest_height - base_height + : dest_height; + + if (min_ratio || max_ratio) { + if (height == 0) + height = 1; + + double current_ratio + = static_cast<double>(width) / static_cast<double>(height); + + std::optional<double> new_ratio = std::nullopt; + + if (min_ratio && current_ratio < *min_ratio) + new_ratio = min_ratio; + + if (!new_ratio && max_ratio && current_ratio > *max_ratio) + new_ratio = max_ratio; + + if (new_ratio) { + height = std::round(static_cast<double>(width) / *new_ratio); + width = std::round(static_cast<double>(height) * *new_ratio); + + dest_width = width + base_width; + dest_height = height + base_height; + } + } + + if (inc_height && dest_height >= base_height) { + dest_height -= base_height; + dest_height -= dest_height % *inc_height; + dest_height += base_height; + } + + if (inc_width && dest_width >= base_width) { + dest_width -= base_width; + dest_width -= dest_width % *inc_width; + dest_width += base_width; + } + + dim.h = std::max(dest_height, 0); + dim.w = std::max(dest_width, 0); +} diff --git a/src/winsys/hints.hh b/src/winsys/hints.hh @@ -0,0 +1,42 @@ +#ifndef __WINSYS_HINTS_H_GUARD__ +#define __WINSYS_HINTS_H_GUARD__ + +#include "geometry.hh" +#include "window.hh" + +#include <optional> + +namespace winsys +{ + + struct SizeHints final + { + bool by_user; + std::optional<Pos> pos; + std::optional<int> min_width; + std::optional<int> min_height; + std::optional<int> max_width; + std::optional<int> max_height; + std::optional<int> base_width; + std::optional<int> base_height; + std::optional<int> inc_width; + std::optional<int> inc_height; + std::optional<double> min_ratio; + std::optional<double> max_ratio; + std::optional<Ratio> min_ratio_vulgar; + std::optional<Ratio> max_ratio_vulgar; + + void apply(Dim&); + }; + + struct Hints final + { + bool urgent; + std::optional<bool> input; + std::optional<IcccmWindowState> initial_state; + std::optional<Window> group; + }; + +} + +#endif//__WINSYS_HINTS_H_GUARD__ diff --git a/src/winsys/input.hh b/src/winsys/input.hh @@ -0,0 +1,394 @@ +#ifndef __WINSYS_INPUT_H_GUARD__ +#define __WINSYS_INPUT_H_GUARD__ + +#include "common.hh" +#include "window.hh" +#include "geometry.hh" + +#include <unordered_set> +#include <optional> +#include <numeric> + +namespace winsys +{ + + enum class Key + { + Any, + BackSpace, + Tab, + Clear, + Return, + Shift, + Control, + Alt, + Super, + Menu, + Pause, + CapsLock, + Escape, + Space, + ExclamationMark, + QuotationMark, + QuestionMark, + NumberSign, + DollarSign, + PercentSign, + AtSign, + Ampersand, + Apostrophe, + LeftParenthesis, + RightParenthesis, + LeftBracket, + RightBracket, + LeftBrace, + RightBrace, + Underscore, + Grave, + Bar, + Tilde, + QuoteLeft, + Asterisk, + Plus, + Comma, + Minus, + Period, + Slash, + BackSlash, + Colon, + SemiColon, + Less, + Equal, + Greater, + PageUp, + PageDown, + End, + Home, + Left, + Up, + Right, + Down, + Select, + Print, + Execute, + PrintScreen, + Insert, + Delete, + Help, + Zero, + One, + Two, + Three, + Four, + Five, + Six, + Seven, + Eight, + Nine, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + NumPad0, + NumPad1, + NumPad2, + NumPad3, + NumPad4, + NumPad5, + NumPad6, + NumPad7, + NumPad8, + NumPad9, + Multiply, + Add, + Seperator, + Subtract, + Decimal, + Divide, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + Numlock, + ScrollLock, + LeftShift, + RightShift, + LeftControl, + RightContol, + LeftAlt, + RightAlt, + LeftSuper, + RightSuper, + BrowserBack, + BrowserForward, + BrowserRefresh, + BrowserStop, + BrowserSearch, + BrowserFavorites, + BrowserHome, + VolumeMute, + VolumeDown, + VolumeUp, + MicMute, + NextTrack, + PreviousTrack, + StopMedia, + PlayPause, + BrightnessDown, + BrightnessUp, + KeyboardBrightnessDown, + KeyboardBrightnessUp, + LaunchMail, + SelectMedia, + LaunchAppA, + LaunchAppB, + LaunchAppC, + LaunchAppD, + LaunchAppE, + LaunchAppF, + LaunchApp0, + LaunchApp1, + LaunchApp2, + LaunchApp3, + LaunchApp4, + LaunchApp5, + LaunchApp6, + LaunchApp7, + LaunchApp8, + LaunchApp9 + }; + + enum class Button + { + Left, + Middle, + Right, + ScrollUp, + ScrollDown, + Backward, + Forward + }; + + enum Modifier + { + Ctrl = 1 << 0, + Shift = 1 << 1, + Alt = 1 << 2, + AltGr = 1 << 3, + Super = 1 << 4, + NumLock = 1 << 5, + ScrollLock = 1 << 6, +#ifndef DEBUG + Main = Super, + Sec = Alt +#else + Main = Alt, + Sec = Super +#endif + }; + + inline Modifier + operator|(Modifier lhs, Modifier rhs) + { + return static_cast<Modifier>( + static_cast<std::size_t>(lhs) + | static_cast<std::size_t>(rhs) + ); + } + + struct KeyInput final + { + Key key; + std::unordered_set<Modifier> modifiers; + }; + + struct KeyCapture final + { + KeyInput input; + std::optional<Window> window; + }; + + struct MouseInput final + { + enum class MouseInputTarget + { + Global, + Root, + Client + }; + + MouseInputTarget target; + Button button; + std::unordered_set<Modifier> modifiers; + }; + + struct MouseCapture final + { + enum class MouseCaptureKind + { + Press, + Release, + Motion + }; + + MouseCaptureKind kind; + MouseInput input; + std::optional<Window> window; + Pos root_rpos; + }; + + enum Grip + { + Left = 1 << 0, + Right = 1 << 1, + Top = 1 << 2, + Bottom = 1 << 3, + }; + + inline Grip + operator|(Grip lhs, Grip rhs) + { + return static_cast<Grip>( + static_cast<std::size_t>(lhs) + | static_cast<std::size_t>(rhs) + ); + } + + inline Grip& + operator|=(Grip& lhs, Grip rhs) + { + lhs = static_cast<Grip>( + static_cast<std::size_t>(lhs) + | static_cast<std::size_t>(rhs) + ); + + return lhs; + } + + inline Grip + operator&(Grip lhs, Grip rhs) + { + return static_cast<Grip>( + static_cast<std::size_t>(lhs) + & static_cast<std::size_t>(rhs) + ); + } + + inline Grip& + operator&=(Grip& lhs, Grip rhs) + { + lhs = static_cast<Grip>( + static_cast<std::size_t>(lhs) + & static_cast<std::size_t>(rhs) + ); + + return lhs; + } + + inline bool + operator==(KeyInput const& lhs, KeyInput const& rhs) + { + return lhs.key == rhs.key + && lhs.modifiers == rhs.modifiers; + } + + inline bool + operator==(MouseInput const& lhs, MouseInput const& rhs) + { + return lhs.target == rhs.target + && lhs.button == rhs.button + && lhs.modifiers == rhs.modifiers; + } + +} + +namespace std +{ + template <> + struct hash<winsys::KeyInput> + { + std::size_t + operator()(winsys::KeyInput const& input) const + { + std::size_t key_hash = std::hash<winsys::Key>()(input.key); + std::size_t modifiers_hash = std::hash<std::size_t>()( + std::accumulate( + input.modifiers.begin(), + input.modifiers.end(), + static_cast<winsys::Modifier>(0), + std::bit_or<winsys::Modifier>() + ) + ); + + return key_hash ^ modifiers_hash; + } + }; +} + +namespace std +{ + template <> + struct hash<winsys::MouseInput> + { + std::size_t + operator()(winsys::MouseInput const& input) const + { + std::size_t target_hash = std::hash<winsys::MouseInput::MouseInputTarget>()(input.target); + std::size_t button_hash = std::hash<winsys::Button>()(input.button); + std::size_t modifiers_hash = std::hash<std::size_t>()( + std::accumulate( + input.modifiers.begin(), + input.modifiers.end(), + static_cast<winsys::Modifier>(0), + std::bit_or<winsys::Modifier>() + ) + ); + + return target_hash ^ button_hash ^ modifiers_hash; + } + }; +} + +#endif//__WINSYS_INPUT_H_GUARD__ diff --git a/src/winsys/screen.cc b/src/winsys/screen.cc @@ -0,0 +1,141 @@ +#include "screen.hh" +#include "util.hh" + +using namespace winsys; + +std::vector<Window> +Screen::show_and_get_struts(bool show) +{ + m_showing_struts = show; + compute_placeable_region(); + + std::vector<Window> windows; + windows.reserve(m_windows.size()); + + std::transform( + m_windows.begin(), + m_windows.end(), + std::back_inserter(windows), + [](auto kv) -> Window { + return kv.first; + } + ); + + return windows; +} + +void +Screen::add_struts(std::vector<std::optional<Strut>> struts) +{ + if (struts[0]) { + add_strut(Edge::Left, *struts[0]); + } + + if (struts[1]) { + add_strut(Edge::Top, *struts[1]); + } + + if (struts[2]) { + add_strut(Edge::Right, *struts[2]); + } + + if (struts[3]) { + add_strut(Edge::Bottom, *struts[3]); + } +} + +void +Screen::add_strut(Edge edge, Strut strut) +{ + m_struts.at(edge).insert(strut); + + if (m_windows.count(strut.window) > 0) { + std::vector<Edge>& edges = m_windows.at(strut.window); + edges.push_back(edge); + } else + m_windows[strut.window] = { edge }; +} + +void +Screen::add_strut(Edge edge, Window window, int width) +{ + m_struts.at(edge).insert(Strut { + window, + width + }); + + if (m_windows.count(window) > 0) { + std::vector<Edge>& edges = m_windows.at(window); + edges.push_back(edge); + } else + m_windows[window] = { edge }; +} + +void +Screen::update_strut(Edge edge, Window window, int width) +{ + remove_strut(window); + add_strut(edge, window, width); +} + +void +Screen::remove_strut(Window window) +{ + std::optional<std::vector<Edge>> const& edges + = Util::retrieve(m_windows, window); + + if (edges) + for (auto& edge : *edges) { + std::set<Strut, StrutComparer>& struts = m_struts.at(edge); + + auto iter = std::find_if( + struts.begin(), + struts.end(), + [window](Strut const& strut) -> bool { + return strut.window == window; + } + ); + + if (iter != struts.end()) + struts.erase(iter); + } + + m_windows.erase(window); +} + +void +Screen::compute_placeable_region() +{ + Region region = m_full_region; + + if (m_showing_struts) { + std::set<Strut, StrutComparer>& left = m_struts.at(Edge::Left); + std::set<Strut, StrutComparer>& top = m_struts.at(Edge::Top); + std::set<Strut, StrutComparer>& right = m_struts.at(Edge::Right); + std::set<Strut, StrutComparer>& bottom = m_struts.at(Edge::Bottom); + + if (!left.empty()) { + int width = left.rbegin()->width; + region.pos.x += width; + region.dim.w -= width; + } + + if (!top.empty()) { + int width = top.rbegin()->width; + region.pos.y += width; + region.dim.h -= width; + } + + if (!right.empty()) { + int width = right.rbegin()->width; + region.dim.w -= width; + } + + if (!bottom.empty()) { + int width = bottom.rbegin()->width; + region.dim.h -= width; + } + } + + m_placeable_region = region; +} diff --git a/src/winsys/screen.hh b/src/winsys/screen.hh @@ -0,0 +1,109 @@ +#ifndef __WINSYS_SCREEN_H_GUARD__ +#define __WINSYS_SCREEN_H_GUARD__ + +#include "common.hh" +#include "geometry.hh" +#include "window.hh" + +#include <set> +#include <unordered_map> +#include <vector> + +namespace winsys +{ + + class Screen final + { + public: + Screen(Index index, Region region) + : m_index(index), + m_full_region(region), + m_placeable_region(region), + m_windows({}), + m_struts({ + { Edge::Left, {{}, s_strut_comparer} }, + { Edge::Top, {{}, s_strut_comparer} }, + { Edge::Right, {{}, s_strut_comparer} }, + { Edge::Bottom, {{}, s_strut_comparer} }, + }), + m_showing_struts(true) + {} + + Region + full_region() const + { + return m_full_region; + } + + Region + placeable_region() const + { + return m_placeable_region; + } + + bool + showing_struts() const + { + return m_showing_struts; + } + + bool + contains_strut(Window window) const + { + return m_windows.count(window) > 0; + } + + Index + index() const + { + return m_index; + } + + void + set_index(Index index) + { + m_index = index; + } + + std::optional<int> + max_strut_at_edge(winsys::Edge edge) + { + return m_struts.at(edge).empty() + ? std::nullopt + : std::optional(m_struts[edge].rbegin()->width); + } + + std::vector<Window> show_and_get_struts(bool); + + void add_struts(std::vector<std::optional<Strut>>); + void add_strut(Edge, Strut); + void add_strut(Edge, Window, int); + void update_strut(Edge, Window, int); + void remove_strut(Window); + + void compute_placeable_region(); + + private: + Index m_index; + + Region m_full_region; + Region m_placeable_region; + + static constexpr struct StrutComparer final { + bool + operator()(const Strut& lhs, const Strut& rhs) const + { + return lhs.width < rhs.width; + } + } s_strut_comparer = {}; + + std::unordered_map<Window, std::vector<Edge>> m_windows; + std::unordered_map<Edge, std::set<Strut, StrutComparer>> m_struts; + + bool m_showing_struts; + + }; + +} + +#endif//__WINSYS_SCREEN_H_GUARD__ diff --git a/src/winsys/util.cc b/src/winsys/util.cc @@ -0,0 +1,16 @@ +#include "util.hh" + +#include <unistd.h> + +void +Util::die(const std::string&& msg) +{ + std::cerr << msg << std::endl; + exit(1); +} + +void +Util::warn(const std::string&& msg) +{ + std::cerr << msg << std::endl; +} diff --git a/src/winsys/util.hh b/src/winsys/util.hh @@ -0,0 +1,140 @@ +#ifndef __WINSYS_UTIL_H_GUARD__ +#define __WINSYS_UTIL_H_GUARD__ + +#include <cstdlib> +#include <iostream> +#include <optional> +#include <string> +#include <type_traits> +#include <unordered_map> + +namespace Util +{ + + void die(const std::string&&); + void warn(const std::string&&); + + template < + typename T, + typename = typename std::enable_if<std::is_arithmetic<T>::value, T>::type + > + struct Change final + { + Change(T value) + : value(value) + {} + + operator T() const { return value; } + + T value; + }; + + template<typename T> + struct is_iterable final + { + private: + template<typename Container> static char test(typename Container::iterator*); + template<typename Container> static int test(...); + public: + enum { value = sizeof(test<T>(0)) == sizeof(char) }; + }; + + template <typename Container> + typename std::enable_if<is_iterable<Container>::value, void>::type + erase_remove(Container& c, typename Container::value_type const& t) + { + auto iter = std::remove(c.begin(), c.end(), t); + + if (iter == c.end()) + return; + + c.erase(iter, c.end()); + } + + template <typename Container, typename UnaryPredicate> + typename std::enable_if<is_iterable<Container>::value, void>::type + erase_remove_if(Container& c, UnaryPredicate p) + { + auto iter = std::remove_if(c.begin(), c.end(), p); + + if (iter == c.end()) + return; + + c.erase(iter, c.end()); + } + + template <typename Container> + typename std::enable_if<is_iterable<Container>::value, void>::type + erase_at_index(Container& c, const std::size_t index) + { + if (index < c.size()) + c.erase(c.begin() + index); + } + + template <typename Container> + typename std::enable_if<is_iterable<Container>::value, void>::type + append(Container& c1, Container const& c2) + { + c1.reserve(c1.size() + c2.size()); + c1.insert(c1.end(), c2.begin(), c2.end()); + } + + template <typename Container> + typename std::enable_if<is_iterable<Container>::value, const bool>::type + contains(Container const& c, typename Container::value_type const& t) + { + return std::find(c.begin(), c.end(), t) != c.end(); + } + + template <typename K, typename V> + std::optional<V> + retrieve(std::unordered_map<K, V>& c, K& key) + { + typename std::unordered_map<K, V>::iterator iter = c.find(key); + + if (iter == c.end()) + return std::nullopt; + + return iter->second; + } + + template <typename K, typename V> + V& + at(std::unordered_map<K, V>& c, K& key) + { + return c.at(key); + } + + template <typename K, typename V> + std::optional<const V> + const_retrieve(std::unordered_map<K, V> const& c, K const& key) + { + typename std::unordered_map<K, V>::const_iterator iter = c.find(key); + + if (iter == c.end()) + return std::nullopt; + + return iter->second; + } + + template <typename Container> + typename std::enable_if<is_iterable<Container>::value, const std::optional<const std::size_t>>::type + index_of(Container const& c, typename Container::value_type const& t) + { + auto iter = std::find(c.begin(), c.end(), t); + if (iter != c.end()) + return iter - c.begin(); + + return std::nullopt; + } + + template <typename Container> + typename std::enable_if<is_iterable<Container>::value, const std::size_t>::type + last_index(Container const& c) + { + return c.empty() ? 0 : c.size() - 1; + } + +} + +#endif//__WINSYS_UTIL_H_GUARD__ diff --git a/src/winsys/window.hh b/src/winsys/window.hh @@ -0,0 +1,54 @@ +#ifndef __WINSYS_WINDOW_H_GUARD__ +#define __WINSYS_WINDOW_H_GUARD__ + +#include <cstdlib> + +namespace winsys +{ + + typedef std::size_t Window; + + enum class IcccmWindowState + { + Withdrawn, + Normal, + Iconic + }; + + enum class WindowState + { + Modal, + Sticky, + MaximizedVert, + MaximizedHorz, + Shaded, + SkipTaskbar, + SkipPager, + Hidden, + Fullscreen, + Above_, + Below_, + DemandsAttention + }; + + enum class WindowType + { + Desktop, + Dock, + Toolbar, + Menu, + Utility, + Splash, + Dialog, + DropdownMenu, + PopupMenu, + Tooltip, + Notification, + Combo, + Dnd, + Normal + }; + +} + +#endif//__WINSYS_WINDOW_H_GUARD__ diff --git a/src/winsys/xdata/xconnection.cc b/src/winsys/xdata/xconnection.cc @@ -0,0 +1,3395 @@ +#include "../common.hh" +#include "../util.hh" +#include "xconnection.hh" + +#include <cstring> +#include <iterator> +#include <numeric> +#include <proc/readproc.h> +#include <sstream> +#include <unistd.h> + +extern "C" { +#include <X11/XF86keysym.h> +#include <X11/Xatom.h> +#include <X11/Xproto.h> +#include <X11/Xutil.h> +#include <X11/extensions/XRes.h> +#include <X11/extensions/Xrandr.h> +#include <X11/keysym.h> +#include <X11/keysymdef.h> +#include <xkbcommon/xkbcommon-keysyms.h> +} + +#include <xcb/xcb.h> +#include <xcb/xproto.h> + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" + +XConnection::XConnection() + : mp_dpy(XOpenDisplay(NULL)), + m_conn_number(XConnectionNumber(mp_dpy)), + m_root(XDefaultRootWindow(mp_dpy)), + m_check_window(XCreateWindow( + mp_dpy, + m_root, + -1, -1, + 1, 1, + CopyFromParent, 0, InputOnly, + CopyFromParent, 0, NULL + )), + m_conn(XConnectionNumber(mp_dpy)), + m_interned_atoms({}), + m_atom_names({}), + m_keys({}), + m_keycodes({}), + m_netwm_atoms({}) +{ + static const std::unordered_map<NetWMID, const char*> NETWM_ATOM_NAMES({ + { NetWMID::NetSupported, "_NET_SUPPORTED" }, + { NetWMID::NetClientList, "_NET_CLIENT_LIST" }, + { NetWMID::NetNumberOfDesktops, "_NET_NUMBER_OF_DESKTOPS" }, + { NetWMID::NetCurrentDesktop, "_NET_CURRENT_DESKTOP" }, + { NetWMID::NetDesktopNames, "_NET_DESKTOP_NAMES" }, + { NetWMID::NetDesktopGeometry, "_NET_DESKTOP_GEOMETRY" }, + { NetWMID::NetDesktopViewport, "_NET_DESKTOP_VIEWPORT" }, + { NetWMID::NetWorkarea, "_NET_WORKAREA" }, + { NetWMID::NetActiveWindow, "_NET_ACTIVE_WINDOW" }, + { NetWMID::NetWMName, "_NET_WM_NAME" }, + { NetWMID::NetWMDesktop, "_NET_WM_DESKTOP" }, + { NetWMID::NetWMStrut, "_NET_WM_STRUT" }, + { NetWMID::NetWMStrutPartial, "_NET_WM_STRUT_PARTIAL" }, + { NetWMID::NetWMFrameExtents, "_NET_WM_FRAME_EXTENTS" }, + { NetWMID::NetSupportingWMCheck, "_NET_SUPPORTING_WM_CHECK" }, + { NetWMID::NetWMState, "_NET_WM_STATE" }, + { NetWMID::NetWMWindowType, "_NET_WM_WINDOW_TYPE" }, + // root messages + { NetWMID::NetWMCloseWindow, "_NET_CLOSE_WINDOW" }, + { NetWMID::NetWMMoveResize, "_NET_WM_MOVERESIZE" }, + { NetWMID::NetRequestFrameExtents, "_NET_REQUEST_FRAME_EXTENTS" }, + { NetWMID::NetMoveResizeWindow, "_NET_MOVERESIZE_WINDOW" }, + // window states + { NetWMID::NetWMStateFullscreen, "_NET_WM_STATE_FULLSCREEN" }, + { NetWMID::NetWMStateAbove, "_NET_WM_STATE_ABOVE" }, + { NetWMID::NetWMStateBelow, "_NET_WM_STATE_BELOW" }, + { NetWMID::NetWMStateDemandsAttention, "_NET_WM_STATE_DEMANDS_ATTENTION" }, + { NetWMID::NetWMStateHidden, "_NET_WM_STATE_HIDDEN" }, + // window types + { NetWMID::NetWMWindowTypeDesktop, "_NET_WM_WINDOW_TYPE_DESKTOP" }, + { NetWMID::NetWMWindowTypeDock, "_NET_WM_WINDOW_TYPE_DOCK" }, + { NetWMID::NetWMWindowTypeToolbar, "_NET_WM_WINDOW_TYPE_TOOLBAR" }, + { NetWMID::NetWMWindowTypeMenu, "_NET_WM_WINDOW_TYPE_MENU" }, + { NetWMID::NetWMWindowTypeUtility, "_NET_WM_WINDOW_TYPE_UTILITY" }, + { NetWMID::NetWMWindowTypeSplash, "_NET_WM_WINDOW_TYPE_SPLASH" }, + { NetWMID::NetWMWindowTypeDialog, "_NET_WM_WINDOW_TYPE_DIALOG" }, + { NetWMID::NetWMWindowTypeDropdownMenu, "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU" }, + { NetWMID::NetWMWindowTypePopupMenu, "_NET_WM_WINDOW_TYPE_POPUP_MENU" }, + { NetWMID::NetWMWindowTypeTooltip, "_NET_WM_WINDOW_TYPE_TOOLTIP" }, + { NetWMID::NetWMWindowTypeNotification, "_NET_WM_WINDOW_TYPE_NOTIFICATION" }, + { NetWMID::NetWMWindowTypeNormal, "_NET_WM_WINDOW_TYPE_NORMAL" }, + }); + + for (auto&& [id,name] : NETWM_ATOM_NAMES) + m_netwm_atoms[id] = get_atom(name); + + for (std::size_t i = 0; i < 256; ++i) + m_event_dispatcher[i] = &XConnection::on_unimplemented; + + m_event_dispatcher[ButtonPress] = &XConnection::on_button_press; + m_event_dispatcher[ButtonRelease] = &XConnection::on_button_release; + m_event_dispatcher[CirculateRequest] = &XConnection::on_circulate_request; + m_event_dispatcher[ClientMessage] = &XConnection::on_client_message; + m_event_dispatcher[ConfigureNotify] = &XConnection::on_configure_notify; + m_event_dispatcher[ConfigureRequest] = &XConnection::on_configure_request; + m_event_dispatcher[DestroyNotify] = &XConnection::on_destroy_notify; + m_event_dispatcher[Expose] = &XConnection::on_expose; + m_event_dispatcher[FocusIn] = &XConnection::on_focus_in; + m_event_dispatcher[KeyPress] = &XConnection::on_key_press; + m_event_dispatcher[MapNotify] = &XConnection::on_map_notify; + m_event_dispatcher[MapRequest] = &XConnection::on_map_request; + m_event_dispatcher[MappingNotify] = &XConnection::on_mapping_notify; + m_event_dispatcher[MotionNotify] = &XConnection::on_motion_notify; + m_event_dispatcher[PropertyNotify] = &XConnection::on_property_notify; + m_event_dispatcher[UnmapNotify] = &XConnection::on_unmap_notify; + + int event_base, _error_base; + if (XRRQueryExtension(mp_dpy, &event_base, &_error_base)) { + m_event_dispatcher[event_base + RRScreenChangeNotify] + = &XConnection::on_screen_change; + } +} + +XConnection::~XConnection() +{} + + +bool +XConnection::flush() +{ + XFlush(mp_dpy); + return true; +} + +winsys::Event +XConnection::step() +{ + next_event(m_current_event); + + if (m_current_event.type >= 0 && m_current_event.type <= 256) + return (this->*(m_event_dispatcher[m_current_event.type]))(); + + return std::monostate{}; +} + +std::vector<winsys::Screen> +XConnection::connected_outputs() +{ + XRRScreenResources* screen_resources = XRRGetScreenResources(mp_dpy, m_root); + + if (!screen_resources) + return {}; + + XRRCrtcInfo* crtc_info = NULL; + std::vector<winsys::Screen> screens = {}; + + for (int i = 0; i < screen_resources->ncrtc; ++i) { + crtc_info = XRRGetCrtcInfo( + mp_dpy, + screen_resources, + screen_resources->crtcs[i] + ); + + if (crtc_info->width > 0 && crtc_info->height > 0) { + winsys::Region region = winsys::Region { + winsys::Pos { + crtc_info->x, + crtc_info->y + }, + winsys::Dim { + static_cast<int>(crtc_info->width), + static_cast<int>(crtc_info->height) + } + }; + + screens.emplace_back(winsys::Screen(i, region)); + } + } + + return screens; +} + +std::vector<winsys::Window> +XConnection::top_level_windows() +{ + Window _w; + Window* children; + unsigned nchildren; + + XQueryTree(mp_dpy, m_root, &_w, &_w, &children, &nchildren); + + std::vector<winsys::Window> windows; + for (std::size_t i = 0; i < nchildren; ++i) + if (m_root != children[i]) + windows.push_back(children[i]); + + if (children) + XFree(children); + + return windows; +} + +winsys::Pos +XConnection::get_pointer_position() +{ + winsys::Window _r, _c; + int rx, ry, _wx, _wy; + unsigned _m; + + XQueryPointer( + mp_dpy, + m_root, + &_r, &_c, + &rx, &ry, + &_wx, &_wy, + &_m + ); + + return winsys::Pos { rx, ry }; +} + +void +XConnection::warp_pointer_center_of_window_or_root(std::optional<winsys::Window> window, winsys::Screen& screen) +{ + winsys::Pos pos; + + if (window) { + XWindowAttributes wa; + XGetWindowAttributes(mp_dpy, *window, &wa); + + pos = winsys::Pos::from_center_of_dim( + winsys::Dim { + wa.width, + wa.height + } + ); + } else + pos = winsys::Pos::from_center_of_dim( + screen.placeable_region().dim + ); + + XWarpPointer( + mp_dpy, + None, + window.value_or(m_root), + 0, 0, 0, 0, + pos.x, pos.y + ); +} + +void +XConnection::warp_pointer(winsys::Pos pos) +{ + XWarpPointer( + mp_dpy, + None, + m_root, + 0, 0, 0, 0, + pos.x, pos.y + ); +} + +void +XConnection::warp_pointer_rpos(winsys::Window window, winsys::Pos pos) +{ + XWarpPointer( + mp_dpy, + None, + window, + 0, 0, 0, 0, + pos.x, pos.y + ); +} + +void +XConnection::confine_pointer(winsys::Window window) +{ + if (!m_confined_to) { + int status = XGrabPointer( + mp_dpy, + m_root, + False, + PointerMotionMask | ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, + m_root, + None, + CurrentTime + ); + + if (status == 0) { + XGrabKeyboard( + mp_dpy, + m_root, + False, + GrabModeAsync, GrabModeAsync, + CurrentTime + ); + + m_confined_to = window; + } + } +} + +bool +XConnection::release_pointer() +{ + if (m_confined_to) { + XUngrabPointer(mp_dpy, CurrentTime); + XUngrabKeyboard(mp_dpy, CurrentTime); + + m_confined_to = std::nullopt; + return true; + } + + return false; +} + +void +XConnection::call_external_command(std::string& command) +{ + if (!fork()) { + if (mp_dpy) + close(m_conn_number); + + setsid(); + execl("/bin/sh", "/bin/sh", "-c", ("exec " + command).c_str(), NULL); + exit(EXIT_SUCCESS); + } +} + +void +XConnection::cleanup() +{ + XUngrabKey(mp_dpy, AnyKey, AnyModifier, m_root); + XDestroyWindow(mp_dpy, m_check_window); + + unset_card_property(m_root, "_NET_ACTIVE_WINDOW"); + unset_window_property(m_root, "_NET_SUPPORTING_WM_CHECK"); + unset_string_property(m_root, "_NET_WM_NAME"); + unset_string_property(m_root, "WN_CLASS"); + unset_string_property(m_root, "WN_CLASS"); + unset_atomlist_property(m_root, "_NET_SUPPORTED"); + unset_card_property(m_root, "_NET_WM_PID"); + unset_windowlist_property(m_root, "_NET_CLIENT_LIST"); + + XCloseDisplay(mp_dpy); +} + + +// window manipulation +winsys::Window +XConnection::create_frame(winsys::Region region) +{ + winsys::Window window = XCreateSimpleWindow( + mp_dpy, m_root, + region.pos.x, region.pos.y, + region.dim.w, region.dim.h, + 0, 0, 0 + ); + + flush(); + + return window; +} + +void +XConnection::init_window(winsys::Window window, bool) +{ + static const long window_event_mask + = PropertyChangeMask | StructureNotifyMask | FocusChangeMask; + + XSetWindowAttributes wa; + wa.event_mask = window_event_mask; + + XChangeWindowAttributes(mp_dpy, window, CWEventMask, &wa); +} + +void +XConnection::init_frame(winsys::Window window, bool focus_follows_mouse) +{ + static const long frame_event_mask + = StructureNotifyMask | SubstructureNotifyMask | SubstructureRedirectMask + | ButtonPressMask | ButtonReleaseMask | PointerMotionMask; + + XSetWindowAttributes wa; + wa.event_mask = frame_event_mask; + + if (focus_follows_mouse) + wa.event_mask |= EnterWindowMask; + + XChangeWindowAttributes(mp_dpy, window, CWEventMask, &wa); +} + +void +XConnection::init_unmanaged(winsys::Window window) +{ + static const long unmanaged_event_mask = StructureNotifyMask; + + XSetWindowAttributes wa; + wa.event_mask = unmanaged_event_mask; + + XChangeWindowAttributes(mp_dpy, window, CWEventMask, &wa); +} + +void +XConnection::init_move(winsys::Window) +{ + static winsys::Window handle = create_handle(); + confine_pointer(handle); +} + +void +XConnection::init_resize(winsys::Window) +{ + static winsys::Window handle = create_handle(); + confine_pointer(handle); +} + +void +XConnection::cleanup_window(winsys::Window window) +{ + XDeleteProperty(mp_dpy, window, get_atom("_NET_WM_STATE")); + XDeleteProperty(mp_dpy, window, get_atom("_NET_WM_DESKTOP")); +} + +void +XConnection::map_window(winsys::Window window) +{ + XMapWindow(mp_dpy, window); +} + +void +XConnection::unmap_window(winsys::Window window) +{ + XUnmapWindow(mp_dpy, window); +} + +void +XConnection::reparent_window(winsys::Window window, winsys::Window parent, winsys::Pos pos) +{ + disable_substructure_events(); + XReparentWindow(mp_dpy, window, parent, pos.x, pos.y); + enable_substructure_events(); +} + +void +XConnection::unparent_window(winsys::Window window, winsys::Pos pos) +{ + disable_substructure_events(); + XReparentWindow(mp_dpy, window, m_root, pos.x, pos.y); + enable_substructure_events(); +} + +void +XConnection::destroy_window(winsys::Window window) +{ + XDestroyWindow(mp_dpy, window); +} + +bool +XConnection::close_window(winsys::Window window) +{ + XEvent event; + event.type = ClientMessage; + event.xclient.window = window; + event.xclient.message_type = get_atom("WM_PROTOCOLS"); + event.xclient.format = 32; + event.xclient.data.l[0] = get_atom("WM_DELETE_WINDOW"); + event.xclient.data.l[1] = CurrentTime; + + return XSendEvent(mp_dpy, window, False, NoEventMask, &event) != 0; +} + +bool +XConnection::kill_window(winsys::Window window) +{ + int n; + Atom* protocols; + bool found = false; + + if (XGetWMProtocols(mp_dpy, window, &protocols, &n)) { + while (!found && n--) + found = get_atom("WM_DELETE_WINDOW") == protocols[n]; + + XFree(protocols); + } + + if (found) + return close_window(window); + + XGrabServer(mp_dpy); + XSetErrorHandler(s_passthrough_error_handler); + XSetCloseDownMode(mp_dpy, DestroyAll); + XKillClient(mp_dpy, window); + XSync(mp_dpy, False); + XSetErrorHandler(s_default_error_handler); + XUngrabServer(mp_dpy); + + return true; +} + +void +XConnection::place_window(winsys::Window window, winsys::Region& region) +{ + disable_substructure_events(); + XMoveResizeWindow(mp_dpy, window, region.pos.x, region.pos.y, region.dim.w, region.dim.h); + enable_substructure_events(); +} + +void +XConnection::move_window(winsys::Window window, winsys::Pos pos) +{ + disable_substructure_events(); + XMoveWindow(mp_dpy, window, pos.x, pos.y); + enable_substructure_events(); +} + +void +XConnection::resize_window(winsys::Window window, winsys::Dim dim) +{ + disable_substructure_events(); + XResizeWindow(mp_dpy, window, dim.w, dim.h); + enable_substructure_events(); +} + +void +XConnection::focus_window(winsys::Window window) +{ + if (window == None) + window = m_root; + + XSetInputFocus(mp_dpy, window, RevertToNone, CurrentTime); +} + +void +XConnection::stack_window_above(winsys::Window window, std::optional<winsys::Window> sibling) +{ + long mask = CWStackMode; + + XWindowChanges wc; + wc.stack_mode = Above; + + if (sibling) { + wc.sibling = *sibling; + mask |= CWSibling; + } + + XConfigureWindow(mp_dpy, window, mask, &wc); +} + +void +XConnection::stack_window_below(winsys::Window window, std::optional<winsys::Window> sibling) +{ + long mask = CWStackMode; + + XWindowChanges wc; + wc.stack_mode = Below; + + if (sibling != std::nullopt) { + wc.sibling = *sibling; + mask |= CWSibling; + } + + XConfigureWindow(mp_dpy, window, mask, &wc); +} + +void +XConnection::insert_window_in_save_set(winsys::Window window) +{ + XChangeSaveSet(mp_dpy, window, SetModeInsert); +} + +void +XConnection::grab_bindings(std::vector<winsys::KeyInput>& key_inputs, std::vector<winsys::MouseInput>& mouse_inputs) +{ + static const long mouse_event_mask = + ButtonPressMask | ButtonReleaseMask | ButtonMotionMask; + + auto modifier_to_x11 = [](winsys::Modifier modifier) -> std::size_t { + switch (modifier) { + case winsys::Modifier::Ctrl: return ControlMask; + case winsys::Modifier::Shift: return ShiftMask; + case winsys::Modifier::Alt: return Mod1Mask; + case winsys::Modifier::Super: return Mod4Mask; + case winsys::Modifier::NumLock: return Mod2Mask; + case winsys::Modifier::ScrollLock: return Mod5Mask; + default: return 0; + } + }; + + std::vector<std::size_t> modifiers_to_ignore = { + 0, + Mod2Mask, + Mod5Mask + }; + + for (auto& modifier : modifiers_to_ignore) { + for (auto& key_input : key_inputs) + XGrabKey(mp_dpy, + get_keycode(key_input.key), + std::accumulate( + key_input.modifiers.begin(), + key_input.modifiers.end(), + 0, + [modifier_to_x11](std::size_t const& lhs, winsys::Modifier const& rhs) { + return lhs | modifier_to_x11(rhs); + } + ) | modifier, + m_root, + True, + GrabModeAsync, + GrabModeAsync + ); + + for (auto& mouse_input : mouse_inputs) + XGrabButton(mp_dpy, + get_buttoncode(mouse_input.button), + std::accumulate( + mouse_input.modifiers.begin(), + mouse_input.modifiers.end(), + 0, + [modifier_to_x11](std::size_t const& lhs, winsys::Modifier const& rhs) { + return lhs | modifier_to_x11(rhs); + } + ) | modifier, + m_root, + False, + mouse_event_mask, + GrabModeAsync, + GrabModeAsync, + None, + None + ); + } + + XSetWindowAttributes wa; + wa.event_mask = PropertyChangeMask | SubstructureRedirectMask | StructureNotifyMask + | ButtonPressMask | PointerMotionMask | FocusChangeMask; + + XChangeWindowAttributes(mp_dpy, m_root, CWEventMask, &wa); + + flush(); +} + +void +XConnection::regrab_buttons(winsys::Window window) +{ + static const long regrab_event_mask + = ButtonPressMask | ButtonReleaseMask; + + XGrabButton(mp_dpy, + AnyButton, + AnyModifier, + window, + True, + regrab_event_mask, + GrabModeAsync, + GrabModeAsync, + None, + None + ); +} + +void +XConnection::ungrab_buttons(winsys::Window window) +{ + XUngrabButton(mp_dpy, AnyButton, AnyModifier, window); +} + +void +XConnection::unfocus() +{ + XSetInputFocus(mp_dpy, m_root, m_check_window, CurrentTime); +} + +void +XConnection::set_window_border_width(winsys::Window window, unsigned width) +{ + XSetWindowBorderWidth(mp_dpy, window, width); +} + +void +XConnection::set_window_border_color(winsys::Window window, unsigned color) +{ + XSetWindowBorder(mp_dpy, window, color); +} + +void +XConnection::set_window_background_color(winsys::Window window, unsigned color) +{ + XSetWindowBackground(mp_dpy, window, color); + XClearWindow(mp_dpy, window); +} + +void +XConnection::update_window_offset(winsys::Window window, winsys::Window frame) +{ + XWindowAttributes fa; + XWindowAttributes wa; + + XGetWindowAttributes(mp_dpy, frame, &fa); + XGetWindowAttributes(mp_dpy, window, &wa); + + XEvent event; + event.type = ConfigureNotify; + event.xconfigure.send_event = True; + event.xconfigure.display = mp_dpy; + event.xconfigure.event = window; + event.xconfigure.window = window; + event.xconfigure.x = fa.x + wa.x; + event.xconfigure.y = fa.y + wa.x; + event.xconfigure.width = wa.width; + event.xconfigure.height = wa.height; + event.xconfigure.border_width = 0; + event.xconfigure.above = None; + event.xconfigure.override_redirect = True; + + XSendEvent(mp_dpy, window, False, StructureNotifyMask, &event); +} + +winsys::Window +XConnection::get_focused_window() +{ + winsys::Window window; + int _i; + + XGetInputFocus(mp_dpy, &window, &_i); + return window; +} + +std::optional<winsys::Region> +XConnection::get_window_geometry(winsys::Window window) +{ + static XWindowAttributes wa; + if (!XGetWindowAttributes(mp_dpy, window, &wa)) + return std::nullopt; + + return winsys::Region { + winsys::Pos { + wa.x, + wa.y + }, + winsys::Dim { + wa.width, + wa.height + } + }; +} + +std::optional<winsys::Pid> +XConnection::get_window_pid(winsys::Window window) +{ + XResClientIdSpec spec = { + window, + XRES_CLIENT_ID_PID_MASK + }; + + XResClientIdSpec client_specs[1] = { spec }; + + long n_values = 0; + XResClientIdValue* client_values = nullptr; + + std::optional<winsys::Pid> pid = std::nullopt; + + if (!XResQueryClientIds(mp_dpy, 1, client_specs, &n_values, &client_values)) + for (long i = 0; i < n_values; ++i) + if ((client_values[i].spec.mask & XRES_CLIENT_ID_PID_MASK) != 0) { + CARD32* client_value + = reinterpret_cast<CARD32*>(client_values[i].value); + + if (client_value) { + CARD32 value = *client_value; + + if (value > 0) { + pid = static_cast<winsys::Pid>(value); + goto yield; + } + } + } +yield: + XFree(client_values); + return pid; +} + +std::optional<winsys::Pid> +XConnection::get_ppid(std::optional<winsys::Pid> pid) +{ + if (!pid) + return std::nullopt; + + proc_t proc; + memset(&proc, 0, sizeof(proc)); + PROCTAB* ptab = openproc(PROC_FILLSTATUS | PROC_PID, &pid); + + std::optional<winsys::Pid> ppid = std::nullopt; + + if (readproc(ptab, &proc) != 0 && proc.ppid > 0) { + ppid = proc.ppid; + goto yield; + } + +yield: + closeproc(ptab); + return ppid; +} + +bool +XConnection::must_manage_window(winsys::Window window) +{ + static const std::vector<winsys::WindowType> ignore_types + = { winsys::WindowType::Dock, winsys::WindowType::Toolbar }; + + XWindowAttributes wa; + return XGetWindowAttributes(mp_dpy, window, &wa) + && wa.c_class != InputOnly + && !wa.override_redirect + && !window_is_any_of_types(window, ignore_types); +} + +bool +XConnection::must_free_window(winsys::Window window) +{ + static const std::vector<winsys::WindowState> free_states = { + winsys::WindowState::Modal + }; + + static const std::vector<winsys::WindowType> free_types = { + winsys::WindowType::Dialog, + winsys::WindowType::Utility, + winsys::WindowType::Toolbar, + winsys::WindowType::Splash + }; + + std::optional<Index> desktop = get_window_desktop(window); + + if ((desktop && *desktop == 0xFFFFFFFF) + || window_is_any_of_states(window, free_states) + || window_is_any_of_types(window, free_types)) + { + return true; + } + + XWindowAttributes wa; + XGetWindowAttributes(mp_dpy, window, &wa); + + std::optional<winsys::SizeHints> sh + = get_icccm_window_size_hints(window, std::nullopt); + + if (sh) { + if (sh->min_width && sh->min_height && sh->max_width && sh->max_height) + return *sh->max_width > 0 && *sh->max_height > 0 + && *sh->max_width == *sh->min_width && *sh->max_height == *sh->min_height; + } + + return false; +} + +bool +XConnection::window_is_mappable(winsys::Window window) +{ + static const std::vector<winsys::WindowState> unmappable_states = { + winsys::WindowState::Hidden + }; + + XWindowAttributes wa; + XGetWindowAttributes(mp_dpy, window, &wa); + + std::optional<winsys::Hints> hints = get_icccm_window_hints(window); + + return (wa.c_class != InputOnly) + && (hints && hints->initial_state && *hints->initial_state == winsys::IcccmWindowState::Normal) + && !window_is_any_of_states(window, unmappable_states); +} + +// ICCCM +void +XConnection::set_icccm_window_state(winsys::Window window, winsys::IcccmWindowState state) +{ + long window_state; + + switch (state) { + case winsys::IcccmWindowState::Withdrawn: window_state = WithdrawnState; break; + case winsys::IcccmWindowState::Normal: window_state = NormalState; break; + case winsys::IcccmWindowState::Iconic: window_state = IconicState; break; + default: return; + } + + long data[] = { window_state, None }; + XChangeProperty(mp_dpy, window, get_atom("WM_STATE"), get_atom("WM_STATE"), 32, + PropModeReplace, reinterpret_cast<const unsigned char*>(data), 2); +} + +void +XConnection::set_icccm_window_hints(winsys::Window window, winsys::Hints hints) +{ + bool success = false; + XWMHints hints_; + + XWMHints* x_hints = XGetWMHints(mp_dpy, window); + if (x_hints) { + std::memcpy(&hints_, x_hints, sizeof(XWMHints)); + XFree(x_hints); + success = true; + } + + if (!success) + return; + + if (hints.urgent) + hints_.flags |= XUrgencyHint; + else + hints_.flags &= ~XUrgencyHint; + + XSetWMHints(mp_dpy, window, &hints_); +} + +std::string +XConnection::get_icccm_window_name(winsys::Window window) +{ + std::string name; + char name_raw[512]; + + if (!get_text_property(window, get_atom("_NET_WM_NAME"), name_raw, sizeof name_raw)) + get_text_property(window, XA_WM_NAME, name_raw, sizeof name_raw); + + if (name_raw[0] == '\0') + strcpy(name_raw, "N/a"); + + name.assign(name_raw); + return name; +} + +std::string +XConnection::get_icccm_window_class(winsys::Window window) +{ + std::string class_ = "N/a"; + + XClassHint* hint = XAllocClassHint(); + XGetClassHint(mp_dpy, window, hint); + + if (hint->res_class) { + class_.assign(hint->res_class); + XFree(hint); + } + + return class_; +} + +std::string +XConnection::get_icccm_window_instance(winsys::Window window) +{ + std::string instance = "N/a"; + + XClassHint* hint = XAllocClassHint(); + XGetClassHint(mp_dpy, window, hint); + + if (hint->res_name) { + instance.assign(hint->res_name); + XFree(hint); + } + + return instance; +} + +std::optional<winsys::Window> +XConnection::get_icccm_window_transient_for(winsys::Window window) +{ + winsys::Window transient = None; + XGetTransientForHint(mp_dpy, window, &transient); + + return transient == None ? std::nullopt : std::optional(transient); +} + +std::optional<winsys::Window> +XConnection::get_icccm_window_client_leader(winsys::Window window) +{ + winsys::Window leader = get_window_property(window, "WM_CLIENT_LEADER"); + + if (!property_status_ok() || leader == None) + return std::nullopt; + + return leader; +} + +std::optional<winsys::Hints> +XConnection::get_icccm_window_hints(winsys::Window window) +{ + bool success = false; + XWMHints hints; + + XWMHints* x_hints = XGetWMHints(mp_dpy, window); + if (x_hints) { + std::memcpy(&hints, x_hints, sizeof(XWMHints)); + XFree(x_hints); + success = true; + } + + if (!success) + return std::nullopt; + + std::optional<winsys::IcccmWindowState> initial_state; + + switch (hints.initial_state) { + case NormalState: initial_state = winsys::IcccmWindowState::Normal; break; + case IconicState: initial_state = winsys::IcccmWindowState::Iconic; break; + default: return std::nullopt; + } + + std::optional<winsys::Window> group; + + if (hints.window_group) + group = hints.window_group; + + return winsys::Hints { + (hints.flags & XUrgencyHint) != 0, + hints.input, + initial_state, + group + }; +} + +std::optional<winsys::SizeHints> +XConnection::get_icccm_window_size_hints(winsys::Window window, std::optional<winsys::Dim> min_window_dim) +{ + XSizeHints sh; + if (XGetNormalHints(mp_dpy, window, &sh) == 0) + return std::nullopt; + + std::optional<winsys::Pos> pos = std::nullopt; + std::optional<unsigned> sh_min_width = std::nullopt; + std::optional<unsigned> sh_min_height = std::nullopt; + std::optional<unsigned> sh_base_width = std::nullopt; + std::optional<unsigned> sh_base_height = std::nullopt; + std::optional<unsigned> max_width = std::nullopt; + std::optional<unsigned> max_height = std::nullopt; + + bool by_user = (sh.flags & USPosition) != 0; + + if (sh.x > 0 || sh.y > 0) + pos = winsys::Pos { sh.x, sh.y }; + + if ((sh.flags & PMinSize) != 0) { + if (sh.min_width > 0) + sh_min_width = sh.min_width; + + if (sh.min_height > 0) + sh_min_height = sh.min_height; + } + + if ((sh.flags & PBaseSize) != 0) { + if (sh.base_width > 0) + sh_base_width = sh.base_width; + + if (sh.base_height > 0) + sh_base_height = sh.base_height; + } + + if ((sh.flags & PMaxSize) != 0) { + if (sh.max_width > 0) + max_width = sh.max_width; + + if (sh.max_height > 0) + max_height = sh.max_height; + } + + std::optional<unsigned> min_width = sh_min_width ? sh_min_width : sh_base_width; + std::optional<unsigned> min_height = sh_min_height ? sh_min_height : sh_base_height; + std::optional<unsigned> base_width = sh_base_width ? sh_base_width : sh_min_width; + std::optional<unsigned> base_height = sh_base_height ? sh_base_height : sh_min_height; + + if (min_width) { + if (min_window_dim) { + if (min_width < min_window_dim->w) + min_width = min_window_dim->w; + } else if (*min_width <= 0) + min_width = std::nullopt; + } + + if (min_height) { + if (min_window_dim) { + if (min_height < min_window_dim->h) + min_height = min_window_dim->h; + } else if (*min_height <= 0) + min_height = std::nullopt; + } + + std::optional<unsigned> inc_width = std::nullopt; + std::optional<unsigned> inc_height = std::nullopt; + + if ((sh.flags & PResizeInc) != 0) { + if (sh.width_inc > 0 && sh.width_inc < 0xFFFF) + inc_width = sh.width_inc; + + if (sh.height_inc > 0 && sh.height_inc < 0xFFFF) + inc_height = sh.height_inc; + } + + std::optional<double> min_ratio = std::nullopt; + std::optional<winsys::Ratio> min_ratio_vulgar = std::nullopt; + std::optional<double> max_ratio = std::nullopt; + std::optional<winsys::Ratio> max_ratio_vulgar = std::nullopt; + + if ((sh.flags & PAspect) != 0) { + if (sh.min_aspect.x > 0 && sh.min_aspect.y > 0) + min_ratio = static_cast<double>(sh.min_aspect.x) + / static_cast<double>(sh.min_aspect.y); + + min_ratio_vulgar = winsys::Ratio { + sh.min_aspect.x, + sh.min_aspect.y + }; + + if (sh.max_aspect.x > 0 && sh.max_aspect.y > 0) + max_ratio = static_cast<double>(sh.max_aspect.x) + / static_cast<double>(sh.max_aspect.y); + + max_ratio_vulgar = winsys::Ratio { + sh.max_aspect.x, + sh.max_aspect.y + }; + } + + return winsys::SizeHints { + by_user, + pos, + min_width, + min_height, + max_width, + max_height, + base_width, + base_height, + inc_width, + inc_height, + min_ratio, + max_ratio, + min_ratio_vulgar, + max_ratio_vulgar, + }; +} + + +// EWMH +void +XConnection::init_for_wm(std::string const& wm_name, std::vector<std::string> const& desktop_names) +{ + if (!mp_dpy || !m_root) + Util::die("unable to set up window manager"); + + check_otherwm(); + + map_window(m_check_window); + stack_window_below(m_check_window, std::nullopt); + + XRRSelectInput( + mp_dpy, + m_check_window, + RRScreenChangeNotifyMask + ); + + XSetWindowAttributes wa; + wa.cursor = XCreateFontCursor(mp_dpy, XC_left_ptr); + XChangeWindowAttributes(mp_dpy, m_root, CWCursor, &wa); + + std::vector<std::string> wm_class = { wm_name, wm_name }; + + replace_string_property(m_check_window, "_NET_WM_NAME", wm_name); + replace_stringlist_property(m_check_window, "_WM_CLASS", wm_class); + replace_card_property(m_check_window, "_NET_WM_PID", getpid()); + replace_window_property(m_check_window, "_NET_SUPPORTING_WM_CHECK", m_check_window); + + replace_window_property(m_root, "_NET_SUPPORTING_WM_CHECK", m_check_window); + replace_string_property(m_root, "_NET_WM_NAME", wm_name); + replace_stringlist_property(m_root, "_WM_CLASS", wm_class); + + std::vector<Atom> supported_atoms; + supported_atoms.reserve(NetWMID::NetLast); + + for (NetWMID i = NetWMID::NetFirst; i < NetWMID::NetLast; ++i) + supported_atoms.push_back(get_netwm_atom(i)); + + replace_atomlist_property(m_root, "_NET_SUPPORTED", supported_atoms); + replace_card_property(m_root, "_NET_WM_PID", getpid()); + unset_window_property(m_root, "_NET_CLIENT_LIST"); + + update_desktops(desktop_names); +} + +void +XConnection::set_current_desktop(Index index) +{ + replace_card_property(m_root, "_NET_CURRENT_DESKTOP", index); +} + +void +XConnection::set_root_window_name(std::string const& name) +{ + replace_string_property(m_root, "WM_NAME", name); +} + +void +XConnection::set_window_desktop(winsys::Window window, Index index) +{ + replace_card_property(window, "_NET_WM_DESKTOP", index); +} + +void +XConnection::set_window_state(winsys::Window window, winsys::WindowState state, bool on) +{ + Atom atom = get_atom_from_window_state(state); + if (atom == 0) + return; + + if (on) { + std::vector<winsys::WindowState> check_state = { state }; + if (window_is_any_of_states(window, check_state)) + return; + + append_atomlist_property(window, "_NET_WM_STATE", atom); + } else { + std::vector<Atom> atoms + = get_atomlist_property(window, "_NET_WM_STATE"); + + if (!property_status_ok()) + return; + + atoms.erase( + std::remove( + atoms.begin(), + atoms.end(), + atom + ), + atoms.end() + ); + + replace_atomlist_property(window, "_NET_WM_STATE", atoms); + } +} + +void +XConnection::set_window_frame_extents(winsys::Window window, winsys::Extents extents) +{ + std::vector<unsigned long> frame_extents = { + static_cast<unsigned>(extents.left), + static_cast<unsigned>(extents.right), + static_cast<unsigned>(extents.top), + static_cast<unsigned>(extents.bottom) + }; + + replace_cardlist_property(window, "_NET_FRAME_EXTENTS", frame_extents); +} + +void +XConnection::set_desktop_geometry(std::vector<winsys::Region> const& geometries) +{ + std::vector<unsigned long> values; + values.reserve(2 * geometries.size()); + + for (auto& geometry : geometries) { + values.push_back(geometry.dim.w); + values.push_back(geometry.dim.h); + } + + replace_cardlist_property(m_root, "_NET_DESKTOP_GEOMETRY", values); +} + +void +XConnection::set_desktop_viewport(std::vector<winsys::Region> const& viewports) +{ + std::vector<unsigned long> values; + values.reserve(2 * viewports.size()); + + for (auto& viewport : viewports) { + values.push_back(viewport.pos.x); + values.push_back(viewport.pos.y); + } + + replace_cardlist_property(m_root, "_NET_DESKTOP_VIEWPORT", values); +} + +void +XConnection::set_workarea(std::vector<winsys::Region> const& workareas) +{ + std::vector<unsigned long> values; + values.reserve(4 * workareas.size()); + + for (auto& workarea : workareas) { + values.push_back(workarea.pos.x); + values.push_back(workarea.pos.y); + values.push_back(workarea.dim.w); + values.push_back(workarea.dim.h); + } + + replace_cardlist_property(m_root, "_NET_WORKAREA", values); +} + +void +XConnection::update_desktops(std::vector<std::string> const& desktop_names) +{ + replace_card_property(m_root, "_NET_NUMBER_OF_DESKTOPS", desktop_names.size()); + replace_stringlist_property(m_root, "_NET_DESKTOP_NAMES", desktop_names); +} + +void +XConnection::update_client_list(std::vector<winsys::Window> const& clients) +{ + replace_windowlist_property(m_root, "_NET_CLIENT_LIST", clients); +} + +void +XConnection::update_client_list_stacking(std::vector<winsys::Window> const& clients) +{ + replace_windowlist_property(m_root, "_NET_CLIENT_LIST_STACKING", clients); +} + +std::optional<std::vector<std::optional<winsys::Strut>>> +XConnection::get_window_strut(winsys::Window window) +{ + std::optional<std::vector<std::optional<winsys::Strut>>> struts_partial + = get_window_strut_partial(window); + + if (struts_partial) + return struts_partial; + + std::vector<unsigned long> strut_widths = get_cardlist_property(window, "_NET_WM_STRUT"); + + if (!property_status_ok() || strut_widths.empty()) + return std::nullopt; + + std::vector<std::optional<winsys::Strut>> struts; + struts.reserve(4); + + for (std::size_t i = 0; i < strut_widths.size() && i < 4; ++i) { + std::optional<winsys::Strut> strut = std::nullopt; + + if (strut_widths[i] > 0) + strut = winsys::Strut { + window, + static_cast<int>(strut_widths[i]) + }; + + struts.push_back(strut); + } + + return struts; +} + +std::optional<std::vector<std::optional<winsys::Strut>>> +XConnection::get_window_strut_partial(winsys::Window window) +{ + std::vector<unsigned long> strut_widths = get_cardlist_property(window, "_NET_WM_STRUT_PARTIAL"); + + if (!property_status_ok() || strut_widths.empty()) + return std::nullopt; + + std::vector<std::optional<winsys::Strut>> struts_partial; + struts_partial.reserve(4); + + for (std::size_t i = 0; i < strut_widths.size() && i < 4; ++i) { + std::optional<winsys::Strut> strut = std::nullopt; + + if (strut_widths[i] > 0) + strut = winsys::Strut { + window, + static_cast<int>(strut_widths[i]) + }; + + struts_partial.push_back(strut); + } + + return struts_partial; +} + +std::optional<Index> +XConnection::get_window_desktop(winsys::Window window) +{ + Index index = get_card_property(window, "_NET_WM_DESKTOP"); + + if (!property_status_ok()) + return std::nullopt; + + return index; +} + +winsys::WindowType +XConnection::get_window_preferred_type(winsys::Window window) +{ + std::vector<winsys::WindowType> window_types = get_window_types(window); + + if (window_types.size() > 0) + return window_types[0]; + + return winsys::WindowType::Normal; +} + +std::vector<winsys::WindowType> +XConnection::get_window_types(winsys::Window window) +{ + std::vector<Atom> window_type_atoms = get_atomlist_property(window, "_NET_WM_WINDOW_TYPE"); + + if (!property_status_ok()) + return {}; + + std::vector<winsys::WindowType> window_types = {}; + + std::transform( + window_type_atoms.begin(), + window_type_atoms.end(), + std::back_inserter(window_types), + [=, this](Atom atom) -> winsys::WindowType { + return get_window_type_from_atom(atom); + } + ); + + return window_types; +} + +std::optional<winsys::WindowState> +XConnection::get_window_preferred_state(winsys::Window window) +{ + std::vector<winsys::WindowState> window_states = get_window_states(window); + + if (window_states.size() > 0) + return window_states[0]; + + return std::nullopt; +} + +std::vector<winsys::WindowState> +XConnection::get_window_states(winsys::Window window) +{ + std::vector<Atom> window_state_atoms = get_atomlist_property(window, "_NET_WM_STATE"); + + if (!property_status_ok()) + return {}; + + std::vector<winsys::WindowState> window_states = {}; + + std::transform( + window_state_atoms.begin(), + window_state_atoms.end(), + std::back_inserter(window_states), + [=, this](Atom atom) -> winsys::WindowState { + return get_window_state_from_atom(atom); + } + ); + + return window_states; +} + +bool +XConnection::window_is_fullscreen(winsys::Window window) +{ + static const std::vector<winsys::WindowState> fullscreen_state + = { winsys::WindowState::Fullscreen }; + + return window_is_any_of_states(window, fullscreen_state); +} + +bool +XConnection::window_is_above(winsys::Window window) +{ + static const std::vector<winsys::WindowState> above_state + = { winsys::WindowState::Above_ }; + + return window_is_any_of_states(window, above_state); +} + +bool +XConnection::window_is_below(winsys::Window window) +{ + static const std::vector<winsys::WindowState> below_state + = { winsys::WindowState::Below_ }; + + return window_is_any_of_states(window, below_state); +} + +bool +XConnection::window_is_sticky(winsys::Window window) +{ + static const std::vector<winsys::WindowState> sticky_state + = { winsys::WindowState::Sticky }; + + return window_is_any_of_states(window, sticky_state); +} + + +void +XConnection::enable_substructure_events() +{ + if (--m_substructure_level != 0) + return; + + if ((m_prev_root_mask & SubstructureNotifyMask) == 0) + return; + + XSelectInput(mp_dpy, m_root, m_prev_root_mask | SubstructureNotifyMask); + XFlush(mp_dpy); +} + +void +XConnection::disable_substructure_events() +{ + if (++m_substructure_level != 1) + return; + + if ((m_prev_root_mask & SubstructureNotifyMask) == 0) + return; + + XSelectInput(mp_dpy, m_root, m_prev_root_mask & ~SubstructureNotifyMask); + XFlush(mp_dpy); +} + +void +XConnection::next_event(XEvent& event) +{ + XNextEvent(mp_dpy, &event); +} + +bool +XConnection::typed_event(XEvent& event, int type) +{ + return XCheckTypedEvent(mp_dpy, type, &event); +} + +void +XConnection::last_typed_event(XEvent& event, int type) +{ + while (typed_event(event, type)); +} + +void +XConnection::sync(bool discard) +{ + XSync(mp_dpy, discard); +} + +int +XConnection::pending() +{ + return XPending(mp_dpy); +} + +winsys::Window +XConnection::create_handle() +{ + winsys::Window window = XCreateWindow( + mp_dpy, m_root, + -2, -2, + 1, 1, + CopyFromParent, 0, + InputOnly, + CopyFromParent, 0, NULL + ); + + flush(); + + return window; +} + + +Atom +XConnection::get_atom(std::string const& name) +{ + if (m_interned_atoms.count(name) > 0) + return m_interned_atoms.at(name); + + Atom atom = XInternAtom(mp_dpy, name.c_str(), False); + intern_atom(name, atom); + return atom; +} + +Atom +XConnection::get_netwm_atom(NetWMID const& id) +{ + if (m_netwm_atoms.count(id) > 0) + return m_netwm_atoms.at(id); + + return NetWMID::NetLast; +} + +void +XConnection::intern_atom(std::string const& name, Atom atom) +{ + m_interned_atoms[name] = atom; + m_atom_names[atom] = name; +} + +winsys::Key +XConnection::get_key(const std::size_t keycode) +{ + if (m_keys.count(keycode) > 0) + return m_keys.at(keycode); + + return winsys::Key::Any; +} + +std::size_t +XConnection::get_keycode(const winsys::Key key) +{ + if (m_keycodes.count(key) > 0) + return m_keycodes.at(key); + + unsigned keycode = 0; + + switch (key) { + case winsys::Key::BackSpace: keycode = XKeysymToKeycode(mp_dpy, XK_BackSpace); break; + case winsys::Key::Tab: keycode = XKeysymToKeycode(mp_dpy, XK_Tab); break; + case winsys::Key::Clear: keycode = XKeysymToKeycode(mp_dpy, XK_Clear); break; + case winsys::Key::Return: keycode = XKeysymToKeycode(mp_dpy, XK_Return); break; + case winsys::Key::Shift: keycode = XKeysymToKeycode(mp_dpy, XK_Shift_L); break; + case winsys::Key::Control: keycode = XKeysymToKeycode(mp_dpy, XK_Control_L); break; + case winsys::Key::Alt: keycode = XKeysymToKeycode(mp_dpy, XK_Alt_L); break; + case winsys::Key::Super: keycode = XKeysymToKeycode(mp_dpy, XK_Super_L); break; + case winsys::Key::Menu: keycode = XKeysymToKeycode(mp_dpy, XK_Menu); break; + case winsys::Key::Pause: keycode = XKeysymToKeycode(mp_dpy, XK_Pause); break; + case winsys::Key::CapsLock: keycode = XKeysymToKeycode(mp_dpy, XK_Caps_Lock); break; + case winsys::Key::Escape: keycode = XKeysymToKeycode(mp_dpy, XK_Escape); break; + case winsys::Key::Space: keycode = XKeysymToKeycode(mp_dpy, XK_space); break; + case winsys::Key::ExclamationMark: keycode = XKeysymToKeycode(mp_dpy, XK_exclam); break; + case winsys::Key::QuotationMark: keycode = XKeysymToKeycode(mp_dpy, XK_quotedbl); break; + case winsys::Key::QuestionMark: keycode = XKeysymToKeycode(mp_dpy, XK_question); break; + case winsys::Key::NumberSign: keycode = XKeysymToKeycode(mp_dpy, XK_numbersign); break; + case winsys::Key::DollarSign: keycode = XKeysymToKeycode(mp_dpy, XK_dollar); break; + case winsys::Key::PercentSign: keycode = XKeysymToKeycode(mp_dpy, XK_percent); break; + case winsys::Key::AtSign: keycode = XKeysymToKeycode(mp_dpy, XK_at); break; + case winsys::Key::Ampersand: keycode = XKeysymToKeycode(mp_dpy, XK_ampersand); break; + case winsys::Key::Apostrophe: keycode = XKeysymToKeycode(mp_dpy, XK_apostrophe); break; + case winsys::Key::LeftParenthesis: keycode = XKeysymToKeycode(mp_dpy, XK_parenleft); break; + case winsys::Key::RightParenthesis: keycode = XKeysymToKeycode(mp_dpy, XK_parenright); break; + case winsys::Key::LeftBracket: keycode = XKeysymToKeycode(mp_dpy, XK_bracketleft); break; + case winsys::Key::RightBracket: keycode = XKeysymToKeycode(mp_dpy, XK_bracketright); break; + case winsys::Key::LeftBrace: keycode = XKeysymToKeycode(mp_dpy, XK_braceleft); break; + case winsys::Key::RightBrace: keycode = XKeysymToKeycode(mp_dpy, XK_braceright); break; + case winsys::Key::Underscore: keycode = XKeysymToKeycode(mp_dpy, XK_underscore); break; + case winsys::Key::Grave: keycode = XKeysymToKeycode(mp_dpy, XK_grave); break; + case winsys::Key::Bar: keycode = XKeysymToKeycode(mp_dpy, XK_bar); break; + case winsys::Key::Tilde: keycode = XKeysymToKeycode(mp_dpy, XK_asciitilde); break; + case winsys::Key::QuoteLeft: keycode = XKeysymToKeycode(mp_dpy, XK_quoteleft); break; + case winsys::Key::Asterisk: keycode = XKeysymToKeycode(mp_dpy, XK_asterisk); break; + case winsys::Key::Plus: keycode = XKeysymToKeycode(mp_dpy, XK_plus); break; + case winsys::Key::Comma: keycode = XKeysymToKeycode(mp_dpy, XK_comma); break; + case winsys::Key::Minus: keycode = XKeysymToKeycode(mp_dpy, XK_minus); break; + case winsys::Key::Period: keycode = XKeysymToKeycode(mp_dpy, XK_period); break; + case winsys::Key::Slash: keycode = XKeysymToKeycode(mp_dpy, XK_slash); break; + case winsys::Key::BackSlash: keycode = XKeysymToKeycode(mp_dpy, XK_backslash); break; + case winsys::Key::Colon: keycode = XKeysymToKeycode(mp_dpy, XK_colon); break; + case winsys::Key::SemiColon: keycode = XKeysymToKeycode(mp_dpy, XK_semicolon); break; + case winsys::Key::Less: keycode = XKeysymToKeycode(mp_dpy, XK_less); break; + case winsys::Key::Equal: keycode = XKeysymToKeycode(mp_dpy, XK_equal); break; + case winsys::Key::Greater: keycode = XKeysymToKeycode(mp_dpy, XK_greater); break; + case winsys::Key::PageUp: keycode = XKeysymToKeycode(mp_dpy, XK_Prior); break; + case winsys::Key::PageDown: keycode = XKeysymToKeycode(mp_dpy, XK_Next); break; + case winsys::Key::End: keycode = XKeysymToKeycode(mp_dpy, XK_End); break; + case winsys::Key::Home: keycode = XKeysymToKeycode(mp_dpy, XK_Home); break; + case winsys::Key::Left: keycode = XKeysymToKeycode(mp_dpy, XK_Left); break; + case winsys::Key::Up: keycode = XKeysymToKeycode(mp_dpy, XK_Up); break; + case winsys::Key::Right: keycode = XKeysymToKeycode(mp_dpy, XK_Right); break; + case winsys::Key::Down: keycode = XKeysymToKeycode(mp_dpy, XK_Down); break; + case winsys::Key::Select: keycode = XKeysymToKeycode(mp_dpy, XK_Select); break; + case winsys::Key::Print: keycode = XKeysymToKeycode(mp_dpy, XK_Print); break; + case winsys::Key::Execute: keycode = XKeysymToKeycode(mp_dpy, XK_Execute); break; + case winsys::Key::PrintScreen: keycode = XKeysymToKeycode(mp_dpy, XK_Print); break; + case winsys::Key::Insert: keycode = XKeysymToKeycode(mp_dpy, XK_Insert); break; + case winsys::Key::Delete: keycode = XKeysymToKeycode(mp_dpy, XK_Delete); break; + case winsys::Key::Help: keycode = XKeysymToKeycode(mp_dpy, XK_Help); break; + case winsys::Key::Zero: keycode = XKeysymToKeycode(mp_dpy, XK_0); break; + case winsys::Key::One: keycode = XKeysymToKeycode(mp_dpy, XK_1); break; + case winsys::Key::Two: keycode = XKeysymToKeycode(mp_dpy, XK_2); break; + case winsys::Key::Three: keycode = XKeysymToKeycode(mp_dpy, XK_3); break; + case winsys::Key::Four: keycode = XKeysymToKeycode(mp_dpy, XK_4); break; + case winsys::Key::Five: keycode = XKeysymToKeycode(mp_dpy, XK_5); break; + case winsys::Key::Six: keycode = XKeysymToKeycode(mp_dpy, XK_6); break; + case winsys::Key::Seven: keycode = XKeysymToKeycode(mp_dpy, XK_7); break; + case winsys::Key::Eight: keycode = XKeysymToKeycode(mp_dpy, XK_8); break; + case winsys::Key::Nine: keycode = XKeysymToKeycode(mp_dpy, XK_9); break; + case winsys::Key::A: keycode = XKeysymToKeycode(mp_dpy, XK_a); break; + case winsys::Key::B: keycode = XKeysymToKeycode(mp_dpy, XK_b); break; + case winsys::Key::C: keycode = XKeysymToKeycode(mp_dpy, XK_c); break; + case winsys::Key::D: keycode = XKeysymToKeycode(mp_dpy, XK_d); break; + case winsys::Key::E: keycode = XKeysymToKeycode(mp_dpy, XK_e); break; + case winsys::Key::F: keycode = XKeysymToKeycode(mp_dpy, XK_f); break; + case winsys::Key::G: keycode = XKeysymToKeycode(mp_dpy, XK_g); break; + case winsys::Key::H: keycode = XKeysymToKeycode(mp_dpy, XK_h); break; + case winsys::Key::I: keycode = XKeysymToKeycode(mp_dpy, XK_i); break; + case winsys::Key::J: keycode = XKeysymToKeycode(mp_dpy, XK_j); break; + case winsys::Key::K: keycode = XKeysymToKeycode(mp_dpy, XK_k); break; + case winsys::Key::L: keycode = XKeysymToKeycode(mp_dpy, XK_l); break; + case winsys::Key::M: keycode = XKeysymToKeycode(mp_dpy, XK_m); break; + case winsys::Key::N: keycode = XKeysymToKeycode(mp_dpy, XK_n); break; + case winsys::Key::O: keycode = XKeysymToKeycode(mp_dpy, XK_o); break; + case winsys::Key::P: keycode = XKeysymToKeycode(mp_dpy, XK_p); break; + case winsys::Key::Q: keycode = XKeysymToKeycode(mp_dpy, XK_q); break; + case winsys::Key::R: keycode = XKeysymToKeycode(mp_dpy, XK_r); break; + case winsys::Key::S: keycode = XKeysymToKeycode(mp_dpy, XK_s); break; + case winsys::Key::T: keycode = XKeysymToKeycode(mp_dpy, XK_t); break; + case winsys::Key::U: keycode = XKeysymToKeycode(mp_dpy, XK_u); break; + case winsys::Key::V: keycode = XKeysymToKeycode(mp_dpy, XK_v); break; + case winsys::Key::W: keycode = XKeysymToKeycode(mp_dpy, XK_w); break; + case winsys::Key::X: keycode = XKeysymToKeycode(mp_dpy, XK_x); break; + case winsys::Key::Y: keycode = XKeysymToKeycode(mp_dpy, XK_y); break; + case winsys::Key::Z: keycode = XKeysymToKeycode(mp_dpy, XK_z); break; + case winsys::Key::NumPad0: keycode = XKeysymToKeycode(mp_dpy, XK_KP_0); break; + case winsys::Key::NumPad1: keycode = XKeysymToKeycode(mp_dpy, XK_KP_1); break; + case winsys::Key::NumPad2: keycode = XKeysymToKeycode(mp_dpy, XK_KP_2); break; + case winsys::Key::NumPad3: keycode = XKeysymToKeycode(mp_dpy, XK_KP_3); break; + case winsys::Key::NumPad4: keycode = XKeysymToKeycode(mp_dpy, XK_KP_4); break; + case winsys::Key::NumPad5: keycode = XKeysymToKeycode(mp_dpy, XK_KP_5); break; + case winsys::Key::NumPad6: keycode = XKeysymToKeycode(mp_dpy, XK_KP_6); break; + case winsys::Key::NumPad7: keycode = XKeysymToKeycode(mp_dpy, XK_KP_7); break; + case winsys::Key::NumPad8: keycode = XKeysymToKeycode(mp_dpy, XK_KP_8); break; + case winsys::Key::NumPad9: keycode = XKeysymToKeycode(mp_dpy, XK_KP_9); break; + case winsys::Key::Multiply: keycode = XKeysymToKeycode(mp_dpy, XK_KP_Multiply); break; + case winsys::Key::Add: keycode = XKeysymToKeycode(mp_dpy, XK_KP_Add); break; + case winsys::Key::Seperator: keycode = XKeysymToKeycode(mp_dpy, XK_KP_Separator); break; + case winsys::Key::Subtract: keycode = XKeysymToKeycode(mp_dpy, XK_KP_Subtract); break; + case winsys::Key::Decimal: keycode = XKeysymToKeycode(mp_dpy, XK_KP_Decimal); break; + case winsys::Key::Divide: keycode = XKeysymToKeycode(mp_dpy, XK_KP_Divide); break; + case winsys::Key::F1: keycode = XKeysymToKeycode(mp_dpy, XK_F1); break; + case winsys::Key::F2: keycode = XKeysymToKeycode(mp_dpy, XK_F2); break; + case winsys::Key::F3: keycode = XKeysymToKeycode(mp_dpy, XK_F3); break; + case winsys::Key::F4: keycode = XKeysymToKeycode(mp_dpy, XK_F4); break; + case winsys::Key::F5: keycode = XKeysymToKeycode(mp_dpy, XK_F5); break; + case winsys::Key::F6: keycode = XKeysymToKeycode(mp_dpy, XK_F6); break; + case winsys::Key::F7: keycode = XKeysymToKeycode(mp_dpy, XK_F7); break; + case winsys::Key::F8: keycode = XKeysymToKeycode(mp_dpy, XK_F8); break; + case winsys::Key::F9: keycode = XKeysymToKeycode(mp_dpy, XK_F9); break; + case winsys::Key::F10: keycode = XKeysymToKeycode(mp_dpy, XK_F10); break; + case winsys::Key::F11: keycode = XKeysymToKeycode(mp_dpy, XK_F11); break; + case winsys::Key::F12: keycode = XKeysymToKeycode(mp_dpy, XK_F12); break; + case winsys::Key::F13: keycode = XKeysymToKeycode(mp_dpy, XK_F13); break; + case winsys::Key::F14: keycode = XKeysymToKeycode(mp_dpy, XK_F14); break; + case winsys::Key::F15: keycode = XKeysymToKeycode(mp_dpy, XK_F15); break; + case winsys::Key::F16: keycode = XKeysymToKeycode(mp_dpy, XK_F16); break; + case winsys::Key::F17: keycode = XKeysymToKeycode(mp_dpy, XK_F17); break; + case winsys::Key::F18: keycode = XKeysymToKeycode(mp_dpy, XK_F18); break; + case winsys::Key::F19: keycode = XKeysymToKeycode(mp_dpy, XK_F19); break; + case winsys::Key::F20: keycode = XKeysymToKeycode(mp_dpy, XK_F20); break; + case winsys::Key::F21: keycode = XKeysymToKeycode(mp_dpy, XK_F21); break; + case winsys::Key::F22: keycode = XKeysymToKeycode(mp_dpy, XK_F22); break; + case winsys::Key::F23: keycode = XKeysymToKeycode(mp_dpy, XK_F23); break; + case winsys::Key::F24: keycode = XKeysymToKeycode(mp_dpy, XK_F24); break; + case winsys::Key::Numlock: keycode = XKeysymToKeycode(mp_dpy, XK_Num_Lock); break; + case winsys::Key::ScrollLock: keycode = XKeysymToKeycode(mp_dpy, XK_Scroll_Lock); break; + case winsys::Key::LeftShift: keycode = XKeysymToKeycode(mp_dpy, XK_Shift_L); break; + case winsys::Key::RightShift: keycode = XKeysymToKeycode(mp_dpy, XK_Shift_R); break; + case winsys::Key::LeftControl: keycode = XKeysymToKeycode(mp_dpy, XK_Control_L); break; + case winsys::Key::RightContol: keycode = XKeysymToKeycode(mp_dpy, XK_Control_R); break; + case winsys::Key::LeftAlt: keycode = XKeysymToKeycode(mp_dpy, XK_Alt_L); break; + case winsys::Key::RightAlt: keycode = XKeysymToKeycode(mp_dpy, XK_Alt_R); break; + case winsys::Key::LeftSuper: keycode = XKeysymToKeycode(mp_dpy, XK_Super_L); break; + case winsys::Key::RightSuper: keycode = XKeysymToKeycode(mp_dpy, XK_Super_R); break; + case winsys::Key::BrowserBack: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Back); break; + case winsys::Key::BrowserForward: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Forward); break; + case winsys::Key::BrowserRefresh: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Refresh); break; + case winsys::Key::BrowserStop: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Close); break; + case winsys::Key::BrowserSearch: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Search); break; + case winsys::Key::BrowserFavorites: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Favorites); break; + case winsys::Key::BrowserHome: keycode = XKeysymToKeycode(mp_dpy, XF86XK_HomePage); break; + case winsys::Key::VolumeMute: keycode = XKeysymToKeycode(mp_dpy, XF86XK_AudioMute); break; + case winsys::Key::VolumeDown: keycode = XKeysymToKeycode(mp_dpy, XF86XK_AudioLowerVolume); break; + case winsys::Key::VolumeUp: keycode = XKeysymToKeycode(mp_dpy, XF86XK_AudioRaiseVolume); break; + case winsys::Key::MicMute: keycode = XKeysymToKeycode(mp_dpy, XF86XK_AudioMicMute); break; + case winsys::Key::NextTrack: keycode = XKeysymToKeycode(mp_dpy, XF86XK_AudioNext); break; + case winsys::Key::PreviousTrack: keycode = XKeysymToKeycode(mp_dpy, XF86XK_AudioPrev); break; + case winsys::Key::StopMedia: keycode = XKeysymToKeycode(mp_dpy, XF86XK_AudioStop); break; + case winsys::Key::PlayPause: keycode = XKeysymToKeycode(mp_dpy, XF86XK_AudioPlay); break; + case winsys::Key::LaunchMail: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Mail); break; + case winsys::Key::SelectMedia: keycode = XKeysymToKeycode(mp_dpy, XF86XK_AudioMedia); break; + case winsys::Key::LaunchAppA: keycode = XKeysymToKeycode(mp_dpy, XF86XK_LaunchA); break; + case winsys::Key::LaunchAppB: keycode = XKeysymToKeycode(mp_dpy, XF86XK_LaunchB); break; + case winsys::Key::LaunchAppC: keycode = XKeysymToKeycode(mp_dpy, XF86XK_LaunchC); break; + case winsys::Key::LaunchAppD: keycode = XKeysymToKeycode(mp_dpy, XF86XK_LaunchD); break; + case winsys::Key::LaunchAppE: keycode = XKeysymToKeycode(mp_dpy, XF86XK_LaunchE); break; + case winsys::Key::LaunchAppF: keycode = XKeysymToKeycode(mp_dpy, XF86XK_LaunchF); break; + case winsys::Key::LaunchApp0: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Launch0); break; + case winsys::Key::LaunchApp1: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Launch1); break; + case winsys::Key::LaunchApp2: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Launch2); break; + case winsys::Key::LaunchApp3: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Launch3); break; + case winsys::Key::LaunchApp4: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Launch4); break; + case winsys::Key::LaunchApp5: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Launch5); break; + case winsys::Key::LaunchApp6: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Launch6); break; + case winsys::Key::LaunchApp7: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Launch7); break; + case winsys::Key::LaunchApp8: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Launch8); break; + case winsys::Key::LaunchApp9: keycode = XKeysymToKeycode(mp_dpy, XF86XK_Launch9); break; + case winsys::Key::BrightnessDown: keycode = XKeysymToKeycode(mp_dpy, XKB_KEY_XF86MonBrightnessDown); break; + case winsys::Key::BrightnessUp: keycode = XKeysymToKeycode(mp_dpy, XKB_KEY_XF86MonBrightnessDown); break; + case winsys::Key::KeyboardBrightnessDown: keycode = XKeysymToKeycode(mp_dpy, XKB_KEY_XF86KbdBrightnessDown); break; + case winsys::Key::KeyboardBrightnessUp: keycode = XKeysymToKeycode(mp_dpy, XKB_KEY_XF86KbdBrightnessDown); break; + case winsys::Key::Any: return 0; + } + + intern_key(keycode, key); + return keycode; +} + +void +XConnection::intern_key(const unsigned keycode, const winsys::Key key) +{ + m_keys[keycode] = key; + m_keycodes[key] = keycode; +} + +winsys::Button +XConnection::get_button(const unsigned buttoncode) const +{ + switch (buttoncode) { + case 1: return winsys::Button::Left; + case 2: return winsys::Button::Middle; + case 3: return winsys::Button::Right; + case 4: return winsys::Button::ScrollUp; + case 5: return winsys::Button::ScrollDown; + case 6: return winsys::Button::Backward; + case 7: return winsys::Button::Forward; + default: return winsys::Button::Left; + } +} + +unsigned +XConnection::get_buttoncode(const winsys::Button button) const +{ + switch (button) { + case winsys::Button::Left: return 1; + case winsys::Button::Middle: return 2; + case winsys::Button::Right: return 3; + case winsys::Button::ScrollUp: return 4; + case winsys::Button::ScrollDown: return 5; + case winsys::Button::Backward: return 6; + case winsys::Button::Forward: return 7; + default: return 0; + } +} + +winsys::WindowState +XConnection::get_window_state_from_atom(Atom atom) +{ + if (atom == get_atom("_NET_WM_STATE_MODAL")) return winsys::WindowState::Modal; + if (atom == get_atom("_NET_WM_STATE_STICKY")) return winsys::WindowState::Sticky; + if (atom == get_atom("_NET_WM_STATE_MAXIMIZEDVERT")) return winsys::WindowState::MaximizedVert; + if (atom == get_atom("_NET_WM_STATE_MAXIMIZEDHORZ")) return winsys::WindowState::MaximizedHorz; + if (atom == get_atom("_NET_WM_STATE_SHADED")) return winsys::WindowState::Shaded; + if (atom == get_atom("_NET_WM_STATE_SKIPTASKBAR")) return winsys::WindowState::SkipTaskbar; + if (atom == get_atom("_NET_WM_STATE_SKIPPAGER")) return winsys::WindowState::SkipPager; + if (atom == get_atom("_NET_WM_STATE_HIDDEN")) return winsys::WindowState::Hidden; + if (atom == get_atom("_NET_WM_STATE_FULLSCREEN")) return winsys::WindowState::Fullscreen; + if (atom == get_atom("_NET_WM_STATE_ABOVE")) return winsys::WindowState::Above_; + if (atom == get_atom("_NET_WM_STATE_BELOW")) return winsys::WindowState::Below_; + if (atom == get_atom("_NET_WM_STATE_DEMANDS_ATTENTION")) return winsys::WindowState::DemandsAttention; + return winsys::WindowState::Hidden; +} + +winsys::WindowType +XConnection::get_window_type_from_atom(Atom atom) +{ + if (atom == get_atom("_NET_WM_WINDOW_TYPE_DESKTOP")) return winsys::WindowType::Desktop; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_DOCK")) return winsys::WindowType::Dock; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_TOOLBAR")) return winsys::WindowType::Toolbar; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_MENU")) return winsys::WindowType::Menu; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_UTILITY")) return winsys::WindowType::Utility; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_SPLASH")) return winsys::WindowType::Splash; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_DIALOG")) return winsys::WindowType::Dialog; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_DROPDOWNMENU")) return winsys::WindowType::DropdownMenu; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_POPUPMENU")) return winsys::WindowType::PopupMenu; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_TOOLTIP")) return winsys::WindowType::Tooltip; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_NOTIFICATION")) return winsys::WindowType::Notification; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_COMBO")) return winsys::WindowType::Combo; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_DND")) return winsys::WindowType::Dnd; + if (atom == get_atom("_NET_WM_WINDOW_TYPE_NORMAL")) return winsys::WindowType::Normal; + return winsys::WindowType::Normal; +} + +Atom +XConnection::get_atom_from_window_state(winsys::WindowState state) +{ + switch (state) { + case winsys::WindowState::Modal: return get_atom("_NET_WM_STATE_MODAL"); + case winsys::WindowState::Sticky: return get_atom("_NET_WM_STATE_STICKY"); + case winsys::WindowState::MaximizedVert: return get_atom("_NET_WM_STATE_MAXIMIZEDVERT"); + case winsys::WindowState::MaximizedHorz: return get_atom("_NET_WM_STATE_MAXIMIZEDHORZ"); + case winsys::WindowState::Shaded: return get_atom("_NET_WM_STATE_SHADED"); + case winsys::WindowState::SkipTaskbar: return get_atom("_NET_WM_STATE_SKIPTASKBAR"); + case winsys::WindowState::SkipPager: return get_atom("_NET_WM_STATE_SKIPPAGER"); + case winsys::WindowState::Hidden: return get_atom("_NET_WM_STATE_HIDDEN"); + case winsys::WindowState::Fullscreen: return get_atom("_NET_WM_STATE_FULLSCREEN"); + case winsys::WindowState::Above_: return get_atom("_NET_WM_STATE_ABOVE"); + case winsys::WindowState::Below_: return get_atom("_NET_WM_STATE_BELOW"); + case winsys::WindowState::DemandsAttention: return get_atom("_NET_WM_STATE_DEMANDS_ATTENTION"); + default: return 0; + } +} + +Atom +XConnection::get_atom_from_window_type(winsys::WindowType type) +{ + switch (type) { + case winsys::WindowType::Desktop: return get_atom("_NET_WM_WINDOW_TYPE_DESKTOP"); + case winsys::WindowType::Dock: return get_atom("_NET_WM_WINDOW_TYPE_DOCK"); + case winsys::WindowType::Toolbar: return get_atom("_NET_WM_WINDOW_TYPE_TOOLBAR"); + case winsys::WindowType::Menu: return get_atom("_NET_WM_WINDOW_TYPE_MENU"); + case winsys::WindowType::Utility: return get_atom("_NET_WM_WINDOW_TYPE_UTILITY"); + case winsys::WindowType::Splash: return get_atom("_NET_WM_WINDOW_TYPE_SPLASH"); + case winsys::WindowType::Dialog: return get_atom("_NET_WM_WINDOW_TYPE_DIALOG"); + case winsys::WindowType::DropdownMenu: return get_atom("_NET_WM_WINDOW_TYPE_DROPDOWNMENU"); + case winsys::WindowType::PopupMenu: return get_atom("_NET_WM_WINDOW_TYPE_POPUPMENU"); + case winsys::WindowType::Tooltip: return get_atom("_NET_WM_WINDOW_TYPE_TOOLTIP"); + case winsys::WindowType::Notification: return get_atom("_NET_WM_WINDOW_TYPE_NOTIFICATION"); + case winsys::WindowType::Combo: return get_atom("_NET_WM_WINDOW_TYPE_COMBO"); + case winsys::WindowType::Dnd: return get_atom("_NET_WM_WINDOW_TYPE_DND"); + case winsys::WindowType::Normal: return get_atom("_NET_WM_WINDOW_TYPE_NORMAL"); + default: return get_atom("_NET_WM_WINDOW_TYPE_NORMAL"); + } +} + +bool +XConnection::property_status_ok() +{ + bool status_ok = m_property_status == 0; + m_property_status = 0; + + return status_ok; +} + +bool +XConnection::has_atom_property(winsys::Window window, Atom atom) +{ + int _i; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom returned_type = None; + unsigned long n_items_returned = 0; + + return (XGetWindowProperty(mp_dpy, window, atom, + 0L, 32, False, XA_ATOM, + &returned_type, &_i, &n_items_returned, + &_ul, &ucp) == Success + && ucp && XFree(ucp) + && returned_type == XA_ATOM + && n_items_returned > 0 + ); +} + +Atom +XConnection::get_atom_property(winsys::Window window, std::string const& name) +{ + int _i; + unsigned long n; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom _a = None; + Atom atom = None; + + m_property_status = XGetWindowProperty( + mp_dpy, + window, + get_atom(name), + 0L, 32, False, + XA_ATOM, + &_a, &_i, &n, &_ul, + &ucp + ); + + if (m_property_status == Success && ucp) { + atom = *(Atom*)ucp; + XFree(ucp); + } + + return atom; +} + +void +XConnection::replace_atom_property(winsys::Window window, std::string const& name, Atom atom) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_ATOM, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(&atom), + 1 + ); +} + +void +XConnection::unset_atom_property(winsys::Window window, std::string const& name) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_ATOM, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(0), + 0 + ); +} + +bool +XConnection::has_atomlist_property(winsys::Window window, Atom atom) +{ + int _i; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom returned_type = None; + unsigned long n_items_returned = 0; + + return (XGetWindowProperty(mp_dpy, window, atom, + 0L, 32, False, XA_ATOM, + &returned_type, &_i, &n_items_returned, + &_ul, &ucp) == Success + && ucp && XFree(ucp) + && returned_type == XA_ATOM + && n_items_returned > 0 + ); +} + +std::vector<Atom> +XConnection::get_atomlist_property(winsys::Window window, std::string const& name) +{ + int _i; + unsigned long n; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom _a = None; + std::vector<Atom> atomlist = {}; + + m_property_status = XGetWindowProperty( + mp_dpy, + window, + get_atom(name), + 0L, 32, False, + XA_ATOM, + &_a, &_i, &n, &_ul, + &ucp + ); + + if (m_property_status == Success && ucp) { + for (std::size_t i = 0; i < n; ++i) + atomlist.push_back(((Atom*)ucp)[i]); + + XFree(ucp); + } + + return atomlist; +} + +void +XConnection::replace_atomlist_property(winsys::Window window, std::string const& name, std::vector<Atom> const& atomlist) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_ATOM, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(atomlist.data()), + atomlist.size() + ); +} + +void +XConnection::append_atomlist_property(winsys::Window window, std::string const& name, Atom atom) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_ATOM, + 32, + PropModeAppend, + reinterpret_cast<const unsigned char*>(&atom), + 1 + ); +} + +void +XConnection::unset_atomlist_property(winsys::Window window, std::string const& name) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_ATOM, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(0), + 0 + ); +} + +bool +XConnection::has_window_property(winsys::Window window, Atom atom) +{ + int _i; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom returned_type = None; + unsigned long n_items_returned = 0; + + return (XGetWindowProperty(mp_dpy, window, atom, + 0L, 32, False, XA_WINDOW, + &returned_type, &_i, &n_items_returned, + &_ul, &ucp) == Success + && ucp && XFree(ucp) + && returned_type == XA_WINDOW + && n_items_returned > 0 + ); +} + +winsys::Window +XConnection::get_window_property(winsys::Window window, std::string const& name) +{ + int _i; + unsigned long n; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom _a = None; + winsys::Window window_ = None; + + m_property_status = XGetWindowProperty( + mp_dpy, + window, + get_atom(name), + 0L, 32, False, + XA_WINDOW, + &_a, &_i, &n, &_ul, + &ucp + ); + + if (m_property_status == Success && ucp) { + window_ = *(winsys::Window*)ucp; + XFree(ucp); + } + + return window_; +} + +void +XConnection::replace_window_property(winsys::Window window, std::string const& name, winsys::Window window_) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_WINDOW, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(&window_), + 1 + ); +} + +void +XConnection::unset_window_property(winsys::Window window, std::string const& name) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_WINDOW, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(0), + 0 + ); +} + +bool +XConnection::has_windowlist_property(winsys::Window window, Atom atom) +{ + int _i; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom returned_type = None; + unsigned long n_items_returned = 0; + + return (XGetWindowProperty(mp_dpy, window, atom, + 0L, 32, False, XA_WINDOW, + &returned_type, &_i, &n_items_returned, + &_ul, &ucp) == Success + && ucp && XFree(ucp) + && returned_type == XA_ATOM + && n_items_returned > 0 + ); +} + +std::vector<winsys::Window> +XConnection::get_windowlist_property(winsys::Window window, std::string const& name) +{ + int _i; + unsigned long n; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom _a = None; + std::vector<winsys::Window> windowlist = {}; + + m_property_status = XGetWindowProperty( + mp_dpy, + window, + get_atom(name), + 0L, 32, False, + XA_WINDOW, + &_a, &_i, &n, &_ul, + &ucp + ); + + if (m_property_status == Success && ucp) { + for (std::size_t i = 0; i < n; ++i) + windowlist.push_back(((winsys::Window*)ucp)[i]); + + XFree(ucp); + } + + return windowlist; +} + +void +XConnection::replace_windowlist_property(winsys::Window window, std::string const& name, std::vector<winsys::Window> const& windowlist) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_WINDOW, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(windowlist.data()), + windowlist.size() + ); +} + +void +XConnection::append_windowlist_property(winsys::Window window, std::string const& name, winsys::Window window_) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_WINDOW, + 32, + PropModeAppend, + reinterpret_cast<const unsigned char*>(&window_), + 1 + ); +} + +void +XConnection::unset_windowlist_property(winsys::Window window, std::string const& name) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_WINDOW, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(0), + 0 + ); +} + +bool +XConnection::has_string_property(winsys::Window window, Atom atom) +{ + int _i; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom returned_type = None; + unsigned long n_items_returned = 0; + + return (XGetWindowProperty(mp_dpy, window, atom, + 0L, 8, False, get_atom("UTF8_STRING"), + &returned_type, &_i, &n_items_returned, + &_ul, &ucp) == Success + && ucp && XFree(ucp) + && returned_type == get_atom("UTF8_STRING") + && n_items_returned > 0 + ); +} + +std::string +XConnection::get_string_property(winsys::Window window, std::string const& name) +{ + int _i; + unsigned long n; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom _a = None; + std::string str_ = {}; + + m_property_status = XGetWindowProperty( + mp_dpy, + window, + get_atom(name), + 0L, 8, False, + get_atom("UTF8_STRING"), + &_a, &_i, &n, &_ul, + &ucp + ); + + if (m_property_status == Success && ucp) { + str_ = std::string((char*)ucp); + XFree(ucp); + } + + return str_; +} + +void +XConnection::replace_string_property(winsys::Window window, std::string const& name, std::string const& str_) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + get_atom("UTF8_STRING"), + 8, + PropModeReplace, + reinterpret_cast<const unsigned char*>(str_.c_str()), + str_.length() + ); +} + +void +XConnection::unset_string_property(winsys::Window window, std::string const& name) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + get_atom("UTF8_STRING"), + 8, + PropModeReplace, + reinterpret_cast<const unsigned char*>(0), + 0 + ); +} + +bool +XConnection::has_stringlist_property(winsys::Window window, Atom atom) +{ + int _i; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom returned_type = None; + unsigned long n_items_returned = 0; + + return (XGetWindowProperty(mp_dpy, window, atom, + 0L, 8, False, get_atom("UTF8_STRING"), + &returned_type, &_i, &n_items_returned, + &_ul, &ucp) == Success + && ucp && XFree(ucp) + && returned_type == get_atom("UTF8_STRING") + && n_items_returned > 0 + ); +} + +std::vector<std::string> +XConnection::get_stringlist_property(winsys::Window window, std::string const& name) +{ + int _i; + unsigned long n; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom _a = None; + std::vector<std::string> stringlist = {}; + + m_property_status = XGetWindowProperty( + mp_dpy, + window, + get_atom(name), + 0L, 8, False, + get_atom("UTF8_STRING"), + &_a, &_i, &n, &_ul, + &ucp + ); + + if (m_property_status == Success && ucp) { + for (std::size_t i = 0; i < n; ++i) + stringlist.emplace_back(std::string(((char**)ucp)[i])); + + XFree(ucp); + } + + return stringlist; +} + +void +XConnection::replace_stringlist_property(winsys::Window window, std::string const& name, std::vector<std::string> const& stringlist) +{ + static char buffer[1024]; + + std::size_t i = 0; + for (auto& str_ : stringlist) { + if (i == 1023) + goto append; + + for (auto& c : str_) { + if (i == 1023) + goto append; + + buffer[i++] = c; + } + + buffer[i++] = '\0'; + } + +append: + buffer[1023] = '\0'; + + XChangeProperty( + mp_dpy, + window, + get_atom(name), + get_atom("UTF8_STRING"), + 8, + PropModeAppend, + reinterpret_cast<const unsigned char*>(buffer), + i + ); +} + +void +XConnection::append_stringlist_property(winsys::Window window, std::string const& name, std::string const& str_) +{ + static char buffer[1024]; + + std::size_t i = 0; + for (auto& c : str_) { + if (i == 1023) + break; + + buffer[i++] = c; + } + + buffer[1023] = '\0'; + + XChangeProperty( + mp_dpy, + window, + get_atom(name), + get_atom("UTF8_STRING"), + 8, + PropModeAppend, + reinterpret_cast<const unsigned char*>(buffer), + i + ); +} + +void +XConnection::unset_stringlist_property(winsys::Window window, std::string const& name) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + get_atom("UTF8_STRING"), + 8, + PropModeReplace, + reinterpret_cast<const unsigned char*>(0), + 0 + ); +} + +bool +XConnection::has_card_property(winsys::Window window, Atom atom) +{ + int _i; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom returned_type = None; + unsigned long n_items_returned = 0; + + return (XGetWindowProperty(mp_dpy, window, atom, + 0L, 32, False, XA_CARDINAL, + &returned_type, &_i, &n_items_returned, + &_ul, &ucp) == Success + && ucp && XFree(ucp) + && returned_type == XA_CARDINAL + && n_items_returned > 0 + ); +} + +unsigned long +XConnection::get_card_property(winsys::Window window, std::string const& name) +{ + int _i; + unsigned long n; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom _a = None; + unsigned long card = None; + + m_property_status = XGetWindowProperty( + mp_dpy, + window, + get_atom(name), + 0L, 32, False, + XA_CARDINAL, + &_a, &_i, &n, &_ul, + &ucp + ); + + if (m_property_status == Success && ucp) { + card = *(winsys::Window*)ucp; + XFree(ucp); + } + + return card; +} + +void +XConnection::replace_card_property(winsys::Window window, std::string const& name, unsigned long card) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_CARDINAL, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(&card), + 1 + ); +} + +void +XConnection::unset_card_property(winsys::Window window, std::string const& name) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_CARDINAL, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(0), + 0 + ); +} + +std::vector<unsigned long> +XConnection::get_cardlist_property(winsys::Window window, std::string const& name) +{ + int _i; + unsigned long n; + unsigned long _ul; + unsigned char* ucp = nullptr; + Atom _a = None; + std::vector<unsigned long> cardlist = {}; + + m_property_status = XGetWindowProperty( + mp_dpy, + window, + get_atom(name), + 0L, 32, False, + XA_CARDINAL, + &_a, &_i, &n, &_ul, + &ucp + ); + + if (m_property_status == Success && ucp) { + for (std::size_t i = 0; i < n; ++i) + cardlist.push_back(((unsigned long*)ucp)[i]); + + XFree(ucp); + } + + return cardlist; +} + +void +XConnection::replace_cardlist_property(winsys::Window window, std::string const& name, std::vector<unsigned long> const& cardlist) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_CARDINAL, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(cardlist.data()), + cardlist.size() + ); +} + +void +XConnection::append_cardlist_property(winsys::Window window, std::string const& name, unsigned long card) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_CARDINAL, + 32, + PropModeAppend, + reinterpret_cast<const unsigned char*>(&card), + 1 + ); +} + +void +XConnection::unset_cardlist_property(winsys::Window window, std::string const& name) +{ + XChangeProperty( + mp_dpy, + window, + get_atom(name), + XA_CARDINAL, + 32, + PropModeReplace, + reinterpret_cast<const unsigned char*>(0), + 0 + ); +} + +bool +XConnection::get_text_property(winsys::Window window, Atom atom, char* text, unsigned size) +{ + char** list = NULL; + int n; + XTextProperty name; + + if (!text || size == 0) + return false; + + text[0] = '\0'; + + if (!XGetTextProperty(mp_dpy, window, &name, atom) || !name.nitems) + return false; + + if (name.encoding == XA_STRING) + std::strncpy(text, (char*)name.value, size - 1); + else if (XmbTextPropertyToTextList(mp_dpy, &name, &list, &n) + >= Success && n > 0 && *list + ) { + std::strncpy(text, *list, size - 1); + XFreeStringList(list); + } + + text[size - 1] = '\0'; + XFree(name.value); + return true; +} + +bool +XConnection::window_is_any_of_states(winsys::Window window, std::vector<winsys::WindowState> const& free_states) +{ + std::vector<Atom> window_state_atoms + = get_atomlist_property(window, "_NET_WM_STATE"); + + if (!property_status_ok()) + return false; + + std::vector<Atom> free_state_atoms; + free_state_atoms.reserve(free_states.size()); + + std::transform( + free_states.begin(), + free_states.end(), + std::back_inserter(free_state_atoms), + [=, this](winsys::WindowState state) -> Atom { + return get_atom_from_window_state(state); + } + ); + + std::sort(window_state_atoms.begin(), window_state_atoms.end()); + std::sort(free_state_atoms.begin(), free_state_atoms.end()); + + std::vector<Atom> found_atoms; + std::set_intersection( + window_state_atoms.begin(), + window_state_atoms.end(), + free_state_atoms.begin(), + free_state_atoms.end(), + std::back_inserter(found_atoms) + ); + + return found_atoms.size() > 0; +} + +bool +XConnection::window_is_any_of_types(winsys::Window window, std::vector<winsys::WindowType> const& free_types) +{ + std::vector<Atom> window_type_atoms + = get_atomlist_property(window, "_NET_WM_WINDOW_TYPE"); + + if (!property_status_ok()) + return false; + + std::vector<Atom> free_type_atoms; + free_type_atoms.reserve(free_types.size()); + + std::transform( + free_types.begin(), + free_types.end(), + std::back_inserter(free_type_atoms), + [=, this](winsys::WindowType type) -> Atom { + return get_atom_from_window_type(type); + } + ); + + std::sort(window_type_atoms.begin(), window_type_atoms.end()); + std::sort(free_type_atoms.begin(), free_type_atoms.end()); + + std::vector<Atom> found_atoms; + std::set_intersection(window_type_atoms.begin(), window_type_atoms.end(), + free_type_atoms.begin(), free_type_atoms.end(), std::back_inserter(found_atoms)); + + return found_atoms.size() > 0; +} + +void +XConnection::check_otherwm() +{ + XSetErrorHandler(XConnection::s_otherwm_error_handler); + XSelectInput(mp_dpy, m_root, SubstructureRedirectMask); + sync(false); + XSetErrorHandler(s_default_error_handler); + sync(false); +} + + +winsys::Event +XConnection::on_button_press() +{ + XButtonEvent event = m_current_event.xbutton; + winsys::Window window = event.window; + winsys::Window subwindow = event.subwindow; + + bool on_root = false; + if (window == m_root && subwindow == None) + on_root = true; + + if (window == m_root || window == None) + window = subwindow; + + winsys::Button button; + switch (event.button) { + case 1: button = winsys::Button::Left; break; + case 2: button = winsys::Button::Middle; break; + case 3: button = winsys::Button::Right; break; + case 4: button = winsys::Button::ScrollUp; break; + case 5: button = winsys::Button::ScrollDown; break; + case 6: button = winsys::Button::Backward; break; + case 7: button = winsys::Button::Forward; break; + default: return std::monostate{}; + } + + std::size_t x11_modifiers[] = { + ControlMask, + ShiftMask, + Mod1Mask, + Mod4Mask, + Mod2Mask, + Mod5Mask + }; + + auto x11_to_modifier = [](std::size_t x11_modifier) -> winsys::Modifier { + switch (x11_modifier) { + case ControlMask: return winsys::Modifier::Ctrl; + case ShiftMask: return winsys::Modifier::Shift; + case Mod1Mask: return winsys::Modifier::Alt; + case Mod4Mask: return winsys::Modifier::Super; + case Mod2Mask: return winsys::Modifier::NumLock; + case Mod5Mask: return winsys::Modifier::ScrollLock; + default: return static_cast<winsys::Modifier>(0); + } + }; + + std::unordered_set<winsys::Modifier> modifiers = {}; + for (auto& x11_modifier : x11_modifiers) + if ((event.state & x11_modifier) > 0) + modifiers.insert(x11_to_modifier(x11_modifier)); + + return winsys::MouseEvent { + { + winsys::MouseCapture::MouseCaptureKind::Press, + winsys::MouseInput { + winsys::MouseInput::MouseInputTarget::Global, + button, + modifiers + }, + window, + winsys::Pos { + event.x_root, + event.y_root + } + }, + on_root + }; +} + +winsys::Event +XConnection::on_button_release() +{ + XButtonEvent event = m_current_event.xbutton; + winsys::Window window = event.window; + winsys::Window subwindow = event.subwindow; + + bool on_root = false; + if (window == m_root && subwindow == None) + on_root = true; + + if (window == m_root || window == None) + window = subwindow; + + winsys::Button button; + switch (event.button) { + case 1: button = winsys::Button::Left; break; + case 2: button = winsys::Button::Middle; break; + case 3: button = winsys::Button::Right; break; + case 4: button = winsys::Button::ScrollUp; break; + case 5: button = winsys::Button::ScrollDown; break; + case 6: button = winsys::Button::Backward; break; + case 7: button = winsys::Button::Forward; break; + default: return std::monostate{}; + } + + std::size_t x11_modifiers[] = { + ControlMask, + ShiftMask, + Mod1Mask, + Mod4Mask, + Mod2Mask, + Mod5Mask + }; + + auto x11_to_modifier = [](std::size_t x11_modifier) -> winsys::Modifier { + switch (x11_modifier) { + case ControlMask: return winsys::Modifier::Ctrl; + case ShiftMask: return winsys::Modifier::Shift; + case Mod1Mask: return winsys::Modifier::Alt; + case Mod4Mask: return winsys::Modifier::Super; + case Mod2Mask: return winsys::Modifier::NumLock; + case Mod5Mask: return winsys::Modifier::ScrollLock; + default: return static_cast<winsys::Modifier>(0); + } + }; + + std::unordered_set<winsys::Modifier> modifiers = {}; + for (auto& x11_modifier : x11_modifiers) + if ((event.state & x11_modifier) > 0) + modifiers.insert(x11_to_modifier(x11_modifier)); + + return winsys::MouseEvent { + { + winsys::MouseCapture::MouseCaptureKind::Release, + winsys::MouseInput { + winsys::MouseInput::MouseInputTarget::Global, + button, + modifiers + }, + window, + winsys::Pos { + event.x_root, + event.y_root + } + }, + on_root + }; +} + +winsys::Event +XConnection::on_circulate_request() +{ + XCirculateSubwindows( + mp_dpy, + m_current_event.xcirculaterequest.window, + m_current_event.xcirculaterequest.place + == PlaceOnTop ? RaiseLowest : LowerHighest + ); + + return std::monostate{}; +} + +winsys::Event +XConnection::on_client_message() +{ + XClientMessageEvent event = m_current_event.xclient; + winsys::Window window = event.window; + + NetWMID id; + for (id = NetWMID::NetFirst; id < NetWMID::NetLast; ++id) + if (event.message_type == get_netwm_atom(id)) + break; + + if (id >= NetWMID::NetLast) + return std::monostate{}; + + switch (id) { + case NetWMID::NetWMState: + { + for (std::size_t property = 1; property <= 2; ++property) { + if (event.data.l[property] == 0) + continue; + + if (event.data.l[0] >= static_cast<int>(NetWMAction::NetNoAction)) + break; + + Atom atom = static_cast<Atom>(event.data.l[property]); + winsys::WindowState state; + + if (atom == get_netwm_atom(NetWMID::NetWMStateFullscreen)) + state = winsys::WindowState::Fullscreen; + else if (atom == get_netwm_atom(NetWMID::NetWMStateAbove)) + state = winsys::WindowState::Above_; + else if (atom == get_netwm_atom(NetWMID::NetWMStateBelow)) + state = winsys::WindowState::Below_; + else if (atom == get_netwm_atom(NetWMID::NetWMStateDemandsAttention)) + state = winsys::WindowState::DemandsAttention; + else + return std::monostate{}; + + winsys::Toggle toggle; + switch (event.data.l[0]) { + case 0: toggle = winsys::Toggle::Off; break; + case 1: toggle = winsys::Toggle::On; break; + case 2: toggle = winsys::Toggle::Reverse; break; + default: return std::monostate{}; + } + + return winsys::StateRequestEvent { + window, + state, + toggle, + window == m_root + }; + } + + break; + } + case NetWMID::NetMoveResizeWindow: + { + std::optional<winsys::Pos> pos = {}; + std::optional<winsys::Dim> dim = {}; + + pos = winsys::Pos { + static_cast<int>(event.data.l[1]), + static_cast<int>(event.data.l[2]) + }; + + if (event.data.l[3] > 0 && event.data.l[4] > 0) + dim = winsys::Dim { + static_cast<int>(event.data.l[3]), + static_cast<int>(event.data.l[4]) + }; + + return winsys::PlacementRequestEvent { + window, + pos, + dim, + window == m_root + }; + } + case NetWMID::NetWMMoveResize: + { + winsys::Grip grip = static_cast<winsys::Grip>(0); + + switch (event.data.l[2]) { + case 0: grip |= winsys::Grip::Left; // fallthrough + case 1: grip |= winsys::Grip::Top; break; + + case 2: grip |= winsys::Grip::Top; // fallthrough + case 3: grip |= winsys::Grip::Right; break; + + case 4: grip |= winsys::Grip::Right; // fallthrough + case 5: grip |= winsys::Grip::Bottom; break; + + case 6: grip |= winsys::Grip::Left; // fallthrough + case 7: grip |= winsys::Grip::Bottom; break; + case 8: break; + default: return std::monostate{}; + } + + return winsys::GripRequestEvent { + event.window, + winsys::Pos { + static_cast<int>(event.data.l[0]), + static_cast<int>(event.data.l[1]) + }, + grip == static_cast<winsys::Grip>(0) + ? std::nullopt + : std::optional(grip), + event.window == m_root + }; + } + case NetWMID::NetActiveWindow: + { + if (event.data.l[0] <= 2) + return winsys::FocusRequestEvent { + window, + window == m_root + }; + + break; + } + case NetWMID::NetWMCloseWindow: + return winsys::CloseRequestEvent { + window, + window == m_root + }; + case NetWMID::NetRequestFrameExtents: + return winsys::FrameExtentsRequestEvent { + window, + window == m_root + }; + case NetWMID::NetCurrentDesktop: + return winsys::WorkspaceRequestEvent { + std::nullopt, + static_cast<unsigned>(event.data.l[0]), + window == m_root + }; + default: break; + } + + return std::monostate{}; +} + +winsys::Event +XConnection::on_configure_notify() +{ + XConfigureEvent event = m_current_event.xconfigure; + winsys::Window window = event.window; + + return winsys::ConfigureEvent { + window, + winsys::Region { + winsys::Pos { event.x, event.y }, + winsys::Dim { + static_cast<int>(event.width), + static_cast<int>(event.height) + } + }, + window == m_root + }; +} + +winsys::Event +XConnection::on_configure_request() +{ + XConfigureRequestEvent event = m_current_event.xconfigurerequest; + winsys::Window window = event.window; + winsys::Window sibling = event.above; + + std::optional<winsys::Region> region + = get_window_geometry(window); + + if (!region) + return std::monostate{}; + + std::optional<int> x = std::nullopt; + std::optional<int> y = std::nullopt; + std::optional<int> w = std::nullopt; + std::optional<int> h = std::nullopt; + + if ((event.value_mask & CWX) != 0) + x = event.x; + + if ((event.value_mask & CWY) != 0) + y = event.y; + + if ((event.value_mask & CWWidth) != 0) + w = event.width; + + if ((event.value_mask & CWHeight) != 0) + h = event.height; + + std::optional<winsys::Pos> pos = std::nullopt; + std::optional<winsys::Dim> dim = std::nullopt; + + if (x || y) + pos = winsys::Pos { + x ? *x : region->pos.x, + y ? *y : region->pos.y + }; + + if (w || h) + dim = winsys::Dim { + w ? *w : region->dim.w, + h ? *h : region->dim.h + }; + + if (pos || dim) + return winsys::PlacementRequestEvent { + window, + pos, + dim, + window == m_root + }; + + if ((event.value_mask & CWStackMode) != 0) { + if (sibling != None) + switch (event.detail) { + case Above: + return winsys::RestackRequestEvent { + window, + sibling, + winsys::StackMode::Above_, + window == m_root + }; + case Below: + return winsys::RestackRequestEvent { + window, + sibling, + winsys::StackMode::Below_, + window == m_root + }; + default: break; + } + } + + return std::monostate{}; +} + +winsys::Event +XConnection::on_destroy_notify() +{ + return winsys::DestroyEvent { + m_current_event.xdestroywindow.window + }; +} + +winsys::Event +XConnection::on_expose() +{ + return winsys::ExposeEvent { + m_current_event.xexpose.window + }; +} + +winsys::Event +XConnection::on_focus_in() +{ + return std::monostate{}; +} + +winsys::Event +XConnection::on_key_press() +{ + XKeyEvent event = m_current_event.xkey; + winsys::Window window = event.window; + winsys::Window subwindow = event.subwindow; + + if (window == m_root || window == None) + window = subwindow; + + winsys::Key key = get_key(event.keycode); + + std::size_t x11_modifiers[] = { + ControlMask, + ShiftMask, + Mod1Mask, + Mod4Mask, + Mod2Mask, + Mod5Mask + }; + + auto x11_to_modifier = [](std::size_t x11_modifier) -> winsys::Modifier { + switch (x11_modifier) { + case ControlMask: return winsys::Modifier::Ctrl; + case ShiftMask: return winsys::Modifier::Shift; + case Mod1Mask: return winsys::Modifier::Alt; + case Mod4Mask: return winsys::Modifier::Super; + case Mod2Mask: return winsys::Modifier::NumLock; + case Mod5Mask: return winsys::Modifier::ScrollLock; + default: return static_cast<winsys::Modifier>(0); + } + }; + + std::unordered_set<winsys::Modifier> modifiers = {}; + for (auto& x11_modifier : x11_modifiers) + if ((event.state & x11_modifier) != 0) + modifiers.insert(x11_to_modifier(x11_modifier)); + + return winsys::KeyEvent { + { + winsys::KeyInput { + key, + modifiers + }, + window + } + }; +} + +winsys::Event +XConnection::on_map_notify() +{ + XMapEvent event = m_current_event.xmap; + winsys::Window window = event.window; + + return winsys::MapEvent { + window, + !must_manage_window(event.window) + }; +} + +winsys::Event +XConnection::on_map_request() +{ + XMapRequestEvent event = m_current_event.xmaprequest; + winsys::Window window = event.window; + + return winsys::MapRequestEvent { + window, + !must_manage_window(event.window) + }; +} + +winsys::Event +XConnection::on_mapping_notify() +{ + XMappingEvent event = m_current_event.xmapping; + if (event.request == MappingKeyboard) + XRefreshKeyboardMapping(&event); + + return std::monostate{}; +} + +winsys::Event +XConnection::on_motion_notify() +{ + last_typed_event(m_current_event, MotionNotify); + + XMotionEvent event = m_current_event.xmotion; + winsys::Window window = event.window; + winsys::Window subwindow = event.subwindow; + + bool on_root = false; + if (window == m_root && subwindow == None) + on_root = true; + + if (window == m_root || window == None) + window = subwindow; + + std::size_t x11_modifiers[] = { + ControlMask, + ShiftMask, + Mod1Mask, + Mod4Mask, + Mod2Mask, + Mod5Mask + }; + + auto x11_to_modifier = [](std::size_t x11_modifier) -> winsys::Modifier { + switch (x11_modifier) { + case ControlMask: return winsys::Modifier::Ctrl; + case ShiftMask: return winsys::Modifier::Shift; + case Mod1Mask: return winsys::Modifier::Alt; + case Mod4Mask: return winsys::Modifier::Super; + case Mod2Mask: return winsys::Modifier::NumLock; + case Mod5Mask: return winsys::Modifier::ScrollLock; + default: return static_cast<winsys::Modifier>(0); + } + }; + + std::unordered_set<winsys::Modifier> modifiers = {}; + for (auto& x11_modifier : x11_modifiers) + if ((event.state & x11_modifier) != 0) + modifiers.insert(x11_to_modifier(x11_modifier)); + + return winsys::MouseEvent { + { + winsys::MouseCapture::MouseCaptureKind::Motion, + winsys::MouseInput { + winsys::MouseInput::MouseInputTarget::Global, + winsys::Button::Left, + modifiers + }, + window, + winsys::Pos { + event.x_root, + event.y_root + } + }, + on_root + }; +} + +winsys::Event +XConnection::on_property_notify() +{ + XPropertyEvent event = m_current_event.xproperty; + winsys::Window window = event.window; + + if (event.state == PropertyNewValue) { + switch (event.atom) { + case XA_WM_NAME: + return winsys::PropertyEvent { + window, + winsys::PropertyKind::Name, + window == m_root + }; + case XA_WM_CLASS: + return winsys::PropertyEvent { + window, + winsys::PropertyKind::Class, + window == m_root + }; + case XA_WM_NORMAL_HINTS: + return winsys::PropertyEvent { + window, + winsys::PropertyKind::Size, + window == m_root + }; + } + + if (event.atom == get_atom("_NET_WM_NAME")) { + return winsys::PropertyEvent { + window, + winsys::PropertyKind::Name, + window == m_root + }; + } + } + + if (event.atom == get_atom("_NET_WM_STRUT_PARTIAL") + || event.atom == get_atom("_NET_WM_STRUT")) + { + return winsys::PropertyEvent { + window, + winsys::PropertyKind::Strut, + window == m_root + }; + } + + return std::monostate{}; +} + +winsys::Event +XConnection::on_unmap_notify() +{ + XMapEvent event = m_current_event.xmap; + winsys::Window window = event.window; + + return winsys::UnmapEvent { + window, + !must_manage_window(window) + }; +} + +winsys::Event +XConnection::on_screen_change() +{ + XRRUpdateConfiguration(&m_current_event); + return winsys::ScreenChangeEvent {}; +} + +winsys::Event +XConnection::on_unimplemented() +{ + return std::monostate{}; +} + + +int +XConnection::s_otherwm_error_handler(Display*, XErrorEvent*) +{ + Util::die("another window manager is already running"); + return -1; +} + +int +XConnection::s_default_error_handler(Display*, XErrorEvent* error) +{ + static const std::unordered_map<int, int> permissible_errors({ + { X_GrabButton, BadAccess }, + { X_GrabKey, BadAccess }, + { X_CopyArea, BadDrawable }, + { X_PolyFillRectangle, BadDrawable }, + { X_PolySegment, BadDrawable }, + { X_PolyText8, BadDrawable }, + { X_ConfigureWindow, BadMatch }, + { X_SetInputFocus, BadMatch }, + }); + + if (error->error_code == BadWindow) + return 0; + + for (auto reqerr_pair : permissible_errors) + if (error->request_code == reqerr_pair.first && error->error_code == reqerr_pair.second) + return 0; + + return -1; +} + +int +XConnection::s_passthrough_error_handler(Display*, XErrorEvent*) +{ + return 0; +} + +#pragma GCC diagnostic pop diff --git a/src/winsys/xdata/xconnection.hh b/src/winsys/xdata/xconnection.hh @@ -0,0 +1,319 @@ +#ifndef __WINSYS_XDATA_XCONNECTION_H_GUARD__ +#define __WINSYS_XDATA_XCONNECTION_H_GUARD__ + +#include "../connection.hh" +#include "../event.hh" +#include "../input.hh" + +#include <cstddef> +#include <iostream> +#include <string> +#include <unordered_map> +#include <variant> + +extern "C" { +#include <X11/Xlib.h> +#include <X11/cursorfont.h> +#include <X11/Xatom.h> +#include <X11/Xmd.h> +} + +class XConnection final: public winsys::Connection +{ +public: + XConnection(); + ~XConnection(); + + virtual bool flush() override; + virtual winsys::Event step() override; + virtual std::vector<winsys::Screen> connected_outputs() override; + virtual std::vector<winsys::Window> top_level_windows() override; + virtual winsys::Pos get_pointer_position() override; + virtual void warp_pointer_center_of_window_or_root(std::optional<winsys::Window>, winsys::Screen&) override; + virtual void warp_pointer(winsys::Pos) override; + virtual void warp_pointer_rpos(winsys::Window, winsys::Pos) override; + virtual void confine_pointer(winsys::Window) override; + virtual bool release_pointer() override; + virtual void call_external_command(std::string&) override; + virtual void cleanup() override; + + // window manipulation + virtual winsys::Window create_frame(winsys::Region) override; + virtual void init_window(winsys::Window, bool) override; + virtual void init_frame(winsys::Window, bool) override; + virtual void init_unmanaged(winsys::Window) override; + virtual void init_move(winsys::Window) override; + virtual void init_resize(winsys::Window) override; + virtual void cleanup_window(winsys::Window) override; + virtual void map_window(winsys::Window) override; + virtual void unmap_window(winsys::Window) override; + virtual void reparent_window(winsys::Window, winsys::Window, winsys::Pos) override; + virtual void unparent_window(winsys::Window, winsys::Pos) override; + virtual void destroy_window(winsys::Window) override; + virtual bool close_window(winsys::Window) override; + virtual bool kill_window(winsys::Window) override; + virtual void place_window(winsys::Window, winsys::Region&) override; + virtual void move_window(winsys::Window, winsys::Pos) override; + virtual void resize_window(winsys::Window, winsys::Dim) override; + virtual void focus_window(winsys::Window) override; + virtual void stack_window_above(winsys::Window, std::optional<winsys::Window>) override; + virtual void stack_window_below(winsys::Window, std::optional<winsys::Window>) override; + virtual void insert_window_in_save_set(winsys::Window) override; + virtual void grab_bindings(std::vector<winsys::KeyInput>&, std::vector<winsys::MouseInput>&) override; + virtual void regrab_buttons(winsys::Window) override; + virtual void ungrab_buttons(winsys::Window) override; + virtual void unfocus() override; + virtual void set_window_border_width(winsys::Window, unsigned) override; + virtual void set_window_border_color(winsys::Window, unsigned) override; + virtual void set_window_background_color(winsys::Window, unsigned) override; + virtual void update_window_offset(winsys::Window, winsys::Window) override; + virtual winsys::Window get_focused_window() override; + virtual std::optional<winsys::Region> get_window_geometry(winsys::Window) override; + virtual std::optional<winsys::Pid> get_window_pid(winsys::Window) override; + virtual std::optional<winsys::Pid> get_ppid(std::optional<winsys::Pid>) override; + virtual bool must_manage_window(winsys::Window) override; + virtual bool must_free_window(winsys::Window) override; + virtual bool window_is_mappable(winsys::Window) override; + + // ICCCM + virtual void set_icccm_window_state(winsys::Window, winsys::IcccmWindowState) override; + virtual void set_icccm_window_hints(winsys::Window, winsys::Hints) override; + virtual std::string get_icccm_window_name(winsys::Window) override; + virtual std::string get_icccm_window_class(winsys::Window) override; + virtual std::string get_icccm_window_instance(winsys::Window) override; + virtual std::optional<winsys::Window> get_icccm_window_transient_for(winsys::Window) override; + virtual std::optional<winsys::Window> get_icccm_window_client_leader(winsys::Window) override; + virtual std::optional<winsys::Hints> get_icccm_window_hints(winsys::Window) override; + virtual std::optional<winsys::SizeHints> get_icccm_window_size_hints(winsys::Window, std::optional<winsys::Dim>) override; + + // EWMH + virtual void init_for_wm(std::string const&, std::vector<std::string> const&) override; + virtual void set_current_desktop(Index) override; + virtual void set_root_window_name(std::string const&) override; + virtual void set_window_desktop(winsys::Window, Index) override; + virtual void set_window_state(winsys::Window, winsys::WindowState, bool) override; + virtual void set_window_frame_extents(winsys::Window, winsys::Extents) override; + virtual void set_desktop_geometry(std::vector<winsys::Region> const&) override; + virtual void set_desktop_viewport(std::vector<winsys::Region> const&) override; + virtual void set_workarea(std::vector<winsys::Region> const&) override; + virtual void update_desktops(std::vector<std::string> const&) override; + virtual void update_client_list(std::vector<winsys::Window> const&) override; + virtual void update_client_list_stacking(std::vector<winsys::Window> const&) override; + virtual std::optional<std::vector<std::optional<winsys::Strut>>> get_window_strut(winsys::Window) override; + virtual std::optional<std::vector<std::optional<winsys::Strut>>> get_window_strut_partial(winsys::Window) override; + virtual std::optional<Index> get_window_desktop(winsys::Window) override; + virtual winsys::WindowType get_window_preferred_type(winsys::Window) override; + virtual std::vector<winsys::WindowType> get_window_types(winsys::Window) override; + virtual std::optional<winsys::WindowState> get_window_preferred_state(winsys::Window) override; + virtual std::vector<winsys::WindowState> get_window_states(winsys::Window) override; + virtual bool window_is_fullscreen(winsys::Window) override; + virtual bool window_is_above(winsys::Window) override; + virtual bool window_is_below(winsys::Window) override; + virtual bool window_is_sticky(winsys::Window) override; + +private: + static int s_otherwm_error_handler(Display*, XErrorEvent*); + static int s_passthrough_error_handler(Display*, XErrorEvent*); + static int s_default_error_handler(Display*, XErrorEvent*); + + enum NetWMID : int + { // NetWM atom identifiers + NetSupported = 0, NetFirst = NetSupported, + NetClientList, + NetNumberOfDesktops, + NetCurrentDesktop, + NetDesktopNames, + NetDesktopGeometry, + NetDesktopViewport, + NetWorkarea, + NetActiveWindow, + NetWMName, + NetWMDesktop, + NetWMStrut, + NetWMStrutPartial, + NetWMFrameExtents, + NetSupportingWMCheck, + NetWMState, + NetWMWindowType, + // root messages + NetWMCloseWindow, NetWMRootFirst = NetWMCloseWindow, + NetWMMoveResize, + NetRequestFrameExtents, + NetMoveResizeWindow, NetWMRootLast = NetMoveResizeWindow, + // window states + NetWMStateFullscreen, NetWMStateFirst = NetWMStateFullscreen, + NetWMStateAbove, + NetWMStateBelow, + NetWMStateDemandsAttention, + NetWMStateHidden, NetWMStateLast = NetWMStateHidden, + // window types + NetWMWindowTypeDesktop, NetWMWindowTypeFirst = NetWMWindowTypeDesktop, + NetWMWindowTypeDock, + NetWMWindowTypeToolbar, + NetWMWindowTypeMenu, + NetWMWindowTypeUtility, + NetWMWindowTypeSplash, + NetWMWindowTypeDialog, + NetWMWindowTypeDropdownMenu, + NetWMWindowTypePopupMenu, + NetWMWindowTypeTooltip, + NetWMWindowTypeNotification, + NetWMWindowTypeNormal, NetWMwindowtypelast = NetWMWindowTypeNormal, + NetLast + }; + + enum class NetWMAction + { + NetRemove = 0, + NetAdd = 1, + NetToggle = 2, + NetNoAction + }; + + friend inline XConnection::NetWMID& + operator++(XConnection::NetWMID& id) + { + return id = static_cast<XConnection::NetWMID>(static_cast<int>(id) + 1); + } + + Display* mp_dpy; + int m_conn_number; + winsys::Window m_root; + winsys::Window m_check_window; + int m_conn; + + XEvent m_current_event; + + int m_substructure_level = 0; + long m_prev_root_mask = 0; + + Status m_property_status = 0; + + std::optional<winsys::Window> m_confined_to; + + std::unordered_map<std::string, Atom> m_interned_atoms; + std::unordered_map<Atom, std::string> m_atom_names; + + std::unordered_map<std::size_t, winsys::Key> m_keys; + std::unordered_map<winsys::Key, std::size_t> m_keycodes; + + std::unordered_map<NetWMID, Atom> m_netwm_atoms; + + int (*m_checkwm_error_handler)(Display*, XErrorEvent*); + + template <class T> + XEvent + create_event(winsys::Window window, Atom type, T data) + { + XEvent event; + event.type = ClientMessage; + event.xclient.window = window; + event.xclient.message_type = type; + event.xclient.format = 32; + event.xclient.data.l[0] = data; + event.xclient.data.l[1] = CurrentTime; + return event; + } + + void enable_substructure_events(); + void disable_substructure_events(); + + void next_event(XEvent&); + bool typed_event(XEvent&, int); + void last_typed_event(XEvent&, int); + + void sync(bool); + int pending(); + + winsys::Window create_handle(); + + Atom get_atom(std::string const&); + Atom get_netwm_atom(NetWMID const&); + void intern_atom(std::string const&, Atom); + + winsys::Key get_key(const std::size_t); + std::size_t get_keycode(const winsys::Key); + void intern_key(const unsigned, const winsys::Key); + winsys::Button get_button(const unsigned) const; + unsigned get_buttoncode(const winsys::Button) const; + + winsys::WindowState get_window_state_from_atom(Atom); + winsys::WindowType get_window_type_from_atom(Atom); + Atom get_atom_from_window_state(winsys::WindowState); + Atom get_atom_from_window_type(winsys::WindowType); + + bool property_status_ok(); + + bool has_atom_property(winsys::Window, Atom); + bool has_atomlist_property(winsys::Window, Atom); + bool has_window_property(winsys::Window, Atom); + bool has_windowlist_property(winsys::Window, Atom); + bool has_string_property(winsys::Window, Atom); + bool has_stringlist_property(winsys::Window, Atom); + bool has_card_property(winsys::Window, Atom); + bool has_cardlist_property(winsys::Window, Atom); + + Atom get_atom_property(winsys::Window, std::string const&); + std::vector<Atom> get_atomlist_property(winsys::Window, std::string const&); + winsys::Window get_window_property(winsys::Window, std::string const&); + std::vector<winsys::Window> get_windowlist_property(winsys::Window, std::string const&); + std::string get_string_property(winsys::Window, std::string const&); + std::vector<std::string> get_stringlist_property(winsys::Window, std::string const&); + unsigned long get_card_property(winsys::Window, std::string const&); + std::vector<unsigned long> get_cardlist_property(winsys::Window, std::string const&); + + bool get_text_property(winsys::Window, Atom, char*, unsigned); + + void replace_atom_property(winsys::Window, std::string const&, Atom); + void replace_atomlist_property(winsys::Window, std::string const&, std::vector<Atom> const&); + void replace_window_property(winsys::Window, std::string const&, winsys::Window); + void replace_windowlist_property(winsys::Window, std::string const&, std::vector<winsys::Window> const&); + void replace_string_property(winsys::Window, std::string const&, std::string const&); + void replace_stringlist_property(winsys::Window, std::string const&, std::vector<std::string> const&); + void replace_card_property(winsys::Window, std::string const&, const unsigned long); + void replace_cardlist_property(winsys::Window, std::string const&, std::vector<unsigned long> const&); + + void append_atomlist_property(winsys::Window, std::string const&, Atom); + void append_windowlist_property(winsys::Window, std::string const&, winsys::Window); + void append_stringlist_property(winsys::Window, std::string const&, std::string const&); + void append_cardlist_property(winsys::Window, std::string const&, const unsigned long); + + void unset_atom_property(winsys::Window, std::string const&); + void unset_atomlist_property(winsys::Window, std::string const&); + void unset_window_property(winsys::Window, std::string const&); + void unset_windowlist_property(winsys::Window, std::string const&); + void unset_string_property(winsys::Window, std::string const&); + void unset_stringlist_property(winsys::Window, std::string const&); + void unset_card_property(winsys::Window, std::string const&); + void unset_cardlist_property(winsys::Window, std::string const&); + + bool window_is_any_of_states(winsys::Window, std::vector<winsys::WindowState> const&); + bool window_is_any_of_types(winsys::Window, std::vector<winsys::WindowType> const&); + + void check_otherwm(); + + // event dispatching logic + winsys::Event (XConnection::*m_event_dispatcher[256])(); + + winsys::Event on_button_press(); + winsys::Event on_button_release(); + winsys::Event on_circulate_request(); + winsys::Event on_client_message(); + winsys::Event on_configure_notify(); + winsys::Event on_configure_request(); + winsys::Event on_destroy_notify(); + winsys::Event on_expose(); + winsys::Event on_focus_in(); + winsys::Event on_key_press(); + winsys::Event on_map_notify(); + winsys::Event on_map_request(); + winsys::Event on_mapping_notify(); + winsys::Event on_motion_notify(); + winsys::Event on_property_notify(); + winsys::Event on_screen_change(); + winsys::Event on_unmap_notify(); + + winsys::Event on_unimplemented(); +}; + +#endif//__WINSYS_XDATA_XCONNECTION_H_GUARD__ diff --git a/xinitrc b/xinitrc @@ -0,0 +1,7 @@ +st -g 14x4+8+470 & +st & +st -g 10x10+500+10 & + +sleep 1 + +exec ./bin/kranewm