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:
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
{