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 3e7e073d1ade79e2ae79ea49d7402c78cd49989e
parent e0695bae1453b6f4386469c023b43d17be4c8088
Author: deurzen <max@deurzen.net>
Date:   Fri,  3 Jun 2022 16:47:57 +0200

implements {config,client}-specified rule handling

Diffstat:
Minclude/kranewl/conf/options.hh | 7+++++--
Minclude/kranewl/context.hh | 6------
Minclude/kranewl/input/key-bindings.hh | 16++++++++--------
Minclude/kranewl/model.hh | 15++++++++++-----
Ainclude/kranewl/rules.hh | 42++++++++++++++++++++++++++++++++++++++++++
Minclude/kranewl/search.hh | 38++++++++++++++++++++++----------------
Minclude/kranewl/tree/output.hh | 2++
Minclude/kranewl/workspace.hh | 3+++
Msrc/kranewl/conf/options.cc | 33+++++++++++++++++++++++++--------
Msrc/kranewl/env.cc | 19++++++++++++++-----
Msrc/kranewl/main.cc | 1+
Msrc/kranewl/model.cc | 125++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Asrc/kranewl/rules.cc | 210+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/kranewl/tree/output.cc | 6++++++
Msrc/kranewl/tree/view.cc | 4++--
Msrc/kranewl/tree/xdg-view.cc | 9++-------
Msrc/kranewl/tree/xwayland-view.cc | 8+++++++-
Msrc/kranewl/workspace.cc | 6++++++
18 files changed, 454 insertions(+), 96 deletions(-)

