kranewl

A wlroots-based dynamic Wayland compositor, written in C++, configurable with Lua
git clone git://git.deurzen.net/kranewl
Log | Files | Refs | LICENSE

commit 41f30261047f42b328fd2068b2486b496d1eb56d
parent 23f214fb7d705c3fdd2cd6f19c789bbcdee3ea65
Author: deurzen <m.deurzen@tum.de>
Date:   Fri, 20 May 2022 00:03:48 +0200

initial server->server/model overhaul

Diffstat:
MCMakeLists.txt | 11+++++++++++
Minclude/kranewl/client.hh | 13+++++++++++--
Minclude/kranewl/common.hh | 25++++++++++++++++++++++++-
Ainclude/kranewl/cycle.hh | 199+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainclude/kranewl/cycle.t.hh | 651+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/kranewl/geometry.hh | 12------------
Ainclude/kranewl/layout.hh | 173+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/kranewl/model.hh | 9+++++++++
Ainclude/kranewl/placement.hh | 37+++++++++++++++++++++++++++++++++++++
Minclude/kranewl/server.hh | 82++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Ainclude/kranewl/util.hh | 139+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainclude/kranewl/workspace.hh | 162+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Minclude/version.hh | 4++--
Aprotocols/wlr-layer-shell-unstable-v1.xml | 390+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/kranewl/client.cc | 37++++++++++++++++++++++++++++++++++++-
Msrc/kranewl/conf/options.cc | 6++++--
Msrc/kranewl/exec.cc | 4+++-
Asrc/kranewl/layout.cc | 1548+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/kranewl/main.cc | 2+-
Msrc/kranewl/server.cc | 449+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Asrc/kranewl/util.cc | 30++++++++++++++++++++++++++++++
21 files changed, 3825 insertions(+), 158 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt @@ -12,6 +12,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) add_compile_options( + -g -fdiagnostics-show-option # -Weffc++ -Wall -Wextra -Wshadow -Wnon-virtual-dtor -pedantic @@ -31,6 +32,7 @@ add_compile_options( -Wuseless-cast -Wdouble-promotion -Wformat=2 + -DXWAYLAND ) find_program(CCACHE ccache) @@ -117,6 +119,12 @@ add_custom_command( ) add_custom_command( + OUTPUT ${CMAKE_SOURCE_DIR}/include/protocols/wlr-layer-shell-unstable-v1-protocol.h + COMMAND wayland-scanner server-header ${CMAKE_SOURCE_DIR}/protocols/wlr-layer-shell-unstable-v1.xml ${CMAKE_SOURCE_DIR}/include/protocols/wlr-layer-shell-unstable-v1-protocol.h + COMMENT "Generating layer-shell-protocol.h" +) + +add_custom_command( OUTPUT ${CMAKE_SOURCE_DIR}/include/protocols/pointer-constraints-unstable-v1-protocol.h COMMAND wayland-scanner server-header ${WAYLAND_PROTOCOLS}/unstable/pointer-constraints/pointer-constraints-unstable-v1.xml ${CMAKE_SOURCE_DIR}/include/protocols/pointer-constraints-unstable-v1-protocol.h COMMENT "Generating pointer-constraints-unstable-v1.h" @@ -124,6 +132,7 @@ add_custom_command( add_custom_target(run ALL DEPENDS ${CMAKE_SOURCE_DIR}/include/protocols/xdg-shell-protocol.h + DEPENDS ${CMAKE_SOURCE_DIR}/include/protocols/wlr-layer-shell-unstable-v1-protocol.h DEPENDS ${CMAKE_SOURCE_DIR}/include/protocols/pointer-constraints-unstable-v1-protocol.h ) @@ -144,6 +153,7 @@ pkg_check_modules(wlroots REQUIRED IMPORTED_TARGET wlroots) pkg_check_modules(pixman_1 REQUIRED IMPORTED_TARGET pixman-1) pkg_check_modules(libinput REQUIRED IMPORTED_TARGET libinput) pkg_check_modules(xkbcommon REQUIRED IMPORTED_TARGET xkbcommon) +pkg_check_modules(xcb REQUIRED IMPORTED_TARGET xcb) target_compile_features(kranewl PRIVATE cxx_std_20) target_compile_features(kranec PRIVATE cxx_std_20) @@ -163,6 +173,7 @@ target_link_libraries(kranewl PkgConfig::pixman_1 PkgConfig::libinput PkgConfig::xkbcommon + PkgConfig::xcb ) target_include_directories(kranewl PRIVATE diff --git a/include/kranewl/client.hh b/include/kranewl/client.hh @@ -14,6 +14,7 @@ extern "C" { #include <string> #include <vector> +typedef class Server* Server_ptr; typedef class Partition* Partition_ptr; typedef class Context* Context_ptr; typedef class Workspace* Workspace_ptr; @@ -40,6 +41,7 @@ typedef struct Client final || client->disowned; } + Client(); Client( Surface surface, Partition_ptr partition, @@ -56,6 +58,8 @@ typedef struct Client final OutsideState get_outside_state() const noexcept; + struct wlr_surface* get_surface() noexcept; + void focus() noexcept; void unfocus() noexcept; @@ -77,6 +81,9 @@ typedef struct Client final void set_tile_decoration(Decoration const&) noexcept; void set_free_decoration(Decoration const&) noexcept; + struct wl_list link; + Server_ptr server; + SurfaceType surface_type; Surface surface; @@ -132,9 +139,11 @@ typedef struct Client final struct wl_listener l_destroy; struct wl_listener l_set_title; struct wl_listener l_fullscreen; + struct wl_listener l_request_move; + struct wl_listener l_request_resize; #ifdef XWAYLAND - struct wl_listener l_activate; - struct wl_listener l_configure; + struct wl_listener l_request_activate; + struct wl_listener l_request_configure; struct wl_listener l_set_hints; #else struct wl_listener l_new_xdg_popup; diff --git a/include/kranewl/common.hh b/include/kranewl/common.hh @@ -1,10 +1,33 @@ #pragma once +#include <cstddef> + typedef unsigned Pid; +typedef std::size_t Ident; +typedef std::size_t Index; + +enum class Direction { + Forward, + Backward, +}; + +enum class Edge { + Left, + Right, + Top, + Bottom, +}; + +enum class Corner { + TopLeft, + TopRight, + BottomLeft, + BottomRight, +}; enum class Toggle { On, Off, - Reverse + Reverse, }; diff --git a/include/kranewl/cycle.hh b/include/kranewl/cycle.hh @@ -0,0 +1,199 @@ +#pragma once + +#include <kranewl/common.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(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(Direction) const; + Index next_index_from(Index, Direction) const; + + std::optional<Index> index_of_element(const T) const; + + std::optional<T> next_element(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); + + template<typename UnaryPredicate> + void activate_for_condition(UnaryPredicate predicate) + { + auto it = std::find_if( + m_elements.begin(), + m_elements.end(), + predicate + ); + + if (it != m_elements.end()) + activate_element(*it); + } + + bool remove_first(); + bool remove_last(); + bool remove_at_index(Index); + bool remove_element(T); + + template<typename UnaryPredicate> + bool remove_for_condition(UnaryPredicate predicate) + { + auto it = std::find_if( + m_elements.begin(), + m_elements.end(), + predicate + ); + + if (it != m_elements.end()) + return remove_element(*it); + + return false; + } + + std::optional<T> pop_back(); + + void replace_element(T, T); + void swap_elements(T, T); + void swap_indices(Index, Index); + + void reverse(); + void rotate(Direction); + void rotate_range(Direction, Index, Index); + std::optional<T> cycle_active(Direction); + std::optional<T> drag_active(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; + + typename std::deque<T>::iterator + begin() + { + return m_elements.begin(); + } + + typename std::deque<T>::const_iterator + begin() const + { + return m_elements.begin(); + } + + typename std::deque<T>::const_iterator + cbegin() const + { + return m_elements.cbegin(); + } + + typename std::deque<T>::iterator + end() + { + return m_elements.end(); + } + + typename std::deque<T>::const_iterator + end() const + { + return m_elements.end(); + } + + typename std::deque<T>::const_iterator + cend() const + { + return m_elements.cend(); + } + + 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(); + +}; diff --git a/include/kranewl/cycle.t.hh b/include/kranewl/cycle.t.hh @@ -0,0 +1,651 @@ +#pragma once + +#include <kranewl/cycle.hh> +#include <kranewl/util.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(Direction direction) const +{ + switch (direction) { + case Direction::Backward: return m_index == 0; + case 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(Direction direction) const +{ + return next_index_from(m_index, direction); +} + +template <typename T> +Index +Cycle<T>::next_index_from(Index index, Direction direction) const +{ + Index end = Util::last_index(m_elements); + + switch (direction) { + case Direction::Backward: return index == 0 ? end : index - 1; + case 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(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) +{ + std::optional<Index> index = index_of_element(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 (m_index != 0 && index && m_index >= *index) + --m_index; + + 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>::reverse() +{ + std::reverse(m_elements.begin(), m_elements.end()); +} + +template <typename T> +void +Cycle<T>::rotate(Direction direction) +{ + switch (direction) { + case Direction::Backward: + { + std::rotate( + m_elements.rbegin(), + std::next(m_elements.rbegin()), + m_elements.rend() + ); + + return; + } + case Direction::Forward: + { + std::rotate( + m_elements.begin(), + std::next(m_elements.begin()), + m_elements.end() + ); + + return; + } + default: return; + } +} + +template <typename T> +void +Cycle<T>::rotate_range(Direction direction, Index begin, Index end) +{ + if (begin >= end || begin >= m_elements.size() || end > m_elements.size()) + return; + + switch (direction) { + case Direction::Backward: + { + std::rotate( + m_elements.begin() + begin, + std::next(m_elements.begin() + begin), + m_elements.begin() + end + ); + + return; + } + case Direction::Forward: + { + std::rotate( + m_elements.rend() - end, + std::next(m_elements.rend() - end), + m_elements.rend() - begin + ); + + return; + } + default: return; + } +} + +template <typename T> +std::optional<T> +Cycle<T>::cycle_active(Direction direction) +{ + push_active_to_stack(); + m_index = next_index(direction); + return active_element(); +} + +template <typename T> +std::optional<T> +Cycle<T>::drag_active(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(); +} + diff --git a/include/kranewl/geometry.hh b/include/kranewl/geometry.hh @@ -2,18 +2,6 @@ #include <ostream> -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; diff --git a/include/kranewl/layout.hh b/include/kranewl/layout.hh @@ -0,0 +1,173 @@ +#pragma once + +#include <kranewl/cycle.hh> +#include <kranewl/placement.hh> +#include <kranewl/decoration.hh> +#include <kranewl/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, + FramelessFloat, + SingleFloat, + FramelessSingleFloat, + + // overlapping tiled layouts + Center, + Monocle, + MainDeck, + StackDeck, + DoubleDeck, + + // non-overlapping tiled layouts + Paper, + CompactPaper, + DoubleStack, + CompactDoubleStack, + HorizontalStack, + CompactHorizontalStack, + VerticalStack, + CompactVerticalStack, + }; + +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 Extents MAX_MARGIN + = Extents { 700, 700, 400, 400 }; + + LayoutData( + 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 + 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; + 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(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; + 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(Edge, Util::Change<int>); + void reset_gap_size(); + void reset_margin(); + void reset_layout_data(); + void cycle_layout_data(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(Region, placement_vector, client_iter, client_iter) const; + void arrange_frameless_float(Region, placement_vector, client_iter, client_iter) const; + void arrange_single_float(Region, placement_vector, client_iter, client_iter) const; + void arrange_frameless_single_float(Region, placement_vector, client_iter, client_iter) const; + void arrange_center(Region, placement_vector, client_iter, client_iter) const; + void arrange_monocle(Region, placement_vector, client_iter, client_iter) const; + void arrange_main_deck(Region, placement_vector, client_iter, client_iter) const; + void arrange_stack_deck(Region, placement_vector, client_iter, client_iter) const; + void arrange_double_deck(Region, placement_vector, client_iter, client_iter) const; + void arrange_paper(Region, placement_vector, client_iter, client_iter) const; + void arrange_compact_paper(Region, placement_vector, client_iter, client_iter) const; + void arrange_double_stack(Region, placement_vector, client_iter, client_iter) const; + void arrange_compact_double_stack(Region, placement_vector, client_iter, client_iter) const; + void arrange_horizontal_stack(Region, placement_vector, client_iter, client_iter) const; + void arrange_compact_horizontal_stack(Region, placement_vector, client_iter, client_iter) const; + void arrange_vertical_stack(Region, placement_vector, client_iter, client_iter) const; + void arrange_compact_vertical_stack(Region, placement_vector, client_iter, client_iter) const; + +}; diff --git a/include/kranewl/model.hh b/include/kranewl/model.hh @@ -1,5 +1,7 @@ #pragma once +#include <kranewl/cycle.hh> + #include <optional> #include <string> @@ -12,9 +14,16 @@ public: ~Model(); void run(); + void exit(); + + void spawn_external(std::string&&) const; private: Server& m_server; Config const& m_config; + /* Cycle<Partition_ptr> m_partitions; */ + /* Cycle<Context_ptr> m_contexts; */ + /* Cycle<Workspace_ptr> m_workspaces; */ + }; diff --git a/include/kranewl/placement.hh b/include/kranewl/placement.hh @@ -0,0 +1,37 @@ +#pragma once + +#include <kranewl/geometry.hh> +#include <kranewl/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; + Decoration decoration; + std::optional<Region> region; +}; diff --git a/include/kranewl/server.hh b/include/kranewl/server.hh @@ -11,9 +11,9 @@ extern "C" { #include <cstdint> #include <string> -struct View; -class Server final -{ +typedef struct Client* Client_ptr; +typedef class Server* Server_ptr; +typedef class Server final { enum class CursorMode { Passthrough, Move, @@ -28,15 +28,21 @@ public: private: static void new_output(struct wl_listener*, void*); + static void output_layout_change(struct wl_listener*, void*); static void output_manager_apply(struct wl_listener*, void*); static void output_manager_test(struct wl_listener*, void*); static void new_xdg_surface(struct wl_listener*, void*); static void new_layer_shell_surface(struct wl_listener*, void*); + static void xdg_activation(struct wl_listener*, void*); static void new_input(struct wl_listener*, void*); static void new_pointer(Server*, struct wlr_input_device*); static void new_keyboard(Server*, struct wlr_input_device*); + static void inhibit_activate(struct wl_listener*, void*); + static void inhibit_deactivate(struct wl_listener*, void*); + static void idle_inhibitor_create(struct wl_listener*, void*); + static void idle_inhibitor_destroy(struct wl_listener*, void*); static void cursor_motion(struct wl_listener*, void*); static void cursor_motion_absolute(struct wl_listener*, void*); @@ -57,17 +63,26 @@ private: static void request_set_primary_selection(struct wl_listener*, void*); static void output_frame(struct wl_listener*, void*); - static View* desktop_view_at(Server*, double, double, struct wlr_surface**, double*, double*); - static void focus_view(View*, struct wlr_surface*); + static Client_ptr desktop_client_at(Server_ptr, double, double, struct wlr_surface**, double*, double*); + static void focus_client(Client_ptr, struct wlr_surface*); static void xdg_toplevel_map(struct wl_listener*, void*); static void xdg_toplevel_unmap(struct wl_listener*, void*); static void xdg_toplevel_destroy(struct wl_listener*, void*); static void xdg_toplevel_request_move(struct wl_listener*, void*); static void xdg_toplevel_request_resize(struct wl_listener*, void*); - static void xdg_toplevel_handle_moveresize(View*, CursorMode, uint32_t); + static void xdg_toplevel_handle_moveresize(Client_ptr, CursorMode, uint32_t); + +#ifdef XWAYLAND + static void xwayland_ready(struct wl_listener*, void*); + static void new_xwayland_surface(struct wl_listener*, void*); + static void xwayland_request_activate(struct wl_listener*, void*); + static void xwayland_request_configure(struct wl_listener*, void*); + static void xwayland_set_hints(struct wl_listener*, void*); +#endif struct wl_display* m_display; + struct wl_event_loop* m_event_loop; struct wlr_backend* m_backend; struct wlr_renderer* m_renderer; @@ -75,30 +90,45 @@ private: struct wlr_compositor* m_compositor; struct wlr_data_device_manager* m_data_device_manager; +#ifdef XWAYLAND + struct wlr_xwayland* m_xwayland; +#endif + + struct wlr_output_layout* m_output_layout; struct wlr_scene* m_scene; struct wlr_xdg_shell* m_xdg_shell; - struct wl_listener ml_new_xdg_surface; - struct wl_listener ml_new_layer_shell_surface; - struct wl_list m_views; + struct wlr_layer_shell_v1* m_layer_shell; + struct wlr_xdg_activation_v1* m_xdg_activation; - struct wlr_seat* m_seat; + struct wlr_output_manager_v1* m_output_manager; struct wlr_presentation* m_presentation; struct wlr_idle* m_idle; struct wlr_server_decoration_manager* m_server_decoration_manager; struct wlr_xdg_decoration_manager_v1* m_xdg_decoration_manager; - struct wlr_output_manager_v1* m_output_manager; - struct wlr_input_inhibit_manager* m_input_inhibit_manager; - struct wlr_keyboard_shortcuts_inhibit_manager_v1* m_keyboard_shortcuts_inhibit_manager; - + struct wlr_seat* m_seat; + struct wlr_cursor* m_cursor; + struct wlr_xcursor_manager* m_cursor_manager; struct wlr_pointer_constraints_v1* m_pointer_constraints; struct wlr_relative_pointer_manager_v1* m_relative_pointer_manager; struct wlr_virtual_pointer_manager_v1* m_virtual_pointer_manager; struct wlr_virtual_keyboard_manager_v1* m_virtual_keyboard_manager; + struct wlr_input_inhibit_manager* m_input_inhibit_manager; + struct wlr_idle_inhibit_manager_v1* m_idle_inhibit_manager; + struct wlr_keyboard_shortcuts_inhibit_manager_v1* m_keyboard_shortcuts_inhibit_manager; - struct wlr_cursor* m_cursor; - struct wlr_xcursor_manager* m_cursor_mgr; + struct wl_list m_outputs; + struct wl_list m_clients; + struct wl_list m_keyboards; + + struct wl_listener ml_new_output; + struct wl_listener ml_output_layout_change; + struct wl_listener ml_output_manager_apply; + struct wl_listener ml_output_manager_test; + struct wl_listener ml_new_xdg_surface; + struct wl_listener ml_new_layer_shell_surface; + struct wl_listener ml_xdg_activation; struct wl_listener ml_cursor_motion; struct wl_listener ml_cursor_motion_absolute; struct wl_listener ml_cursor_button; @@ -107,24 +137,24 @@ private: struct wl_listener ml_request_set_cursor; struct wl_listener ml_request_start_drag; struct wl_listener ml_start_drag; - - struct wl_list m_keyboards; struct wl_listener ml_new_input; struct wl_listener ml_request_set_selection; struct wl_listener ml_request_set_primary_selection; + struct wl_listener ml_inhibit_activate; + struct wl_listener ml_inhibit_deactivate; + struct wl_listener ml_idle_inhibitor_create; + struct wl_listener ml_idle_inhibitor_destroy; +#ifdef XWAYLAND + struct wl_listener ml_xwayland_ready; + struct wl_listener ml_new_xwayland_surface; +#endif CursorMode m_cursor_mode; - View* m_grabbed_view; + Client_ptr m_grabbed_client; double m_grab_x, m_grab_y; struct wlr_box m_grab_geobox; uint32_t m_resize_edges; - struct wlr_output_layout* m_output_layout; - struct wl_list m_outputs; - struct wl_listener ml_new_output; - struct wl_listener ml_output_manager_apply; - struct wl_listener ml_output_manager_test; - const std::string m_socket; -}; +}* Server_ptr; diff --git a/include/kranewl/util.hh b/include/kranewl/util.hh @@ -0,0 +1,139 @@ +#pragma once + +#include <cstdlib> +#include <iostream> +#include <optional> +#include <string> +#include <type_traits> +#include <unordered_map> +#include <vector> + +namespace Util +{ + + void die(const std::string&&); + void warn(const std::string&&); + void assert(bool, 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; + } + +} diff --git a/include/kranewl/workspace.hh b/include/kranewl/workspace.hh @@ -0,0 +1,162 @@ +#pragma once + +#include <kranewl/common.hh> +#include <kranewl/geometry.hh> +#include <kranewl/placement.hh> +#include <kranewl/cycle.hh> +#include <kranewl/util.hh> + +typedef class Client* Client_ptr; +typedef class Context* Context_ptr; +typedef class Workspace final { +public: + Workspace(Index index, std::string name, Context_ptr context) + : m_index(index), + m_name(name), + m_layout_handler({}), + mp_context(context), + mp_active(nullptr), + m_clients({}, true), + m_icons({}, true), + m_disowned({}, true), + m_focus_follows_mouse(false) + {} + + bool empty() const; + bool contains(Client_ptr) const; + + bool focus_follows_mouse() const; + void set_focus_follows_mouse(bool); + + 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; + + Index size() const; + Index length() const; + Index main_count() const; + + Context_ptr context() const; + + Index index() const; + std::string const& name() const; + std::string identifier() const; + Client_ptr active() const; + + Cycle<Client_ptr> const& clients() const; + std::vector<Client_ptr> stack_after_focus() const; + + Client_ptr next_client() const; + Client_ptr prev_client() const; + + void cycle(Direction); + void drag(Direction); + void reverse(); + void rotate(Direction); + void shuffle_main(Direction); + void shuffle_stack(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(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(Edge, Util::Change<int>); + void reset_gap_size(); + void reset_margin(); + void reset_layout_data(); + + void save_layout(Index) const; + void load_layout(Index); + + void toggle_layout(); + void set_layout(LayoutHandler::LayoutKind); + std::vector<Placement> arrange(Region) const; + + std::deque<Client_ptr>::iterator + begin() + { + return m_clients.begin(); + } + + std::deque<Client_ptr>::const_iterator + begin() const + { + return m_clients.begin(); + } + + std::deque<Client_ptr>::const_iterator + cbegin() const + { + return m_clients.cbegin(); + } + + std::deque<Client_ptr>::iterator + end() + { + return m_clients.end(); + } + + std::deque<Client_ptr>::const_iterator + end() const + { + return m_clients.end(); + } + + std::deque<Client_ptr>::const_iterator + cend() const + { + return m_clients.cend(); + } + + Client_ptr + operator[](Index i) + { + return m_clients[i]; + } + + Client_ptr + operator[](Index i) const + { + return m_clients[i]; + } + +private: + Index m_index; + std::string m_name; + + LayoutHandler m_layout_handler; + + Context_ptr mp_context; + + Client_ptr mp_active; + Cycle<Client_ptr> m_clients; + Cycle<Client_ptr> m_icons; + Cycle<Client_ptr> m_disowned; + + bool m_focus_follows_mouse; + +}* Workspace_ptr; diff --git a/include/version.hh b/include/version.hh @@ -1 +1 @@ -#define VERSION "master/79fe788+" -\ No newline at end of file +#define VERSION "master/23f214f+" +\ No newline at end of file diff --git a/protocols/wlr-layer-shell-unstable-v1.xml b/protocols/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,390 @@ +<?xml version="1.0" encoding="UTF-8"?> +<protocol name="wlr_layer_shell_unstable_v1"> + <copyright> + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + </copyright> + + <interface name="zwlr_layer_shell_v1" version="4"> + <description summary="create surfaces that are layers of the desktop"> + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + </description> + + <request name="get_layer_surface"> + <description summary="create a layer_surface from a surface"> + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + </description> + <arg name="id" type="new_id" interface="zwlr_layer_surface_v1"/> + <arg name="surface" type="object" interface="wl_surface"/> + <arg name="output" type="object" interface="wl_output" allow-null="true"/> + <arg name="layer" type="uint" enum="layer" summary="layer to add this surface to"/> + <arg name="namespace" type="string" summary="namespace for the layer surface"/> + </request> + + <enum name="error"> + <entry name="role" value="0" summary="wl_surface has another role"/> + <entry name="invalid_layer" value="1" summary="layer value is invalid"/> + <entry name="already_constructed" value="2" summary="wl_surface has a buffer attached or committed"/> + </enum> + + <enum name="layer"> + <description summary="available layers for surfaces"> + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + </description> + + <entry name="background" value="0"/> + <entry name="bottom" value="1"/> + <entry name="top" value="2"/> + <entry name="overlay" value="3"/> + </enum> + + <!-- Version 3 additions --> + + <request name="destroy" type="destructor" since="3"> + <description summary="destroy the layer_shell object"> + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + </description> + </request> + </interface> + + <interface name="zwlr_layer_surface_v1" version="4"> + <description summary="layer metadata interface"> + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + </description> + + <request name="set_size"> + <description summary="sets the size of the surface"> + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + </description> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + </request> + + <request name="set_anchor"> + <description summary="configures the anchor point of the surface"> + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + </description> + <arg name="anchor" type="uint" enum="anchor"/> + </request> + + <request name="set_exclusive_zone"> + <description summary="configures the exclusive geometry of this surface"> + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + </description> + <arg name="zone" type="int"/> + </request> + + <request name="set_margin"> + <description summary="sets a margin from the anchor point"> + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + </description> + <arg name="top" type="int"/> + <arg name="right" type="int"/> + <arg name="bottom" type="int"/> + <arg name="left" type="int"/> + </request> + + <enum name="keyboard_interactivity"> + <description summary="types of keyboard interaction possible for a layer shell surface"> + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + </description> + + <entry name="none" value="0"> + <description summary="no keyboard focus is possible"> + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + </description> + </entry> + <entry name="exclusive" value="1"> + <description summary="request exclusive keyboard focus"> + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + </description> + </entry> + <entry name="on_demand" value="2" since="4"> + <description summary="request regular keyboard focus semantics"> + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + </description> + </entry> + </enum> + + <request name="set_keyboard_interactivity"> + <description summary="requests keyboard events"> + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + </description> + <arg name="keyboard_interactivity" type="uint" enum="keyboard_interactivity"/> + </request> + + <request name="get_popup"> + <description summary="assign this layer_surface as an xdg_popup parent"> + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + </description> + <arg name="popup" type="object" interface="xdg_popup"/> + </request> + + <request name="ack_configure"> + <description summary="ack a configure event"> + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + </description> + <arg name="serial" type="uint" summary="the serial from the configure event"/> + </request> + + <request name="destroy" type="destructor"> + <description summary="destroy the layer_surface"> + This request destroys the layer surface. + </description> + </request> + + <event name="configure"> + <description summary="suggest a surface change"> + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + </description> + <arg name="serial" type="uint"/> + <arg name="width" type="uint"/> + <arg name="height" type="uint"/> + </event> + + <event name="closed"> + <description summary="surface should be closed"> + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + </description> + </event> + + <enum name="error"> + <entry name="invalid_surface_state" value="0" summary="provided surface state is invalid"/> + <entry name="invalid_size" value="1" summary="size is invalid"/> + <entry name="invalid_anchor" value="2" summary="anchor bitfield is invalid"/> + <entry name="invalid_keyboard_interactivity" value="3" summary="keyboard interactivity is invalid"/> + </enum> + + <enum name="anchor" bitfield="true"> + <entry name="top" value="1" summary="the top edge of the anchor rectangle"/> + <entry name="bottom" value="2" summary="the bottom edge of the anchor rectangle"/> + <entry name="left" value="4" summary="the left edge of the anchor rectangle"/> + <entry name="right" value="8" summary="the right edge of the anchor rectangle"/> + </enum> + + <!-- Version 2 additions --> + + <request name="set_layer" since="2"> + <description summary="change the layer of the surface"> + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + </description> + <arg name="layer" type="uint" enum="zwlr_layer_shell_v1.layer" summary="layer to move this surface to"/> + </request> + </interface> +</protocol> diff --git a/src/kranewl/client.cc b/src/kranewl/client.cc @@ -1,5 +1,25 @@ #include <kranewl/client.hh> +// https://github.com/swaywm/wlroots/issues/682 +#include <pthread.h> +#define class class_ +#define namespace namespace_ +#define static +extern "C" { +#include <wlr/types/wlr_xdg_shell.h> +#ifdef XWAYLAND +#include <wlr/xwayland.h> +#endif +} +#undef static +#undef class +#undef namespace + +Client::Client() +{ + wl_list_init(&link); +} + Client::Client( Surface surface, Partition_ptr partition, @@ -43,7 +63,9 @@ Client::Client( last_focused(std::chrono::steady_clock::now()), managed_since(std::chrono::steady_clock::now()), m_outside_state(OutsideState::Unfocused) -{} +{ + wl_list_init(&link); +} Client::~Client() {} @@ -57,6 +79,19 @@ Client::get_outside_state() const noexcept return m_outside_state; } +struct wlr_surface* +Client::get_surface() noexcept +{ + switch (surface_type) { + case SurfaceType::XDGShell: //fallthrough + case SurfaceType::LayerShell: return surface.xdg->surface; + case SurfaceType::X11Managed: //fallthrough + case SurfaceType::X11Unmanaged: return surface.xwayland->surface; + } + + return nullptr; +} + void Client::focus() noexcept { diff --git a/src/kranewl/conf/options.cc b/src/kranewl/conf/options.cc @@ -3,11 +3,13 @@ #include <kranewl/conf/options.hh> #include <cassert> -#include <optional> -#include <iostream> #include <cstdlib> +#include <iostream> +#include <optional> +extern "C" { #include <sys/stat.h> #include <unistd.h> +} static const std::string CONFIG_FILE = "kranewlrc.lua"; static const std::string DEFAULT_CONFIG = "/etc/kranewl/" + CONFIG_FILE; diff --git a/src/kranewl/exec.cc b/src/kranewl/exec.cc @@ -1,6 +1,8 @@ -#include <unistd.h> #include <cstdlib> #include <string> +extern "C" { +#include <unistd.h> +} void exec_external(std::string& command) { diff --git a/src/kranewl/layout.cc b/src/kranewl/layout.cc @@ -0,0 +1,1548 @@ +#include <kranewl/layout.hh> + +#include <kranewl/util.hh> +#include <kranewl/client.hh> +#include <kranewl/cycle.t.hh> + +#include <algorithm> +#include <cmath> +#include <sstream> +#include <fstream> + +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({ +#define NEW_LAYOUT(layout) { layout, new Layout(layout) } + NEW_LAYOUT(LayoutKind::Float), + NEW_LAYOUT(LayoutKind::FramelessFloat), + NEW_LAYOUT(LayoutKind::SingleFloat), + NEW_LAYOUT(LayoutKind::FramelessSingleFloat), + NEW_LAYOUT(LayoutKind::Center), + NEW_LAYOUT(LayoutKind::Monocle), + NEW_LAYOUT(LayoutKind::MainDeck), + NEW_LAYOUT(LayoutKind::StackDeck), + NEW_LAYOUT(LayoutKind::DoubleDeck), + NEW_LAYOUT(LayoutKind::Paper), + NEW_LAYOUT(LayoutKind::CompactPaper), + NEW_LAYOUT(LayoutKind::DoubleStack), + NEW_LAYOUT(LayoutKind::CompactDoubleStack), + NEW_LAYOUT(LayoutKind::HorizontalStack), + NEW_LAYOUT(LayoutKind::CompactHorizontalStack), + NEW_LAYOUT(LayoutKind::VerticalStack), + NEW_LAYOUT(LayoutKind::CompactVerticalStack), +#undef NEW_LAYOUT + }), + 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::FramelessFloat: + { + arrange_frameless_float(screen_region, placements, begin, end); + break; + } + case LayoutKind::SingleFloat: + { + arrange_single_float(screen_region, placements, begin, end); + break; + } + case LayoutKind::FramelessSingleFloat: + { + arrange_frameless_single_float(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::MainDeck: + { + arrange_main_deck(screen_region, placements, begin, end); + break; + } + case LayoutKind::StackDeck: + { + arrange_stack_deck(screen_region, placements, begin, end); + break; + } + case LayoutKind::DoubleDeck: + { + arrange_double_deck(screen_region, placements, begin, end); + break; + } + case LayoutKind::Paper: + { + arrange_paper(screen_region, placements, begin, end); + break; + } + case LayoutKind::CompactPaper: + { + arrange_compact_paper(screen_region, placements, begin, end); + break; + } + case LayoutKind::DoubleStack: + { + arrange_double_stack(screen_region, placements, begin, end); + break; + } + case LayoutKind::CompactDoubleStack: + { + arrange_compact_double_stack(screen_region, placements, begin, end); + break; + } + case LayoutKind::HorizontalStack: + { + arrange_horizontal_stack(screen_region, placements, begin, end); + break; + } + case LayoutKind::CompactHorizontalStack: + { + arrange_compact_horizontal_stack(screen_region, placements, begin, end); + break; + } + case LayoutKind::VerticalStack: + { + arrange_vertical_stack(screen_region, placements, begin, end); + break; + } + case LayoutKind::CompactVerticalStack: + { + arrange_compact_vertical_stack(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 = static_cast<std::size_t>(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 = static_cast<std::size_t>(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_frameless_float( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_float(screen_region, placements, begin, end); +} + +void +LayoutHandler::arrange_single_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->focused ? std::optional(client->free_region) : std::nullopt + }; + } + ); +} + +void +LayoutHandler::arrange_frameless_single_float( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_single_float(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_main_deck( + 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 : 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) { + ++i; + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + Region { + Pos { + screen_region.pos.x, + screen_region.pos.y + }, + 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_stack_deck( + 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 : 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 { + ++i; + return Placement { + mp_layout->config.method, + client, + mp_layout->config.decoration, + Region { + Pos { + x_stack, + screen_region.pos.y + }, + Dim { + w_stack, + h_stack + } + } + }; + } + } + ); +} + +void +LayoutHandler::arrange_double_deck( + 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 : 0; + int h_stack = n_stack > 0 ? screen_region.dim.h : 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 + }, + 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 + }, + Dim { + w_stack, + h_stack + } + } + }; + } + } + ); +} + +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_compact_paper( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_paper(screen_region, placements, begin, end); +} + +void +LayoutHandler::arrange_double_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_compact_double_stack( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_double_stack(screen_region, placements, begin, end); +} + +void +LayoutHandler::arrange_horizontal_stack( + 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_compact_horizontal_stack( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_horizontal_stack(screen_region, placements, begin, end); +} + +void +LayoutHandler::arrange_vertical_stack( + 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_compact_vertical_stack( + Region screen_region, + placement_vector placements, + client_iter begin, + client_iter end +) const +{ + arrange_vertical_stack(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::FramelessFloat: + { + 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::FramelessSingleFloat: + { + 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::MainDeck: + { + 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::StackDeck: + { + 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::DoubleDeck: + { + 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::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::CompactPaper: + { + 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::DoubleStack: + { + 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::CompactDoubleStack: + { + 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::HorizontalStack: + { + 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::CompactHorizontalStack: + { + 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::VerticalStack: + { + 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::CompactVerticalStack: + { + 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::Center: + { + return Layout::LayoutData { + Extents { 0, 0, 0, 0 }, + 0, + 5, + .40f + }; + } + case LayoutKind::Float: // fallthrough + case LayoutKind::FramelessFloat: // fallthrough + case LayoutKind::SingleFloat: // fallthrough + case LayoutKind::FramelessSingleFloat: // fallthrough + case LayoutKind::Monocle: // fallthrough + case LayoutKind::MainDeck: // fallthrough + case LayoutKind::StackDeck: // fallthrough + case LayoutKind::DoubleDeck: // fallthrough + case LayoutKind::Paper: // fallthrough + case LayoutKind::CompactPaper: // fallthrough + case LayoutKind::DoubleStack: // fallthrough + case LayoutKind::CompactDoubleStack: // fallthrough + case LayoutKind::HorizontalStack: // fallthrough + case LayoutKind::CompactHorizontalStack: // fallthrough + case LayoutKind::VerticalStack: // fallthrough + case LayoutKind::CompactVerticalStack: + { + 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/kranewl/main.cc b/src/kranewl/main.cc @@ -22,7 +22,7 @@ int main(int argc, char** argv) { #ifndef NDEBUG - wlr_log_init(WLR_DEBUG, NULL); + /* wlr_log_init(WLR_DEBUG, NULL); */ #endif const Options options = parse_options(argc, argv); diff --git a/src/kranewl/server.cc b/src/kranewl/server.cc @@ -1,9 +1,9 @@ #include <kranewl/server.hh> +#include <kranewl/client.hh> +#include <kranewl/exec.hh> #include <kranewl/input/keyboard.hh> #include <kranewl/tree/output.hh> -#include <kranewl/tree/view.hh> -#include <kranewl/exec.hh> #include <spdlog/spdlog.h> @@ -23,16 +23,20 @@ extern "C" { #include <wlr/types/wlr_cursor.h> #include <wlr/types/wlr_data_device.h> #include <wlr/types/wlr_idle.h> +#include <wlr/types/wlr_idle_inhibit_v1.h> #include <wlr/types/wlr_input_device.h> #include <wlr/types/wlr_input_inhibitor.h> #include <wlr/types/wlr_keyboard.h> #include <wlr/types/wlr_keyboard_shortcuts_inhibit_v1.h> +#include <wlr/types/wlr_layer_shell_v1.h> #include <wlr/types/wlr_output.h> #include <wlr/types/wlr_output_layout.h> #include <wlr/types/wlr_output_management_v1.h> #include <wlr/types/wlr_pointer.h> #include <wlr/types/wlr_pointer_constraints_v1.h> #include <wlr/types/wlr_presentation_time.h> +#include <wlr/types/wlr_primary_selection.h> +#include <wlr/types/wlr_primary_selection_v1.h> #include <wlr/types/wlr_relative_pointer_v1.h> #include <wlr/types/wlr_relative_pointer_v1.h> #include <wlr/types/wlr_scene.h> @@ -41,11 +45,13 @@ extern "C" { #include <wlr/types/wlr_virtual_keyboard_v1.h> #include <wlr/types/wlr_virtual_pointer_v1.h> #include <wlr/types/wlr_xcursor_manager.h> +#include <wlr/types/wlr_xdg_activation_v1.h> #include <wlr/types/wlr_xdg_decoration_v1.h> #include <wlr/types/wlr_xdg_output_v1.h> #include <wlr/types/wlr_xdg_shell.h> #include <wlr/util/box.h> -#ifdef WLR_HAS_XWAYLAND +#ifdef XWAYLAND +#include <X11/Xlib.h> #include <wlr/xwayland.h> #endif } @@ -61,6 +67,7 @@ extern "C" { Server::Server() : m_display(wl_display_create()), + m_event_loop(wl_display_get_event_loop(m_display)), m_backend(wlr_backend_autocreate(m_display)), m_renderer([](struct wl_display* display, struct wlr_backend* backend) { struct wlr_renderer* renderer = wlr_renderer_autocreate(backend); @@ -69,8 +76,13 @@ Server::Server() return renderer; }(m_display, m_backend)), m_allocator(wlr_allocator_autocreate(m_backend, m_renderer)), + ml_new_output({ .notify = Server::new_output }), + ml_output_layout_change({ .notify = Server::output_layout_change }), + ml_output_manager_apply({ .notify = Server::output_manager_apply }), + ml_output_manager_test({ .notify = Server::output_manager_test }), ml_new_xdg_surface({ .notify = Server::new_xdg_surface }), ml_new_layer_shell_surface({ .notify = Server::new_layer_shell_surface }), + ml_xdg_activation({ .notify = Server::xdg_activation }), ml_cursor_motion({ .notify = Server::cursor_motion }), ml_cursor_motion_absolute({ .notify = Server::cursor_motion_absolute }), ml_cursor_button({ .notify = Server::cursor_button }), @@ -81,9 +93,15 @@ Server::Server() ml_start_drag({ .notify = Server::start_drag }), ml_new_input({ .notify = Server::new_input }), ml_request_set_selection({ .notify = Server::request_set_selection }), - ml_new_output({ .notify = Server::new_output }), - ml_output_manager_apply({ .notify = Server::output_manager_apply }), - ml_output_manager_test({ .notify = Server::output_manager_test }), + ml_request_set_primary_selection({ .notify = Server::request_set_primary_selection }), + ml_inhibit_activate({ .notify = Server::inhibit_activate }), + ml_inhibit_deactivate({ .notify = Server::inhibit_deactivate }), + ml_idle_inhibitor_create({ .notify = Server::idle_inhibitor_create }), + ml_idle_inhibitor_destroy({ .notify = Server::idle_inhibitor_destroy }), +#ifdef XWAYLAND + ml_xwayland_ready({ .notify = Server::xwayland_ready }), + ml_new_xwayland_surface({ .notify = Server::new_xwayland_surface }), +#endif m_socket(wl_display_add_socket_auto(m_display)) { m_compositor = wlr_compositor_create(m_display, m_renderer); @@ -97,20 +115,27 @@ Server::Server() m_scene = wlr_scene_create(); wlr_scene_attach_output_layout(m_scene, m_output_layout); - wl_list_init(&m_views); + wl_list_init(&m_clients); + + m_layer_shell = wlr_layer_shell_v1_create(m_display); + wl_signal_add(&m_layer_shell->events.new_surface, &ml_new_layer_shell_surface); + m_xdg_shell = wlr_xdg_shell_create(m_display); wl_signal_add(&m_xdg_shell->events.new_surface, &ml_new_xdg_surface); m_cursor = wlr_cursor_create(); wlr_cursor_attach_output_layout(m_cursor, m_output_layout); - m_cursor_mgr = wlr_xcursor_manager_create(NULL, 24); - wlr_xcursor_manager_load(m_cursor_mgr, 1); + m_cursor_manager = wlr_xcursor_manager_create(NULL, 24); + wlr_xcursor_manager_load(m_cursor_manager, 1); m_seat = wlr_seat_create(m_display, "seat0"); m_presentation = wlr_presentation_create(m_display, m_backend); m_idle = wlr_idle_create(m_display); + m_idle_inhibit_manager = wlr_idle_inhibit_v1_create(m_display); + wl_signal_add(&m_idle_inhibit_manager->events.new_inhibitor, &ml_idle_inhibitor_create); + { // set up cursor handling wl_signal_add(&m_cursor->events.motion, &ml_cursor_motion); wl_signal_add(&m_cursor->events.motion_absolute, &ml_cursor_motion_absolute); @@ -136,17 +161,45 @@ Server::Server() ); wlr_xdg_output_manager_v1_create(m_display, m_output_layout); + wl_signal_add(&m_output_layout->events.change, &ml_output_layout_change); + m_output_manager = wlr_output_manager_v1_create(m_display); - wl_signal_add(&m_output_manager->events.apply, &ml_output_manager_apply); - wl_signal_add(&m_output_manager->events.test, &ml_output_manager_test); + wl_signal_add(&m_output_manager->events.apply, &ml_output_manager_apply); + wl_signal_add(&m_output_manager->events.test, &ml_output_manager_test); + + wlr_scene_set_presentation(m_scene, wlr_presentation_create(m_display, m_backend)); + +#ifdef XWAYLAND + m_xwayland = wlr_xwayland_create(m_display, m_compositor, 1); + + if (m_xwayland) { + wl_signal_add(&m_xwayland->events.ready, &ml_xwayland_ready); + wl_signal_add(&m_xwayland->events.new_surface, &ml_new_xwayland_surface); + + setenv("DISPLAY", m_xwayland->display_name, 1); + } else + spdlog::error("Failed to initiate XWayland"); + spdlog::warn("Continuing without XWayland functionality"); +#endif m_input_inhibit_manager = wlr_input_inhibit_manager_create(m_display); + wl_signal_add(&m_input_inhibit_manager->events.activate, &ml_inhibit_activate); + wl_signal_add(&m_input_inhibit_manager->events.deactivate, &ml_inhibit_deactivate); + m_keyboard_shortcuts_inhibit_manager = wlr_keyboard_shortcuts_inhibit_v1_create(m_display); + // TODO: m_keyboard_shortcuts_inhibit_manager signals m_pointer_constraints = wlr_pointer_constraints_v1_create(m_display); + // TODO: m_pointer_constraints signals + m_relative_pointer_manager = wlr_relative_pointer_manager_v1_create(m_display); + // TODO: m_relative_pointer_manager signals + m_virtual_pointer_manager = wlr_virtual_pointer_manager_v1_create(m_display); + // TODO: m_virtual_pointer_manager signals + m_virtual_keyboard_manager = wlr_virtual_keyboard_manager_v1_create(m_display); + // TODO: m_virtual_keyboard_manager signals if (m_socket.empty()) { wlr_backend_destroy(m_backend); @@ -185,7 +238,7 @@ Server::start() noexcept void Server::new_output(struct wl_listener* listener, void* data) { - Server* server = wl_container_of(listener, server, ml_new_output); + Server_ptr server = wl_container_of(listener, server, ml_new_output); struct wlr_output* wlr_output = reinterpret_cast<struct wlr_output*>(data); @@ -211,6 +264,12 @@ Server::new_output(struct wl_listener* listener, void* data) } void +Server::output_layout_change(struct wl_listener* listener, void* data) +{ + // TODO +} + +void Server::output_manager_apply(struct wl_listener* listener, void* data) { // TODO @@ -225,7 +284,7 @@ Server::output_manager_test(struct wl_listener* listener, void* data) void Server::new_xdg_surface(struct wl_listener* listener, void* data) { - Server* server = wl_container_of(listener, server, ml_new_xdg_surface); + Server_ptr server = wl_container_of(listener, server, ml_new_xdg_surface); struct wlr_xdg_surface* xdg_surface = reinterpret_cast<struct wlr_xdg_surface*>(data); @@ -241,28 +300,32 @@ Server::new_xdg_surface(struct wl_listener* listener, void* data) } assert(xdg_surface->role == WLR_XDG_SURFACE_ROLE_TOPLEVEL); - View* view = reinterpret_cast<View*>(calloc(1, sizeof(View))); - view->server = server; - view->xdg_surface = xdg_surface; - view->scene_node = wlr_scene_xdg_surface_create( - &view->server->m_scene->node, - view->xdg_surface + Client_ptr client = new Client; + + client->server = server; + xdg_surface->data = client->scene; + + client->surface = Surface{ .xdg = xdg_surface }; + client->surface_type = SurfaceType::XDGShell; + + client->scene = wlr_scene_xdg_surface_create( + &client->server->m_scene->node, + client->surface.xdg ); - view->scene_node->data = view; - xdg_surface->data = view->scene_node; + client->scene->data = client; - view->l_map.notify = xdg_toplevel_map; - wl_signal_add(&xdg_surface->events.map, &view->l_map); - view->l_unmap.notify = xdg_toplevel_unmap; - wl_signal_add(&xdg_surface->events.unmap, &view->l_unmap); - view->l_destroy.notify = xdg_toplevel_destroy; - wl_signal_add(&xdg_surface->events.destroy, &view->l_destroy); + client->l_map.notify = xdg_toplevel_map; + wl_signal_add(&xdg_surface->events.map, &client->l_map); + client->l_unmap.notify = xdg_toplevel_unmap; + wl_signal_add(&xdg_surface->events.unmap, &client->l_unmap); + client->l_destroy.notify = xdg_toplevel_destroy; + wl_signal_add(&xdg_surface->events.destroy, &client->l_destroy); struct wlr_xdg_toplevel* toplevel = xdg_surface->toplevel; - view->l_request_move.notify = xdg_toplevel_request_move; - wl_signal_add(&toplevel->events.request_move, &view->l_request_move); - view->l_request_resize.notify = xdg_toplevel_request_resize; - wl_signal_add(&toplevel->events.request_resize, &view->l_request_resize); + client->l_request_move.notify = xdg_toplevel_request_move; + wl_signal_add(&toplevel->events.request_move, &client->l_request_move); + client->l_request_resize.notify = xdg_toplevel_request_resize; + wl_signal_add(&toplevel->events.request_resize, &client->l_request_resize); } void @@ -272,9 +335,15 @@ Server::new_layer_shell_surface(struct wl_listener* listener, void* data) } void +Server::xdg_activation(struct wl_listener* listener, void* data) +{ + // TODO +} + +void Server::new_input(struct wl_listener* listener, void* data) { - Server* server = wl_container_of(listener, server, ml_new_input); + Server_ptr server = wl_container_of(listener, server, ml_new_input); struct wlr_input_device* device = reinterpret_cast<struct wlr_input_device*>(data); switch (device->type) { @@ -296,13 +365,13 @@ Server::new_input(struct wl_listener* listener, void* data) } void -Server::new_pointer(Server* server, struct wlr_input_device* device) +Server::new_pointer(Server_ptr server, struct wlr_input_device* device) { wlr_cursor_attach_input_device(server->m_cursor, device); } void -Server::new_keyboard(Server* server, struct wlr_input_device* device) +Server::new_keyboard(Server_ptr server, struct wlr_input_device* device) { Keyboard* keyboard = reinterpret_cast<Keyboard*>(calloc(1, sizeof(Keyboard))); keyboard->server = server; @@ -330,9 +399,33 @@ Server::new_keyboard(Server* server, struct wlr_input_device* device) } void +Server::inhibit_activate(struct wl_listener* listener, void* data) +{ + // TODO +} + +void +Server::inhibit_deactivate(struct wl_listener* listener, void* data) +{ + // TODO +} + +void +Server::idle_inhibitor_create(struct wl_listener* listener, void* data) +{ + // TODO +} + +void +Server::idle_inhibitor_destroy(struct wl_listener* listener, void* data) +{ + // TODO +} + +void Server::cursor_motion(struct wl_listener* listener, void* data) { - Server* server = wl_container_of(listener, server, ml_cursor_motion); + Server_ptr server = wl_container_of(listener, server, ml_cursor_motion); struct wlr_event_pointer_motion* event = reinterpret_cast<wlr_event_pointer_motion*>(data); @@ -343,7 +436,7 @@ Server::cursor_motion(struct wl_listener* listener, void* data) void Server::cursor_motion_absolute(struct wl_listener* listener, void* data) { - Server* server = wl_container_of(listener, server, ml_cursor_motion_absolute); + Server_ptr server = wl_container_of(listener, server, ml_cursor_motion_absolute); struct wlr_event_pointer_motion_absolute* event = reinterpret_cast<wlr_event_pointer_motion_absolute*>(data); @@ -354,7 +447,7 @@ Server::cursor_motion_absolute(struct wl_listener* listener, void* data) void Server::cursor_button(struct wl_listener* listener, void* data) { - Server* server = wl_container_of(listener, server, ml_cursor_button); + Server_ptr server = wl_container_of(listener, server, ml_cursor_button); struct wlr_event_pointer_button* event = reinterpret_cast<wlr_event_pointer_button*>(data); @@ -369,7 +462,7 @@ Server::cursor_button(struct wl_listener* listener, void* data) struct wlr_surface* surface = NULL; double sx, sy; - View* view = desktop_view_at( + Client_ptr client = desktop_client_at( server, server->m_cursor->x, server->m_cursor->y, @@ -381,13 +474,13 @@ Server::cursor_button(struct wl_listener* listener, void* data) if (event->state == WLR_BUTTON_RELEASED) server->m_cursor_mode = Server::CursorMode::Passthrough; else - focus_view(view, surface); + focus_client(client, surface); } void Server::cursor_axis(struct wl_listener* listener, void* data) { - Server* server = wl_container_of(listener, server, ml_cursor_axis); + Server_ptr server = wl_container_of(listener, server, ml_cursor_axis); struct wlr_event_pointer_axis* event = reinterpret_cast<wlr_event_pointer_axis*>(data); @@ -405,7 +498,7 @@ Server::cursor_axis(struct wl_listener* listener, void* data) void Server::cursor_frame(struct wl_listener* listener, void* data) { - Server* server = wl_container_of(listener, server, ml_cursor_frame); + Server_ptr server = wl_container_of(listener, server, ml_cursor_frame); wlr_seat_pointer_notify_frame(server->m_seat); } @@ -413,7 +506,7 @@ Server::cursor_frame(struct wl_listener* listener, void* data) void Server::request_set_cursor(struct wl_listener* listener, void* data) { - Server* server = wl_container_of(listener, server, ml_request_set_cursor); + Server_ptr server = wl_container_of(listener, server, ml_request_set_cursor); struct wlr_seat_pointer_request_set_cursor_event* event = reinterpret_cast<struct wlr_seat_pointer_request_set_cursor_event*>(data); @@ -429,7 +522,7 @@ Server::request_set_cursor(struct wl_listener* listener, void* data) } void -Server::cursor_process_motion(Server* server, uint32_t time) +Server::cursor_process_motion(Server_ptr server, uint32_t time) { switch (server->m_cursor_mode) { case Server::CursorMode::Move: cursor_process_move(server, time); return; @@ -441,7 +534,7 @@ Server::cursor_process_motion(Server* server, uint32_t time) struct wlr_surface* surface = NULL; double sx, sy; - View* view = desktop_view_at( + Client_ptr client = desktop_client_at( server, server->m_cursor->x, server->m_cursor->y, @@ -450,9 +543,9 @@ Server::cursor_process_motion(Server* server, uint32_t time) &sy ); - if (!view) + if (!client) wlr_xcursor_manager_set_cursor_image( - server->m_cursor_mgr, + server->m_cursor_manager, "left_ptr", server->m_cursor ); @@ -465,20 +558,24 @@ Server::cursor_process_motion(Server* server, uint32_t time) } void -Server::cursor_process_move(Server* server, uint32_t time) +Server::cursor_process_move(Server_ptr server, uint32_t time) { - View* view = server->m_grabbed_view; + Client_ptr client = server->m_grabbed_client; - view->x = server->m_cursor->x - server->m_grab_x; - view->y = server->m_cursor->y - server->m_grab_y; + client->active_region.pos.x = server->m_cursor->x - server->m_grab_x; + client->active_region.pos.y = server->m_cursor->y - server->m_grab_y; - wlr_scene_node_set_position(view->scene_node, view->x, view->y); + wlr_scene_node_set_position( + client->scene, + client->active_region.pos.x, + client->active_region.pos.y + ); } void -Server::cursor_process_resize(Server* server, uint32_t time) +Server::cursor_process_resize(Server_ptr server, uint32_t time) { - View* view = server->m_grabbed_view; + Client_ptr client = server->m_grabbed_client; double border_x = server->m_cursor->x - server->m_grab_x; double border_y = server->m_cursor->y - server->m_grab_y; @@ -513,14 +610,25 @@ Server::cursor_process_resize(Server* server, uint32_t time) } struct wlr_box geo_box; - wlr_xdg_surface_get_geometry(view->xdg_surface, &geo_box); - view->x = new_left - geo_box.x; - view->y = new_top - geo_box.y; - wlr_scene_node_set_position(view->scene_node, view->x, view->y); + wlr_xdg_surface_get_geometry(client->surface.xdg, &geo_box); + + client->active_region.pos.x = new_left - geo_box.x; + client->active_region.pos.y = new_top - geo_box.y; + + wlr_scene_node_set_position( + client->scene, + client->active_region.pos.x, + client->active_region.pos.y + ); int new_width = new_right - new_left; int new_height = new_bottom - new_top; - wlr_xdg_toplevel_set_size(view->xdg_surface, new_width, new_height); + + wlr_xdg_toplevel_set_size( + client->surface.xdg, + new_width, + new_height + ); } void @@ -551,7 +659,7 @@ void Server::keyboard_handle_key(struct wl_listener* listener, void* data) { Keyboard* keyboard = wl_container_of(listener, keyboard, l_key); - Server* server = keyboard->server; + Server_ptr server = keyboard->server; struct wlr_event_keyboard_key* event = reinterpret_cast<struct wlr_event_keyboard_key*>(data); @@ -586,7 +694,7 @@ Server::keyboard_handle_key(struct wl_listener* listener, void* data) } bool -Server::keyboard_handle_keybinding(Server* server, xkb_keysym_t sym) +Server::keyboard_handle_keybinding(Server_ptr server, xkb_keysym_t sym) { switch (sym) { case XKB_KEY_Escape: @@ -594,11 +702,19 @@ Server::keyboard_handle_keybinding(Server* server, xkb_keysym_t sym) break; case XKB_KEY_j: { - if (wl_list_length(&server->m_views) < 2) + if (wl_list_length(&server->m_clients) < 2) break; - View* prev_view = wl_container_of(server->m_views.prev, prev_view, link); - focus_view(prev_view, prev_view->xdg_surface->surface); + Client_ptr prev_client = wl_container_of( + server->m_clients.prev, + prev_client, + link + ); + + focus_client( + prev_client, + prev_client->get_surface() + ); } break; case XKB_KEY_Return: @@ -607,6 +723,12 @@ Server::keyboard_handle_keybinding(Server* server, xkb_keysym_t sym) exec_external(foot); } break; + case XKB_KEY_semicolon: + { + std::string st = "st"; + exec_external(st); + } + break; default: return false; } @@ -616,7 +738,7 @@ Server::keyboard_handle_keybinding(Server* server, xkb_keysym_t sym) void Server::request_set_selection(struct wl_listener* listener, void* data) { - Server* server = wl_container_of(listener, server, ml_request_set_selection); + Server_ptr server = wl_container_of(listener, server, ml_request_set_selection); struct wlr_seat_request_set_selection_event* event = reinterpret_cast<struct wlr_seat_request_set_selection_event*>(data); @@ -625,6 +747,17 @@ Server::request_set_selection(struct wl_listener* listener, void* data) } void +Server::request_set_primary_selection(struct wl_listener* listener, void* data) +{ + Server_ptr server = wl_container_of(listener, server, ml_request_set_primary_selection); + + struct wlr_seat_request_set_primary_selection_event* event + = reinterpret_cast<struct wlr_seat_request_set_primary_selection_event*>(data); + + wlr_seat_set_primary_selection(server->m_seat, event->source, event->serial); +} + +void Server::output_frame(struct wl_listener* listener, void* data) { Output* output = wl_container_of(listener, output, l_frame); @@ -640,9 +773,9 @@ Server::output_frame(struct wl_listener* listener, void* data) wlr_scene_output_send_frame_done(scene_output, &now); } -View* -Server::desktop_view_at( - Server* server, +Client_ptr +Server::desktop_client_at( + Server_ptr server, double lx, double ly, struct wlr_surface** surface, @@ -664,39 +797,44 @@ Server::desktop_view_at( while (node && !node->data) node = node->parent; - return reinterpret_cast<View*>(node->data); + return reinterpret_cast<Client_ptr>(node->data); } void -Server::focus_view(View* view, struct wlr_surface* surface) +Server::focus_client(Client_ptr client, struct wlr_surface* surface) { - if (!view) + if (!client) return; - Server* server = view->server; + Server_ptr server = client->server; struct wlr_seat* seat = server->m_seat; struct wlr_surface* prev_surface = seat->keyboard_state.focused_surface; if (prev_surface == surface) return; - if (prev_surface) - wlr_xdg_toplevel_set_activated( - wlr_xdg_surface_from_wlr_surface( - seat->keyboard_state.focused_surface - ), - false - ); + /* if (prev_surface) */ + /* wlr_xdg_toplevel_set_activated( */ + /* wlr_xdg_surface_from_wlr_surface( */ + /* seat->keyboard_state.focused_surface */ + /* ), */ + /* false */ + /* ); */ struct wlr_keyboard* keyboard = wlr_seat_get_keyboard(seat); - wlr_scene_node_raise_to_top(view->scene_node); - wl_list_remove(&view->link); - wl_list_insert(&server->m_views, &view->link); - wlr_xdg_toplevel_set_activated(view->xdg_surface, true); + if (client->scene) + wlr_scene_node_raise_to_top(client->scene); + + wl_list_remove(&client->link); + wl_list_insert(&server->m_clients, &client->link); + + if (client->surface_type == SurfaceType::XDGShell || client->surface_type == SurfaceType::LayerShell) + wlr_xdg_toplevel_set_activated(client->surface.xdg, true); + wlr_seat_keyboard_notify_enter( seat, - view->xdg_surface->surface, + client->get_surface(), keyboard->keycodes, keyboard->num_keycodes, &keyboard->modifiers @@ -706,38 +844,40 @@ Server::focus_view(View* view, struct wlr_surface* surface) void Server::xdg_toplevel_map(struct wl_listener* listener, void* data) { - View* view = wl_container_of(listener, view, l_map); + Client_ptr client = wl_container_of(listener, client, l_map); + + printf("Address of x is %p\n", (void *)client); - wl_list_insert(&view->server->m_views, &view->link); - focus_view(view, view->xdg_surface->surface); + wl_list_insert(&client->server->m_clients, &client->link); + focus_client(client, client->get_surface()); } void Server::xdg_toplevel_unmap(struct wl_listener* listener, void* data) { - View* view = wl_container_of(listener, view, l_unmap); - wl_list_remove(&view->link); + Client_ptr client = wl_container_of(listener, client, l_unmap); + wl_list_remove(&client->link); } void Server::xdg_toplevel_destroy(struct wl_listener* listener, void* data) { - View* view = wl_container_of(listener, view, l_destroy); + Client_ptr client = wl_container_of(listener, client, l_destroy); - wl_list_remove(&view->l_map.link); - wl_list_remove(&view->l_unmap.link); - wl_list_remove(&view->l_destroy.link); - wl_list_remove(&view->l_request_move.link); - wl_list_remove(&view->l_request_resize.link); + wl_list_remove(&client->l_map.link); + wl_list_remove(&client->l_unmap.link); + wl_list_remove(&client->l_destroy.link); + wl_list_remove(&client->l_request_move.link); + wl_list_remove(&client->l_request_resize.link); - free(view); + delete client; } void Server::xdg_toplevel_request_move(struct wl_listener* listener, void* data) { - View* view = wl_container_of(listener, view, l_request_move); - xdg_toplevel_handle_moveresize(view, Server::CursorMode::Move, 0); + Client_ptr client = wl_container_of(listener, client, l_request_move); + xdg_toplevel_handle_moveresize(client, Server::CursorMode::Move, 0); } void @@ -746,46 +886,50 @@ Server::xdg_toplevel_request_resize(struct wl_listener* listener, void* data) struct wlr_xdg_toplevel_resize_event* event = reinterpret_cast<struct wlr_xdg_toplevel_resize_event*>(data); - View* view = wl_container_of(listener, view, l_request_resize); - xdg_toplevel_handle_moveresize(view, Server::CursorMode::Resize, event->edges); + Client_ptr client = wl_container_of(listener, client, l_request_resize); + xdg_toplevel_handle_moveresize(client, Server::CursorMode::Resize, event->edges); } void -Server::xdg_toplevel_handle_moveresize(View* view, Server::CursorMode mode, uint32_t edges) +Server::xdg_toplevel_handle_moveresize( + Client_ptr client, + Server::CursorMode mode, + uint32_t edges +) { - Server* server = view->server; + Server_ptr server = client->server; - if (view->xdg_surface->surface + if (client->get_surface() != wlr_surface_get_root_surface(server->m_seat->pointer_state.focused_surface)) { return; } - server->m_grabbed_view = view; + server->m_grabbed_client = client; server->m_cursor_mode = mode; switch (mode) { case Server::CursorMode::Move: - server->m_grab_x = server->m_cursor->x - view->x; - server->m_grab_y = server->m_cursor->y - view->y; + server->m_grab_x = server->m_cursor->x - client->active_region.pos.x; + server->m_grab_y = server->m_cursor->y - client->active_region.pos.y; return; case Server::CursorMode::Resize: { struct wlr_box geo_box; - wlr_xdg_surface_get_geometry(view->xdg_surface, &geo_box); + wlr_xdg_surface_get_geometry(client->surface.xdg, &geo_box); - double border_x = (view->x + geo_box.x) + + double border_x = (client->active_region.pos.x + geo_box.x) + ((edges & WLR_EDGE_RIGHT) ? geo_box.width : 0); - double border_y = (view->y + geo_box.y) + + double border_y = (client->active_region.pos.y + geo_box.y) + ((edges & WLR_EDGE_BOTTOM) ? geo_box.height : 0); server->m_grab_x = server->m_cursor->x - border_x; server->m_grab_y = server->m_cursor->y - border_y; server->m_grab_geobox = geo_box; - server->m_grab_geobox.x += view->x; - server->m_grab_geobox.y += view->y; + server->m_grab_geobox.x += client->active_region.pos.x; + server->m_grab_geobox.y += client->active_region.pos.y; server->m_resize_edges = edges; } @@ -793,3 +937,88 @@ Server::xdg_toplevel_handle_moveresize(View* view, Server::CursorMode mode, uint default: return; } } + +#ifdef XWAYLAND +void +Server::xwayland_ready(struct wl_listener* listener, void*) +{ + Server_ptr server = wl_container_of(listener, server, ml_xwayland_ready); + + xcb_connection_t* xconn = xcb_connect(server->m_xwayland->display_name, NULL); + if (xcb_connection_has_error(xconn)) { + spdlog::error("Could not establish a connection with the X server"); + spdlog::warn("Continuing with limited XWayland functionality"); + return; + } + + /* netatom[NetWMWindowTypeDialog] = getatom(xconn, "_NET_WM_WINDOW_TYPE_DIALOG"); */ + /* netatom[NetWMWindowTypeSplash] = getatom(xconn, "_NET_WM_WINDOW_TYPE_SPLASH"); */ + /* netatom[NetWMWindowTypeToolbar] = getatom(xconn, "_NET_WM_WINDOW_TYPE_TOOLBAR"); */ + /* netatom[NetWMWindowTypeUtility] = getatom(xconn, "_NET_WM_WINDOW_TYPE_UTILITY"); */ + + wlr_xwayland_set_seat(server->m_xwayland, server->m_seat); + + struct wlr_xcursor* xcursor; + if ((xcursor = wlr_xcursor_manager_get_xcursor(server->m_cursor_manager, "left_ptr", 1))) + wlr_xwayland_set_cursor(server->m_xwayland, + xcursor->images[0]->buffer, xcursor->images[0]->width * 4, + xcursor->images[0]->width, xcursor->images[0]->height, + xcursor->images[0]->hotspot_x, xcursor->images[0]->hotspot_y); + + xcb_disconnect(xconn); +} + +void +Server::new_xwayland_surface(struct wl_listener* listener, void* data) +{ + Server_ptr server = wl_container_of(listener, server, ml_new_xwayland_surface); + + struct wlr_xwayland_surface* xwayland_surface + = reinterpret_cast<wlr_xwayland_surface*>(data); + + Client_ptr client = new Client; + + client->server = server; + xwayland_surface->data = client; + + client->surface = Surface{ .xwayland = xwayland_surface }; + client->surface_type = xwayland_surface->override_redirect + ? SurfaceType::X11Unmanaged + : SurfaceType::X11Managed; + + client->l_map = { .notify = Server::xdg_toplevel_map }; + client->l_unmap = { .notify = Server::xdg_toplevel_unmap }; + client->l_destroy = { .notify = Server::xdg_toplevel_destroy }; + // TODO: client->l_set_title = { .notify = Server::... }; + // TODO: client->l_fullscreen = { .notify = Server::... }; + client->l_request_activate = { .notify = Server::xwayland_request_activate }; + client->l_request_configure = { .notify = Server::xwayland_request_configure }; + client->l_set_hints = { .notify = Server::xwayland_set_hints }; + // TODO: client->l_new_xdg_popup = { .notify = Server::... }; + + wl_signal_add(&xwayland_surface->events.map, &client->l_map); + wl_signal_add(&xwayland_surface->events.unmap, &client->l_unmap); + wl_signal_add(&xwayland_surface->events.destroy, &client->l_destroy); + wl_signal_add(&xwayland_surface->events.request_activate, &client->l_request_activate); + wl_signal_add(&xwayland_surface->events.request_configure, &client->l_request_configure); + wl_signal_add(&xwayland_surface->events.set_hints, &client->l_set_hints); +} + +void +Server::xwayland_request_activate(struct wl_listener* listener, void*) +{ + +} + +void +Server::xwayland_request_configure(struct wl_listener* listener, void*) +{ + +} + +void +Server::xwayland_set_hints(struct wl_listener* listener, void*) +{ + +} +#endif diff --git a/src/kranewl/util.cc b/src/kranewl/util.cc @@ -0,0 +1,30 @@ +#include <kranewl/util.hh> + +#define assert assert_ +#include <spdlog/spdlog.h> +#undef assert + +#include <utility> +extern "C" { +#include <unistd.h> +} + +void +Util::die(const std::string&& msg) +{ + spdlog::critical(msg); + exit(1); +} + +void +Util::warn(const std::string&& msg) +{ + spdlog::warn(msg); +} + +void +Util::assert(bool condition, std::string const&& msg) +{ + if (!condition) + Util::die(std::forward<const std::string&&>(msg)); +}