commit 864d116b90407682a6a906e918ace9d2d979a200
Author: deurzen <m.deurzen@tum.de>
Date: Mon, 21 Jun 2021 09:38:33 +0200
initial commit
Diffstat:
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