diff --git a/include/kranewl/conf/options.hh b/include/kranewl/conf/options.hh @@ -6,16 +6,19 @@ struct Options final { Options( std::string const&& config_path_, - std::string const&& env_path_, + std::optional<std::string> env_path_, + std::optional<std::string> rules_path_, std::optional<std::string> autostart_path_ ) : config_path(config_path_), env_path(env_path_), + rules_path(rules_path_), autostart_path(autostart_path_) {} std::string config_path; - std::string env_path; + std::optional<std::string> env_path; + std::optional<std::string> rules_path; std::optional<std::string> autostart_path; }; diff --git a/include/kranewl/context.hh b/include/kranewl/context.hh @@ -57,12 +57,6 @@ public: return mp_output; } - bool - is_outputed() const - { - return mp_output != nullptr; - } - void set_output(Output_ptr output) { diff --git a/include/kranewl/input/key-bindings.hh b/include/kranewl/input/key-bindings.hh @@ -124,16 +124,16 @@ static const KeyBindings key_bindings = { CALL(stretch_focus(Edge::Right, -15)) }, { { XKB_KEY_leftarrow, MODKEY | WLR_MODIFIER_CTRL }, - CALL(snap_focus(Edge::Left)) + CALL(snap_focus(WLR_EDGE_LEFT)) }, { { XKB_KEY_downarrow, MODKEY | WLR_MODIFIER_CTRL }, - CALL(snap_focus(Edge::Bottom)) + CALL(snap_focus(WLR_EDGE_BOTTOM)) }, { { XKB_KEY_uparrow, MODKEY | WLR_MODIFIER_CTRL }, - CALL(snap_focus(Edge::Top)) + CALL(snap_focus(WLR_EDGE_TOP)) }, { { XKB_KEY_rightarrow, MODKEY | WLR_MODIFIER_CTRL }, - CALL(snap_focus(Edge::Right)) + CALL(snap_focus(WLR_EDGE_RIGHT)) }, { { XKB_KEY_j, MODKEY }, CALL(cycle_focus(Direction::Forward)) @@ -594,14 +594,11 @@ static const KeyBindings key_bindings = { { { XKB_KEY_Return, MODKEY }, CALL_EXTERNAL(alacritty) }, -{ { XKB_KEY_Return, MODKEY | WLR_MODIFIER_SHIFT }, - CALL(spawn_external("alacritty --class kranewl:cf,Alacritty")) -}, { { XKB_KEY_semicolon, MODKEY }, CALL_EXTERNAL(nemo) }, { { XKB_KEY_p, MODKEY }, - CALL_EXTERNAL(dmenu_run) + CALL_EXTERNAL(rofi -show run) }, { { XKB_KEY_q, MODKEY }, CALL_EXTERNAL(qutebrowser) @@ -615,6 +612,9 @@ static const KeyBindings key_bindings = { { { XKB_KEY_apostrophe, MODKEY }, CALL_EXTERNAL(blueberry) }, +{ { XKB_KEY_Return, MODKEY | WLR_MODIFIER_SHIFT }, + CALL_EXTERNAL(alacritty --class "kranewl:cf,Alacritty") +}, }; } diff --git a/include/kranewl/model.hh b/include/kranewl/model.hh @@ -6,8 +6,9 @@ #include <kranewl/input/bindings.hh> #include <kranewl/layout.hh> #include <kranewl/placement.hh> -#include <kranewl/tree/layer.hh> +#include <kranewl/rules.hh> #include <kranewl/search.hh> +#include <kranewl/tree/layer.hh> #include <kranewl/tree/view.hh> #include <optional> @@ -38,8 +39,9 @@ public: Model(Config const&); ~Model(); - void evaluate_user_env_vars(std::string const&); - void run_user_autostart(std::optional<std::string>); + void evaluate_user_env_vars(std::optional<std::string> const&); + void retrieve_user_default_rules(std::optional<std::string> const&); + void run_user_autostart(std::optional<std::string> const&); void register_server(Server_ptr); void exit(); @@ -77,6 +79,7 @@ public: void destroy_unmanaged(XWaylandUnmanaged_ptr); #endif + void initialize_view(View_ptr, Workspace_ptr); void register_view(View_ptr, Workspace_ptr); void unregister_view(View_ptr); void destroy_view(View_ptr); @@ -191,8 +194,8 @@ public: void stretch_view(Edge, Util::Change<int>, View_ptr); void inflate_focus(Util::Change<int>); void inflate_view(Util::Change<int>, View_ptr); - void snap_focus(Edge); - void snap_view(Edge, View_ptr); + void snap_focus(uint32_t); + void snap_view(View_ptr, uint32_t); void pop_deiconify(); void deiconify_all(); @@ -232,6 +235,8 @@ private: View_ptr mp_focus; View_ptr mp_jumped_from; + std::vector<std::tuple<SearchSelector_ptr, Rules>> m_default_rules; + const KeyBindings m_key_bindings; const CursorBindings m_cursor_bindings; diff --git a/include/kranewl/rules.hh b/include/kranewl/rules.hh @@ -0,0 +1,42 @@ +#pragma once + +#include <kranewl/common.hh> +#include <kranewl/search.hh> + +#include <optional> +#include <vector> + +extern "C" { +#include <wlr/util/edges.h> +} + +struct Rules { + Rules() + : do_focus(std::nullopt), + do_float(std::nullopt), + do_center(std::nullopt), + do_fullscreen(std::nullopt), + to_output(std::nullopt), + to_context(std::nullopt), + to_workspace(std::nullopt), + snap_edges(std::nullopt) + {} + + ~Rules() = default; + + std::optional<bool> do_focus; + std::optional<bool> do_float; + std::optional<bool> do_center; + std::optional<bool> do_fullscreen; + std::optional<Index> to_output; + std::optional<Index> to_context; + std::optional<Index> to_workspace; + std::optional<uint32_t> snap_edges; + + static std::vector<std::tuple<SearchSelector_ptr, Rules>> + compile_default_rules(std::string const&); + + static Rules parse_rules(std::string_view, bool = false); + static Rules merge_rules(Rules const&, Rules const&); + +}; diff --git a/include/kranewl/search.hh b/include/kranewl/search.hh @@ -8,16 +8,16 @@ #include <string> #include <utility> -typedef class SearchSelector final -{ +typedef class SearchSelector final { public: - enum class SelectionCriterium - { + enum class SelectionCriterium { OnWorkspaceBySelector, ByTitleEquals, ByAppIdEquals, + ByHandleEquals, ByTitleContains, ByAppIdContains, + ByHandleContains, ForCondition, }; @@ -30,10 +30,12 @@ public: : m_string(str_) { switch (criterium) { - case SelectionCriterium::ByTitleEquals: m_tag = SearchSelectorTag::ByTitleEquals; return; - case SelectionCriterium::ByAppIdEquals: m_tag = SearchSelectorTag::ByAppIdEquals; return; - case SelectionCriterium::ByTitleContains: m_tag = SearchSelectorTag::ByTitleContains; return; - case SelectionCriterium::ByAppIdContains: m_tag = SearchSelectorTag::ByAppIdContains; return; + case SelectionCriterium::ByTitleEquals: m_tag = SearchSelectorTag::ByTitleEquals; return; + case SelectionCriterium::ByAppIdEquals: m_tag = SearchSelectorTag::ByAppIdEquals; return; + case SelectionCriterium::ByHandleEquals: m_tag = SearchSelectorTag::ByHandleEquals; return; + case SelectionCriterium::ByTitleContains: m_tag = SearchSelectorTag::ByTitleContains; return; + case SelectionCriterium::ByAppIdContains: m_tag = SearchSelectorTag::ByAppIdContains; return; + case SelectionCriterium::ByHandleContains: m_tag = SearchSelectorTag::ByHandleContains; return; default: return; } } @@ -55,10 +57,12 @@ public: return; } - case SearchSelectorTag::ByTitleEquals: // fallthrough - case SearchSelectorTag::ByAppIdEquals: // fallthrough - case SearchSelectorTag::ByTitleContains: // fallthrough - case SearchSelectorTag::ByAppIdContains: + case SearchSelectorTag::ByTitleEquals: // fallthrough + case SearchSelectorTag::ByAppIdEquals: // fallthrough + case SearchSelectorTag::ByHandleEquals: // fallthrough + case SearchSelectorTag::ByTitleContains: // fallthrough + case SearchSelectorTag::ByAppIdContains: // fallthrough + case SearchSelectorTag::ByHandleContains: { (&m_string)->std::string::~string(); @@ -83,8 +87,10 @@ public: case SearchSelectorTag::OnWorkspaceBySelector: return SelectionCriterium::OnWorkspaceBySelector; case SearchSelectorTag::ByTitleEquals: return SelectionCriterium::ByTitleEquals; case SearchSelectorTag::ByAppIdEquals: return SelectionCriterium::ByAppIdEquals; + case SearchSelectorTag::ByHandleEquals: return SelectionCriterium::ByAppIdEquals; case SearchSelectorTag::ByTitleContains: return SelectionCriterium::ByTitleContains; case SearchSelectorTag::ByAppIdContains: return SelectionCriterium::ByAppIdContains; + case SearchSelectorTag::ByHandleContains: return SelectionCriterium::ByHandleContains; case SearchSelectorTag::ForCondition: return SelectionCriterium::ForCondition; default: break; } @@ -111,19 +117,19 @@ public: } private: - enum class SearchSelectorTag - { + enum class SearchSelectorTag { OnWorkspaceBySelector, ByTitleEquals, ByAppIdEquals, + ByHandleEquals, ByTitleContains, ByAppIdContains, + ByHandleContains, ForCondition, }; SearchSelectorTag m_tag; - union - { + union { std::pair<Index, Workspace::ViewSelector::SelectionCriterium> m_workspace_selector; std::string m_string; std::function<bool(const View_ptr)> m_filter; diff --git a/include/kranewl/tree/output.hh b/include/kranewl/tree/output.hh @@ -17,6 +17,7 @@ extern "C" { typedef class Server* Server_ptr; typedef class Model* Model_ptr; typedef class Context* Context_ptr; +typedef class Workspace* Workspace_ptr; typedef class Layer* Layer_ptr; typedef class Output final { @@ -40,6 +41,7 @@ public: void set_context(Context_ptr); Context_ptr context() const; + Workspace_ptr workspace() const; Region full_region() const; Region placeable_region() const; diff --git a/include/kranewl/workspace.hh b/include/kranewl/workspace.hh @@ -8,7 +8,9 @@ #include <kranewl/util.hh> typedef struct View* View_ptr; +typedef class Output* Output_ptr; typedef class Context* Context_ptr; + typedef class Workspace final { public: struct ViewSelector @@ -102,6 +104,7 @@ public: int main_count() const; Context_ptr context() const; + Output_ptr output() const; Index index() const; std::string const& name() const; diff --git a/src/kranewl/conf/options.cc b/src/kranewl/conf/options.cc @@ -13,8 +13,6 @@ extern "C" { static const std::string CONFIG_FILE = "kranewlrc.lua"; static const std::string DEFAULT_CONFIG = "/etc/kranewl/" + CONFIG_FILE; -static const std::string ENV_FILE = "env"; -static const std::string DEFAULT_ENV = "/etc/kranewl/" + ENV_FILE; static const std::string USAGE = "usage: kranewl [...options]\n\n" "options: \n" " -a <autostart_file> Path to an executable autostart file.\n" @@ -59,18 +57,32 @@ resolve_config_path(std::string& path) noexcept return DEFAULT_CONFIG; } -static std::string const& +static std::optional<std::string> resolve_env_path(std::string& path) noexcept { if (!path.empty()) if (assert_permissions(path, R_OK)) return path; - path.assign(default_user_path(ENV_FILE)); + path.assign(default_user_path("env")); if (assert_permissions(path, R_OK)) return path; - return DEFAULT_ENV; + return {}; +} + +static std::optional<std::string> +resolve_rules_path(std::string& path) noexcept +{ + if (!path.empty()) + if (assert_permissions(path, R_OK)) + return path; + + path.assign(default_user_path("rules")); + if (assert_permissions(path, R_OK)) + return {path}; + + return {}; } static std::optional<std::string> @@ -92,10 +104,10 @@ resolve_autostart_path(std::string& path) noexcept Options parse_options(int argc, char** argv) noexcept { - std::string autostart_path, config_path, env_path; + std::string autostart_path, config_path, env_path, rules_path; int opt; - while ((opt = getopt(argc, argv, "vha:c:e:")) != -1) { + while ((opt = getopt(argc, argv, "h?va:c:e:r:")) != -1) { switch (opt) { case 'a': autostart_path = optarg; @@ -109,6 +121,10 @@ parse_options(int argc, char** argv) noexcept env_path = optarg; break; + case 'r': + rules_path = optarg; + break; + case 'v': std::cout << VERSION << std::endl << std::flush; std::exit(EXIT_SUCCESS); @@ -125,7 +141,8 @@ parse_options(int argc, char** argv) noexcept return Options( std::move(resolve_config_path(config_path)), - std::move(resolve_env_path(env_path)), + resolve_env_path(env_path), + resolve_rules_path(rules_path), resolve_autostart_path(autostart_path) ); } diff --git a/src/kranewl/env.cc b/src/kranewl/env.cc @@ -48,9 +48,6 @@ evaluate_value(std::string const& value) void parse_and_set_env_vars(std::string const& env_path) { - if (!file_exists(env_path)) - return; - struct Assignment_ctype : std::ctype<char> { Assignment_ctype() : std::ctype<char>(get_table()) @@ -66,15 +63,27 @@ parse_and_set_env_vars(std::string const& env_path) } }; + if (!file_exists(env_path)) + return; + std::ifstream env_if(env_path); - env_if.imbue(std::locale(env_if.getloc(), new Assignment_ctype)); + if (!env_if.is_open()) + return; + env_if.imbue(std::locale(env_if.getloc(), new Assignment_ctype)); const auto isspace = [](char c) { return std::isspace(static_cast<unsigned char>(c)); }; + std::size_t line = 0; std::string var, value; - while (env_if >> var >> value) { + while (++line, env_if >> var >> value) { + if (env_if.fail()) { + spdlog::error("Erroneous environment variable assignment" + " in {} on line {}", env_path, line); + continue; + } + var.erase(std::remove_if(var.begin(), var.end(), isspace), var.end()); value.erase(std::remove_if(value.begin(), value.end(), isspace), value.end()); diff --git a/src/kranewl/main.cc b/src/kranewl/main.cc @@ -43,6 +43,7 @@ main(int argc, char** argv) server.initialize(); model.evaluate_user_env_vars(options.env_path); + model.retrieve_user_default_rules(options.rules_path); server.start(); model.run_user_autostart(options.autostart_path); server.run(); diff --git a/src/kranewl/model.cc b/src/kranewl/model.cc @@ -98,15 +98,30 @@ Model::~Model() {} void -Model::evaluate_user_env_vars(std::string const& env_path) +Model::evaluate_user_env_vars(std::optional<std::string> const& env_path) { TRACE(); - parse_and_set_env_vars(env_path); + + if (env_path) { + spdlog::info("Populating environment with variables defined in {}", *env_path); + parse_and_set_env_vars(*env_path); + } +} + +void +Model::retrieve_user_default_rules(std::optional<std::string> const& rules_path) +{ + TRACE(); + + if (rules_path) { + spdlog::info("Compiling default rules from {}", *rules_path); + m_default_rules = Rules::compile_default_rules(*rules_path); + } } void Model::run_user_autostart( - [[maybe_unused]] std::optional<std::string> autostart_path + [[maybe_unused]] std::optional<std::string> const& autostart_path ) { TRACE(); @@ -326,7 +341,7 @@ Model::focus_output(Output_ptr output) TRACE(); if (output && output != mp_output) - focus_view(output->context()->workspace()->active()); + focus_view(output->workspace()->active()); } void @@ -728,7 +743,7 @@ Model::move_view_to_workspace(View_ptr view, Workspace_ptr workspace_to) workspace_to->add_view(view); apply_layout(workspace_to); - if (output_to && output_to != output_from) + if (output_to && output_to->workspace() == workspace_to) view->map(); else view->unmap(); @@ -1298,8 +1313,8 @@ Model::apply_layout(Workspace_ptr workspace) { TRACE(); - Output_ptr output = workspace->context()->output(); - if (!output || workspace != output->context()->workspace()) + Output_ptr output = workspace->output(); + if (!output || workspace != output->workspace()) return; for (Placement placement : workspace->arrange(output->placeable_region())) @@ -1892,16 +1907,16 @@ Model::inflate_view(Util::Change<int> change, View_ptr view) } void -Model::snap_focus(Edge edge) +Model::snap_focus(uint32_t edges) { TRACE(); if (mp_focus) - snap_view(edge, mp_focus); + snap_view(mp_focus, edges); } void -Model::snap_view(Edge edge, View_ptr view) +Model::snap_view(View_ptr view, uint32_t edges) { TRACE(); @@ -1912,39 +1927,22 @@ Model::snap_view(Edge edge, View_ptr view) const Region screen_region = view->mp_context->output()->placeable_region(); - switch (edge) { - case Edge::Left: - { + if ((edges & WLR_EDGE_LEFT)) region.pos.x = screen_region.pos.x; - - break; - } - case Edge::Top: - { - region.pos.y = screen_region.pos.y; - - break; - } - case Edge::Right: - { + else if ((edges & WLR_EDGE_RIGHT)) region.pos.x = std::max( 0, (screen_region.dim.w + screen_region.pos.x) - region.dim.w ); - break; - } - case Edge::Bottom: - { + if ((edges & WLR_EDGE_TOP)) + region.pos.y = screen_region.pos.y; + else if ((edges & WLR_EDGE_BOTTOM)) region.pos.y = std::max( 0, (screen_region.dim.h + screen_region.pos.y) - region.dim.h ); - break; - } - } - Placement placement = Placement { Placement::PlacementMethod::Free, view, @@ -2013,6 +2011,64 @@ Model::set_focus_follows_cursor(Toggle toggle, Context_ptr context) context->set_focus_follows_cursor(focus_follows_cursor); } +void +Model::initialize_view(View_ptr view, Workspace_ptr workspace) +{ + TRACE(); + + static std::unordered_map<std::string, Rules> + default_rules_memoized{}; + + std::optional<Rules> default_rules + = Util::const_retrieve(default_rules_memoized, view->handle()); + + if (!default_rules) + for (auto& [selector,default_rules_] : m_default_rules) + if (view_matches_search(view, *selector)) { + default_rules_memoized[view->handle()] = default_rules_; + default_rules = default_rules_; + } + + Rules rules = default_rules + ? Rules::merge_rules(*default_rules, Rules::parse_rules(view->handle())) + : Rules::parse_rules(view->handle()); + + view->format_uid(); + + Output_ptr output; + if (rules.to_output && *rules.to_output < m_outputs.size()) { + view->mp_output = Model::output(*rules.to_output); + workspace = view->mp_output->workspace(); + output = view->mp_output; + } else + output = workspace->output(); + + if (rules.to_context && *rules.to_context < m_contexts.size()) { + view->mp_context = context(*rules.to_context); + workspace = view->mp_context->workspace(); + } else + workspace = output->workspace(); + + Context_ptr context = workspace->context(); + if (rules.to_workspace && *rules.to_workspace < context->workspaces().size()) + workspace = (*context)[*rules.to_workspace]; + + move_view_to_workspace(view, workspace); + + view->relayer( + is_free(view) + ? SCENE_LAYER_FREE + : SCENE_LAYER_TILE + ); + + if (rules.do_float) + set_floating_view(*rules.do_float ? Toggle::On : Toggle::Off, view); + if (rules.snap_edges) + snap_view(view, *rules.snap_edges); + if (rules.do_fullscreen) + set_fullscreen_view(*rules.do_fullscreen ? Toggle::On : Toggle::Off, view); +} + XDGView_ptr Model::create_xdg_shell_view( struct wlr_xdg_surface* wlr_xdg_surface, @@ -2029,7 +2085,6 @@ Model::create_xdg_shell_view( ); m_view_map[view->uid()] = view; - return view; } @@ -2052,7 +2107,6 @@ Model::create_xwayland_view( ); m_view_map[view->uid()] = view; - return view; } @@ -2096,8 +2150,7 @@ Model::register_view(View_ptr view, Workspace_ptr workspace) { TRACE(); - view->format_uid(); - move_view_to_workspace(view, workspace); + initialize_view(view, workspace); spdlog::info("Registered view {}", view->uid_formatted()); sync_focus(); } diff --git a/src/kranewl/rules.cc b/src/kranewl/rules.cc @@ -0,0 +1,210 @@ +#include <kranewl/rules.hh> +#include <kranewl/env.hh> + +#include <spdlog/spdlog.h> + +#include <fstream> + +std::vector<std::tuple<SearchSelector_ptr, Rules>> +Rules::compile_default_rules(std::string const& rules_path) +{ + std::vector<std::tuple<SearchSelector_ptr, Rules>> + default_rules = {}; + + if (!file_exists(rules_path)) + return default_rules; + + std::ifstream rules_if(rules_path); + if (!rules_if.good()) + return default_rules; + + std::string line; + + while (std::getline(rules_if, line)) { + std::string::size_type pos = line.find('#'); + + if (pos != std::string::npos) + line = line.substr(0, pos); + + if (line.length() < 5) + continue; + + line.erase(4, line.find_first_not_of(" \t\n\r\f\v")); + line.erase(line.find_last_not_of(" \t\n\r\f\v") + 1); + + if (line.length() < 5) + continue; + + pos = line.find(':'); + std::string rules = line.substr(0, pos); + if (pos == std::string::npos || rules.empty() || pos >= (line.size() - 1)) + continue; + + line = line.substr(pos + 1); + if (line.length() < 5) + continue; + + SearchSelector::SelectionCriterium criterium; + switch (line[1]) { + case 'T': + { + switch (line[0]) { + case '=': criterium = SearchSelector::SelectionCriterium::ByTitleEquals; break; + case '~': criterium = SearchSelector::SelectionCriterium::ByTitleContains; break; + default: continue; + } + + break; + } + case 'A': + { + switch (line[0]) { + case '=': criterium = SearchSelector::SelectionCriterium::ByAppIdEquals; break; + case '~': criterium = SearchSelector::SelectionCriterium::ByAppIdContains; break; + default: continue; + } + + break; + } + case 'H': + { + switch (line[0]) { + case '=': criterium = SearchSelector::SelectionCriterium::ByHandleEquals; break; + case '~': criterium = SearchSelector::SelectionCriterium::ByHandleContains; break; + default: continue; + } + + break; + } + default: continue; + } + + default_rules.push_back({ + new SearchSelector{criterium, line.substr(3)}, + Rules::parse_rules(rules, true) + }); + } + + return default_rules; +} + +Rules +Rules::parse_rules(std::string_view rule, bool ignore_prefix) +{ + static const std::string prefix = "kranewl:"; + static auto snapedge_list_handler = [](Rules& rules, auto iter) { + switch (*iter) { + case 'l': *rules.snap_edges |= WLR_EDGE_LEFT; return; + case 't': *rules.snap_edges |= WLR_EDGE_TOP; return; + case 'r': *rules.snap_edges |= WLR_EDGE_RIGHT; return; + case 'b': *rules.snap_edges |= WLR_EDGE_BOTTOM; return; + default: return; + } + }; + + Rules rules{}; + std::string_view::const_iterator iter; + + if (!ignore_prefix) { + std::string::size_type prefix_pos = rule.find(prefix); + if (prefix_pos == std::string::npos) + return rules; + + iter = rule.begin() + prefix_pos + prefix.size(); + } else + iter = rule.begin(); + + spdlog::info("Parsing rule: {}", std::string(iter, rule.end())); + + bool invert = false; + bool next_output = false; + bool next_context = false; + bool next_workspace = false; + + std::optional<decltype(snapedge_list_handler)> + list_handler = std::nullopt; + + for (; iter != rule.end(); ++iter) { + if (*iter == '.') { + list_handler = std::nullopt; + continue; + } + if (list_handler) { + (*list_handler)(rules, iter); + continue; + } + + switch (*iter) { + case ':': return rules; + case '!': invert = true; continue; + case 'O': next_output = true; continue; + case 'C': next_context = true; continue; + case 'W': next_workspace = true; continue; + case '@': rules.do_focus = !invert; break; + case 'f': rules.do_float = !invert; break; + case 'F': rules.do_fullscreen = !invert; break; + case 'c': rules.do_center = !invert; break; + case '0': // fallthrough + case '1': // fallthrough + case '2': // fallthrough + case '3': // fallthrough + case '4': // fallthrough + case '5': // fallthrough + case '6': // fallthrough + case '7': // fallthrough + case '8': // fallthrough + case '9': + { + if (next_output) + rules.to_output = *iter - '0'; + if (next_context) + rules.to_context = *iter - '0'; + if (next_workspace) + rules.to_workspace = *iter - '0'; + break; + } + case 'S': + { + if (!rules.snap_edges) + *rules.snap_edges = WLR_EDGE_NONE; + + list_handler = snapedge_list_handler; + break; + } + default: break; + } + + invert = false; + next_output = false; + next_context = false; + next_workspace = false; + } + + return rules; +} + + +Rules +Rules::merge_rules(Rules const& base, Rules const& merger) +{ + Rules rules = base; + + if (merger.do_focus) + rules.do_focus = merger.do_focus; + if (merger.do_float) + rules.do_float = merger.do_float; + if (merger.do_center) + rules.do_center = merger.do_center; + if (merger.do_fullscreen) + rules.do_fullscreen = merger.do_fullscreen; + if (merger.to_output) + rules.to_output = merger.to_output; + if (merger.to_context) + rules.to_context = merger.to_context; + if (merger.to_workspace) + rules.to_workspace = merger.to_workspace; + if (merger.snap_edges) + rules.snap_edges = merger.snap_edges; + + return rules; +} diff --git a/src/kranewl/tree/output.cc b/src/kranewl/tree/output.cc @@ -177,6 +177,12 @@ Output::context() const return mp_context; } +Workspace_ptr +Output::workspace() const +{ + return mp_context->workspace(); +} + Region Output::full_region() const { diff --git a/src/kranewl/tree/view.cc b/src/kranewl/tree/view.cc @@ -321,7 +321,7 @@ View::tile(Toggle toggle) return; set_floating(false); - if (!m_fullscreen) + if (!mp_model->is_free(this)) relayer(SCENE_LAYER_TILE); break; @@ -332,7 +332,7 @@ View::tile(Toggle toggle) return; set_floating(true); - if (!m_fullscreen) + if (mp_model->is_free(this)) relayer(SCENE_LAYER_FREE); break; diff --git a/src/kranewl/tree/xdg-view.cc b/src/kranewl/tree/xdg-view.cc @@ -343,6 +343,7 @@ XDGView::handle_map(struct wl_listener* listener, void* data) view->set_title(wlr_xdg_toplevel->title ? wlr_xdg_toplevel->title : "N/a"); view->set_title_formatted(view->title()); // TODO: format title + view->set_handle(view->title() + " " + view->app_id()); wlr_xdg_toplevel_set_tiled( wlr_xdg_surface, @@ -358,12 +359,6 @@ XDGView::handle_map(struct wl_listener* listener, void* data) ); view->mp_scene_surface->data = view; - view->relayer( - view->floating() - ? SCENE_LAYER_FREE - : SCENE_LAYER_TILE - ); - for (std::size_t i = 0; i < 4; ++i) { view->m_protrusions[i] = wlr_scene_rect_create( view->mp_scene, @@ -405,7 +400,7 @@ XDGView::handle_map(struct wl_listener* listener, void* data) .dim = preferred_dim }; - Output_ptr output = workspace->context()->output(); + Output_ptr output = workspace->output(); if (output) output->place_at_center(region); diff --git a/src/kranewl/tree/xwayland-view.cc b/src/kranewl/tree/xwayland-view.cc @@ -321,6 +321,12 @@ XWaylandView::handle_map(struct wl_listener* listener, void* data) view->set_title(xwayland_surface->title ? xwayland_surface->title : "N/a"); view->set_title_formatted(view->title()); // TODO: format title + view->set_handle( + view->title() + " " + + view->app_id() + " " + + view->class_() + " " + + view->instance() + ); view->mp_scene = &wlr_scene_tree_create( server->m_scene_layers[SCENE_LAYER_TILE] @@ -361,7 +367,7 @@ XWaylandView::handle_map(struct wl_listener* listener, void* data) }; Workspace_ptr workspace = model->mp_workspace; - Output_ptr output = workspace->context()->output(); + Output_ptr output = workspace->output(); if (output) output->place_at_center(region); diff --git a/src/kranewl/workspace.cc b/src/kranewl/workspace.cc @@ -94,6 +94,12 @@ Workspace::context() const return mp_context; } +Output_ptr +Workspace::output() const +{ + return mp_context->output(); +} + Index Workspace::index() const {