From 844dce57d237390d4038a54e8a93aafb3c6a842d Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 11:21:41 +0800 Subject: [PATCH 01/18] enable the receival of X{Enter,Leave}WindowEvents --- src/client.cc | 1 + src/window_manager.cc | 14 ++++++++++++++ src/window_manager.h | 2 ++ 3 files changed, 17 insertions(+) diff --git a/src/client.cc b/src/client.cc index 4dcc2bb..2e96f97 100644 --- a/src/client.cc +++ b/src/client.cc @@ -25,6 +25,7 @@ Client::Client(Display* dpy, Window window, Workspace* workspace) Client::mapper_[window] = this; SetBorderWidth(workspace->config()->border_width()); SetBorderColor(workspace->config()->unfocused_color()); + XSelectInput(dpy, window, EnterWindowMask | LeaveWindowMask); } Client::~Client() { diff --git a/src/window_manager.cc b/src/window_manager.cc index f5b9b77..4031ea8 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -220,6 +220,12 @@ void WindowManager::Run() { case MotionNotify: OnMotionNotify(event.xbutton); break; + case EnterNotify: + OnEnterNotify(event.xcrossing); + break; + case LeaveNotify: + OnLeaveNotify(event.xcrossing); + break; case ClientMessage: OnClientMessage(event.xclient); break; @@ -418,6 +424,14 @@ void WindowManager::OnMotionNotify(const XButtonEvent& e) { c->MoveResize(new_x, new_y, new_width, new_height); } +void WindowManager::OnEnterNotify(const XEnterWindowEvent& e) { + WM_LOG(INFO, "enter!"); +} + +void WindowManager::OnLeaveNotify(const XEnterWindowEvent& e) { + WM_LOG(INFO, "leave!"); +} + void WindowManager::OnClientMessage(const XClientMessageEvent& e) { if (e.message_type == prop_->wmderland_client_event) { ipc_evmgr_.Handle(e); diff --git a/src/window_manager.h b/src/window_manager.h index 01ab2ec..926a456 100644 --- a/src/window_manager.h +++ b/src/window_manager.h @@ -53,6 +53,8 @@ class WindowManager { void OnButtonPress(const XButtonEvent& e); void OnButtonRelease(const XButtonEvent& e); void OnMotionNotify(const XButtonEvent& e); + void OnEnterNotify(const XEnterWindowEvent& e); + void OnLeaveNotify(const XLeaveWindowEvent& e); void OnClientMessage(const XClientMessageEvent& e); void OnConfigReload(); static int OnXError(Display* dpy, XErrorEvent* e); From 23e91a47248da256788519bfbd1823cade9449c0 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 12:21:49 +0800 Subject: [PATCH 02/18] window_manager: replace duplicated code with macros --- src/window_manager.cc | 123 +++++++++++++++++++++--------------------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/src/window_manager.cc b/src/window_manager.cc index 4031ea8..99b2e11 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -14,6 +14,39 @@ extern "C" { #include "config.h" #include "util.h" +#define HAS_CLIENT_OR_RETURN(window) \ + do { \ + auto it = Client::mapper_.find(window); \ + if (it == Client::mapper_.end()) { \ + return; \ + } \ + } while (0) + +#define HAS_NO_CLIENT_OR_RETURN(window) \ + do { \ + auto it = Client::mapper_.find(window); \ + if (it != Client::mapper_.end()) { \ + return; \ + } \ + } while (0) + +#define HAS_CLIENT_OR_RETURN_X(window, return_value) \ + do { \ + auto it = Client::mapper_.find(window); \ + if (it == Client::mapper_.end()) { \ + return return_value; \ + } \ + } while (0) + +#define GET_CLIENT_OR_RETURN(window, client) \ + do { \ + auto it = Client::mapper_.find(window); \ + if (it == Client::mapper_.end()) { \ + return; \ + } \ + client = it->second; \ + } while (0) + #define MOUSE_BTN_LEFT 1 #define MOUSE_BTN_MID 2 #define MOUSE_BTN_RIGHT 3 @@ -311,25 +344,19 @@ void WindowManager::OnMapNotify(const XMapEvent& e) { notifications_.insert(e.window); } - auto it = Client::mapper_.find(e.window); - if (it == Client::mapper_.end()) { - return; - } + Client* c = nullptr; + GET_CLIENT_OR_RETURN(e.window, c); - Client* c = it->second; c->set_mapped(true); } void WindowManager::OnUnmapNotify(const XUnmapEvent& e) { - auto it = Client::mapper_.find(e.window); - if (it == Client::mapper_.end()) { - return; - } + Client* c = nullptr; + GET_CLIENT_OR_RETURN(e.window, c); // Some program unmaps their windows but does not remove them, // so if this window has just been unmapped, but it was not unmapped // by the user, then we will remove them for user. - Client* c = it->second; c->set_mapped(false); if (c->has_unmap_req_from_wm()) { @@ -364,12 +391,9 @@ void WindowManager::OnKeyPress(const XKeyEvent& e) { } void WindowManager::OnButtonPress(const XButtonEvent& e) { - auto it = Client::mapper_.find(e.subwindow); - if (it == Client::mapper_.end()) { - return; - } + Client* c = nullptr; + GET_CLIENT_OR_RETURN(e.subwindow, c); - Client* c = it->second; wm_utils::SetNetActiveWindow(c->window()); c->workspace()->UnsetFocusedClient(); c->workspace()->SetFocusedClient(c->window()); @@ -385,12 +409,9 @@ void WindowManager::OnButtonPress(const XButtonEvent& e) { } void WindowManager::OnButtonRelease(const XButtonEvent&) { - auto it = Client::mapper_.find(btn_pressed_event_.subwindow); - if (it == Client::mapper_.end()) { - return; - } + Client* c = nullptr; + GET_CLIENT_OR_RETURN(btn_pressed_event_.subwindow, c); - Client* c = it->second; if (c->is_floating()) { XWindowAttributes attr = wm_utils::GetXWindowAttributes(btn_pressed_event_.subwindow); cookie_.Put(c->window(), {attr.x, attr.y, attr.width, attr.height}); @@ -401,12 +422,9 @@ void WindowManager::OnButtonRelease(const XButtonEvent&) { } void WindowManager::OnMotionNotify(const XButtonEvent& e) { - auto it = Client::mapper_.find(btn_pressed_event_.subwindow); - if (it == Client::mapper_.end()) { - return; - } + Client* c = nullptr; + GET_CLIENT_OR_RETURN(btn_pressed_event_.subwindow, c); - Client* c = it->second; const XWindowAttributes& attr = c->attr_cache(); int xdiff = e.x - btn_pressed_event_.x; int ydiff = e.y - btn_pressed_event_.y; @@ -444,12 +462,11 @@ void WindowManager::OnClientMessage(const XClientMessageEvent& e) { } else if (e.message_type == prop_->net[atom::NET_WM_STATE]) { if (static_cast(e.data.l[1]) == prop_->net[atom::NET_WM_STATE_FULLSCREEN] || static_cast(e.data.l[2]) == prop_->net[atom::NET_WM_STATE_FULLSCREEN]) { - auto it = Client::mapper_.find(e.window); - if (it == Client::mapper_.end()) { - return; - } + Client* c = nullptr; + GET_CLIENT_OR_RETURN(e.window, c); + bool should_fullscreen = e.data.l[0] == 1 /* _NET_WM_STATE_ADD */ - || (e.data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !it->second->is_fullscreen()); + || (e.data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->is_fullscreen()); SetFullscreen(e.window, should_fullscreen); } } @@ -482,12 +499,9 @@ int WindowManager::OnWmDetected(Display*, XErrorEvent*) { } void WindowManager::Manage(Window window) { - // If this window id has a corresponding Client* in Client::mapper_, - // don't process further. - auto it = Client::mapper_.find(window); - if (it != Client::mapper_.end()) { - return; - } + // This window should NOT have a corresponding Client* in Client::mapper_ yet. + // If we really found one, don't re-manage it. + HAS_NO_CLIENT_OR_RETURN(window); // Spawn this window in the specified workspace if such rule exists, // otherwise spawn it in current workspace. @@ -529,12 +543,8 @@ void WindowManager::Manage(Window window) { void WindowManager::Unmanage(Window window) { // If we aren't managing this window, there's no need to proceed further. - auto it = Client::mapper_.find(window); - if (it == Client::mapper_.end()) { - return; - } - - Client* c = it->second; + Client* c = nullptr; + GET_CLIENT_OR_RETURN(window, c); // If the client being destroyed is in fullscreen mode, make sure to unset the // workspace's fullscreen state. @@ -623,13 +633,14 @@ void WindowManager::GotoWorkspace(int next) { } void WindowManager::MoveWindowToWorkspace(Window window, int next) { - auto it = Client::mapper_.find(window); - if (current_ == next || it == Client::mapper_.end() || next < 0 || - next >= (int)workspaces_.size()) { + Client* c = nullptr; + GET_CLIENT_OR_RETURN(window, c); + + // Return early if current_ == next or `next` is out of bounds. + if (current_ == next || next < 0 || next >= (int) workspaces_.size()) { return; } - Client* c = it->second; if (workspaces_[current_]->is_fullscreen()) { SetFullscreen(c->window(), false); } @@ -641,12 +652,9 @@ void WindowManager::MoveWindowToWorkspace(Window window, int next) { } void WindowManager::SetFloating(Window window, bool floating, bool use_default_size) { - auto it = Client::mapper_.find(window); - if (it == Client::mapper_.end()) { - return; - } + Client* c = nullptr; + GET_CLIENT_OR_RETURN(window, c); - Client* c = it->second; if (c->is_fullscreen()) { return; } @@ -661,12 +669,9 @@ void WindowManager::SetFloating(Window window, bool floating, bool use_default_s } void WindowManager::SetFullscreen(Window window, bool fullscreen) { - auto it = Client::mapper_.find(window); - if (it == Client::mapper_.end()) { - return; - } + Client* c = nullptr; + GET_CLIENT_OR_RETURN(window, c); - Client* c = it->second; if (c->is_fullscreen() == fullscreen) { return; } @@ -772,11 +777,7 @@ Client::Area WindowManager::GetTilingArea() const { Client::Area WindowManager::GetFloatingWindowArea(Window window, bool use_default_size) { Client::Area area; - - auto it = Client::mapper_.find(window); - if (it == Client::mapper_.end()) { - return area; - } + HAS_CLIENT_OR_RETURN_X(window, area); if (use_default_size) { pair res = GetDisplayResolution(); From 0be15e69254ad614d49b14dd27f96324337dc980 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 12:47:46 +0800 Subject: [PATCH 03/18] window_manager: implement follow-mouse-focus feature --- src/window_manager.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/window_manager.cc b/src/window_manager.cc index 99b2e11..95073b8 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -444,6 +444,11 @@ void WindowManager::OnMotionNotify(const XButtonEvent& e) { void WindowManager::OnEnterNotify(const XEnterWindowEvent& e) { WM_LOG(INFO, "enter!"); + + HAS_CLIENT_OR_RETURN(e.window); + + workspaces_[current_]->UnsetFocusedClient(); + workspaces_[current_]->SetFocusedClient(e.window); } void WindowManager::OnLeaveNotify(const XEnterWindowEvent& e) { From 2c740ac5f10cfd91e8f09c0dba5c54c7e516c737 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 12:50:35 +0800 Subject: [PATCH 04/18] disable receival of XLeaveWindowEvent since it is redundant --- src/client.cc | 2 +- src/window_manager.cc | 9 --------- src/window_manager.h | 1 - 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/src/client.cc b/src/client.cc index 2e96f97..e7f6d9c 100644 --- a/src/client.cc +++ b/src/client.cc @@ -25,7 +25,7 @@ Client::Client(Display* dpy, Window window, Workspace* workspace) Client::mapper_[window] = this; SetBorderWidth(workspace->config()->border_width()); SetBorderColor(workspace->config()->unfocused_color()); - XSelectInput(dpy, window, EnterWindowMask | LeaveWindowMask); + XSelectInput(dpy, window, EnterWindowMask); } Client::~Client() { diff --git a/src/window_manager.cc b/src/window_manager.cc index 95073b8..699b9f9 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -256,9 +256,6 @@ void WindowManager::Run() { case EnterNotify: OnEnterNotify(event.xcrossing); break; - case LeaveNotify: - OnLeaveNotify(event.xcrossing); - break; case ClientMessage: OnClientMessage(event.xclient); break; @@ -443,18 +440,12 @@ void WindowManager::OnMotionNotify(const XButtonEvent& e) { } void WindowManager::OnEnterNotify(const XEnterWindowEvent& e) { - WM_LOG(INFO, "enter!"); - HAS_CLIENT_OR_RETURN(e.window); workspaces_[current_]->UnsetFocusedClient(); workspaces_[current_]->SetFocusedClient(e.window); } -void WindowManager::OnLeaveNotify(const XEnterWindowEvent& e) { - WM_LOG(INFO, "leave!"); -} - void WindowManager::OnClientMessage(const XClientMessageEvent& e) { if (e.message_type == prop_->wmderland_client_event) { ipc_evmgr_.Handle(e); diff --git a/src/window_manager.h b/src/window_manager.h index 926a456..e61acad 100644 --- a/src/window_manager.h +++ b/src/window_manager.h @@ -54,7 +54,6 @@ class WindowManager { void OnButtonRelease(const XButtonEvent& e); void OnMotionNotify(const XButtonEvent& e); void OnEnterNotify(const XEnterWindowEvent& e); - void OnLeaveNotify(const XLeaveWindowEvent& e); void OnClientMessage(const XClientMessageEvent& e); void OnConfigReload(); static int OnXError(Display* dpy, XErrorEvent* e); From b121de94399d8dde297e4729e08a48a4c7e00f7c Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 14:09:21 +0800 Subject: [PATCH 05/18] window_manager: call ArrangeWindows() after setting focused client --- src/window_manager.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/window_manager.cc b/src/window_manager.cc index 699b9f9..923c41e 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -444,6 +444,7 @@ void WindowManager::OnEnterNotify(const XEnterWindowEvent& e) { workspaces_[current_]->UnsetFocusedClient(); workspaces_[current_]->SetFocusedClient(e.window); + ArrangeWindows(); } void WindowManager::OnClientMessage(const XClientMessageEvent& e) { From e5c1afdb88b4f565ef1455b21179899f2f7eb0a0 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 14:10:11 +0800 Subject: [PATCH 06/18] workspace: remove redundant call to Tree::set_current_node() --- src/workspace.cc | 1 - 1 file changed, 1 deletion(-) diff --git a/src/workspace.cc b/src/workspace.cc index a369802..ec471ad 100644 --- a/src/workspace.cc +++ b/src/workspace.cc @@ -333,7 +333,6 @@ void Workspace::Navigate(Action::Type focus_action_type) { } UnsetFocusedClient(); SetFocusedClient(node->client()->window()); - client_tree_.set_current_node(node); WindowManager::GetInstance()->ArrangeWindows(); return; } else if (node->parent() == client_tree_.root_node()) { From c928d57886305b4c55a4aae66654fb9fd1e57acd Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 14:23:03 +0800 Subject: [PATCH 07/18] implement toggleable focus_follows_mouse in config --- src/config.cc | 7 +++++++ src/config.h.in | 3 +++ src/window_manager.cc | 4 ++++ 3 files changed, 14 insertions(+) diff --git a/src/config.cc b/src/config.cc index b5e5083..445bcc6 100644 --- a/src/config.cc +++ b/src/config.cc @@ -103,6 +103,10 @@ unsigned long Config::unfocused_color() const { return unfocused_color_; } +bool Config::focus_follows_mouse() const { + return focus_follows_mouse_; +} + const map, vector>& Config::keybind_rules() const { return keybind_rules_; } @@ -171,6 +175,7 @@ ifstream& operator>>(ifstream& ifs, Config& config) { config.min_window_height_ = MIN_WINDOW_HEIGHT; config.focused_color_ = DEFAULT_FOCUSED_COLOR; config.unfocused_color_ = DEFAULT_UNFOCUSED_COLOR; + config.focus_follows_mouse_ = DEFAULT_FOCUS_FOLLOWS_MOUSE; config.symtab_.clear(); config.spawn_rules_.clear(); @@ -224,6 +229,8 @@ ifstream& operator>>(ifstream& ifs, Config& config) { config.focused_color_ = std::stoul(value, nullptr, 16); } else if (key == "unfocused_color") { config.unfocused_color_ = std::stoul(value, nullptr, 16); + } else if (key == "focus_follows_mouse") { + stringstream(tokens.back()) >> std::boolalpha >> config.focus_follows_mouse_; } else { WM_LOG(ERROR, "config: unrecognized identifier: " << key); } diff --git a/src/config.h.in b/src/config.h.in index 5a90e35..5509f03 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -57,6 +57,7 @@ extern "C" { #define DEFAULT_BORDER_WIDTH 3 #define DEFAULT_FOCUSED_COLOR 0xffffffff #define DEFAULT_UNFOCUSED_COLOR 0xff41485f +#define DEFAULT_FOCUS_FOLLOWS_MOUSE true #define VARIABLE_PREFIX "$" #define DEFAULT_EXIT_KEY "Mod4+Shift+Escape" @@ -81,6 +82,7 @@ class Config { unsigned int min_window_height() const; unsigned long focused_color() const; unsigned long unfocused_color() const; + bool focus_follows_mouse() const; const std::map, std::vector>& keybind_rules() const; const std::vector& autostart_cmds() const; const std::vector& autostart_cmds_on_reload() const; @@ -113,6 +115,7 @@ class Config { unsigned int min_window_height_; unsigned long focused_color_; unsigned long unfocused_color_; + bool focus_follows_mouse_; // symtab: for storing user-declared identifiers. // spawn_rules_: spawn certain apps in certain workspaces. diff --git a/src/window_manager.cc b/src/window_manager.cc index 923c41e..f1e2472 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -440,6 +440,10 @@ void WindowManager::OnMotionNotify(const XButtonEvent& e) { } void WindowManager::OnEnterNotify(const XEnterWindowEvent& e) { + if (!config_->focus_follows_mouse()) { + return; + } + HAS_CLIENT_OR_RETURN(e.window); workspaces_[current_]->UnsetFocusedClient(); From 14a68c814bf05af92f35a9cf0e95810aad8057d4 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 17:35:02 +0800 Subject: [PATCH 08/18] fix buggy focus_follows_mouse behaviors If a new window spawns, a floating window is floating on top of it, and the mouse cursor is also on top of the floating window, then an infinite loop will occur where both windows are competing for window focus infinitely. --- src/client.cc | 5 ++++- src/client.h | 1 + src/window_manager.cc | 13 +++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/client.cc b/src/client.cc index e7f6d9c..694b8c6 100644 --- a/src/client.cc +++ b/src/client.cc @@ -25,7 +25,6 @@ Client::Client(Display* dpy, Window window, Workspace* workspace) Client::mapper_[window] = this; SetBorderWidth(workspace->config()->border_width()); SetBorderColor(workspace->config()->unfocused_color()); - XSelectInput(dpy, window, EnterWindowMask); } Client::~Client() { @@ -79,6 +78,10 @@ void Client::SetBorderColor(unsigned long color) const { XSetWindowBorder(dpy_, window_, color); } +void Client::SelectInput(long input_mask) const { + XSelectInput(dpy_, window_, input_mask); +} + XWindowAttributes Client::GetXWindowAttributes() const { return wm_utils::GetXWindowAttributes(window_); } diff --git a/src/client.h b/src/client.h index 39caa5c..4d7f6bc 100644 --- a/src/client.h +++ b/src/client.h @@ -42,6 +42,7 @@ class Client { void SetInputFocus() const; void SetBorderWidth(unsigned int width) const; void SetBorderColor(unsigned long color) const; + void SelectInput(long input_mask) const; XWindowAttributes GetXWindowAttributes() const; Window window() const; diff --git a/src/window_manager.cc b/src/window_manager.cc index f1e2472..c319fa6 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -344,6 +344,7 @@ void WindowManager::OnMapNotify(const XMapEvent& e) { Client* c = nullptr; GET_CLIENT_OR_RETURN(e.window, c); + c->SelectInput(EnterWindowMask); c->set_mapped(true); } @@ -354,6 +355,7 @@ void WindowManager::OnUnmapNotify(const XUnmapEvent& e) { // Some program unmaps their windows but does not remove them, // so if this window has just been unmapped, but it was not unmapped // by the user, then we will remove them for user. + c->SelectInput(None); c->set_mapped(false); if (c->has_unmap_req_from_wm()) { @@ -512,6 +514,12 @@ void WindowManager::Manage(Window window) { } Client* prev_focused_client = workspaces_[target]->GetFocusedClient(); + + // Pause the receival of OnEnterNotify events. + if (prev_focused_client) { + prev_focused_client->SelectInput(None); + } + workspaces_[target]->UnsetFocusedClient(); workspaces_[target]->Add(window); UpdateClientList(); // update NET_CLIENT_LIST @@ -540,6 +548,11 @@ void WindowManager::Manage(Window window) { if (target == current_ && !workspaces_[current_]->is_fullscreen()) { ArrangeWindows(); } + + // Restart the receival of OnEnterNotify events. + if (prev_focused_client) { + prev_focused_client->SelectInput(EnterWindowMask); + } } void WindowManager::Unmanage(Window window) { From 3f215efa112c3d79246755df1c3ea168a67019b5 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 18:03:29 +0800 Subject: [PATCH 09/18] window_manager: fix misbehaving fullscreen windows When moving a window to another workspace where a fullscreen window is already there, the newly arrived window will be rendered on top of the fullscreen window. This commit fix that. --- src/window_manager.cc | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/window_manager.cc b/src/window_manager.cc index c319fa6..ad208a9 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -274,15 +274,13 @@ void WindowManager::ArrangeWindows() const { MapDocks(); wm_utils::ClearNetActiveWindow(); return; - } else { - wm_utils::SetNetActiveWindow(focused_client->window()); } + wm_utils::SetNetActiveWindow(focused_client->window()); + if (workspaces_[current_]->is_fullscreen()) { UnmapDocks(); - focused_client->SetBorderWidth(0); - focused_client->MoveResize(0, 0, GetDisplayResolution()); - focused_client->Raise(); + workspaces_[current_]->SetFocusedClient(focused_client->window()); } else { MapDocks(); workspaces_[current_]->MapAllClients(); @@ -692,22 +690,24 @@ void WindowManager::SetFullscreen(Window window, bool fullscreen) { c->set_fullscreen(fullscreen); c->workspace()->set_fullscreen(fullscreen); - c->SetBorderWidth((fullscreen) ? 0 : config_->border_width()); if (fullscreen) { UnmapDocks(); c->set_attr_cache(c->GetXWindowAttributes()); - c->MoveResize(0, 0, GetDisplayResolution()); c->workspace()->UnmapAllClients(); c->Map(); + c->SetBorderWidth(0); + c->MoveResize(0, 0, GetDisplayResolution()); c->workspace()->SetFocusedClient(c->window()); } else { MapDocks(); const XWindowAttributes& attr = c->attr_cache(); + c->SetBorderWidth(config_->border_width()); c->MoveResize(attr.x, attr.y, attr.width, attr.height); - ArrangeWindows(); } + ArrangeWindows(); + // Update window's _NET_WM_STATE_FULLSCREEN property. // If the window is set to be NOT fullscreen, we will simply write a nullptr // with 0 elements. From ab7185dbb0f55424f19dfeed087484aa1d699b45 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 18:22:40 +0800 Subject: [PATCH 10/18] pause receiving OnEnterWindowEvents in WindowManager::ArrangeWindows() --- src/client.cc | 1 + src/window_manager.cc | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/client.cc b/src/client.cc index 694b8c6..71e8b55 100644 --- a/src/client.cc +++ b/src/client.cc @@ -25,6 +25,7 @@ Client::Client(Display* dpy, Window window, Workspace* workspace) Client::mapper_[window] = this; SetBorderWidth(workspace->config()->border_width()); SetBorderColor(workspace->config()->unfocused_color()); + SelectInput(EnterWindowMask); } Client::~Client() { diff --git a/src/window_manager.cc b/src/window_manager.cc index ad208a9..f47a44f 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -56,6 +56,7 @@ extern "C" { #define CURSOR_RESIZE 3 using std::pair; +using std::vector; namespace wmderland { @@ -278,6 +279,12 @@ void WindowManager::ArrangeWindows() const { wm_utils::SetNetActiveWindow(focused_client->window()); + // Pause receiving OnEnterWindowEvents for all windows in current workspace. + vector clients = workspaces_[current_]->GetClients(); + for (const auto c : clients) { + c->SelectInput(None); + } + if (workspaces_[current_]->is_fullscreen()) { UnmapDocks(); workspaces_[current_]->SetFocusedClient(focused_client->window()); @@ -289,6 +296,11 @@ void WindowManager::ArrangeWindows() const { workspaces_[current_]->RaiseAllFloatingClients(); RaiseNotifications(); } + + // Resume receiving OnEnterWindowEvents for all windows in current workspace. + for (const auto c : clients) { + c->SelectInput(EnterWindowMask); + } } void WindowManager::OnConfigureRequest(const XConfigureRequestEvent& e) { @@ -342,7 +354,6 @@ void WindowManager::OnMapNotify(const XMapEvent& e) { Client* c = nullptr; GET_CLIENT_OR_RETURN(e.window, c); - c->SelectInput(EnterWindowMask); c->set_mapped(true); } @@ -353,7 +364,6 @@ void WindowManager::OnUnmapNotify(const XUnmapEvent& e) { // Some program unmaps their windows but does not remove them, // so if this window has just been unmapped, but it was not unmapped // by the user, then we will remove them for user. - c->SelectInput(None); c->set_mapped(false); if (c->has_unmap_req_from_wm()) { @@ -513,11 +523,6 @@ void WindowManager::Manage(Window window) { Client* prev_focused_client = workspaces_[target]->GetFocusedClient(); - // Pause the receival of OnEnterNotify events. - if (prev_focused_client) { - prev_focused_client->SelectInput(None); - } - workspaces_[target]->UnsetFocusedClient(); workspaces_[target]->Add(window); UpdateClientList(); // update NET_CLIENT_LIST @@ -546,11 +551,6 @@ void WindowManager::Manage(Window window) { if (target == current_ && !workspaces_[current_]->is_fullscreen()) { ArrangeWindows(); } - - // Restart the receival of OnEnterNotify events. - if (prev_focused_client) { - prev_focused_client->SelectInput(EnterWindowMask); - } } void WindowManager::Unmanage(Window window) { From 4c5178edea61e5129cd26c90472e1ae9b73b8065 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 18:36:23 +0800 Subject: [PATCH 11/18] fix laggy move/resize windows --- src/window_manager.cc | 12 ++---- src/workspace.cc | 97 ++++++++++++++++++++++++++----------------- src/workspace.h | 3 ++ 3 files changed, 66 insertions(+), 46 deletions(-) diff --git a/src/window_manager.cc b/src/window_manager.cc index f47a44f..3dffe0e 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -56,7 +56,6 @@ extern "C" { #define CURSOR_RESIZE 3 using std::pair; -using std::vector; namespace wmderland { @@ -280,10 +279,7 @@ void WindowManager::ArrangeWindows() const { wm_utils::SetNetActiveWindow(focused_client->window()); // Pause receiving OnEnterWindowEvents for all windows in current workspace. - vector clients = workspaces_[current_]->GetClients(); - for (const auto c : clients) { - c->SelectInput(None); - } + workspaces_[current_]->DisableFocusFollowsMouse(); if (workspaces_[current_]->is_fullscreen()) { UnmapDocks(); @@ -298,9 +294,7 @@ void WindowManager::ArrangeWindows() const { } // Resume receiving OnEnterWindowEvents for all windows in current workspace. - for (const auto c : clients) { - c->SelectInput(EnterWindowMask); - } + workspaces_[current_]->EnableFocusFollowsMouse(); } void WindowManager::OnConfigureRequest(const XConfigureRequestEvent& e) { @@ -402,6 +396,7 @@ void WindowManager::OnButtonPress(const XButtonEvent& e) { GET_CLIENT_OR_RETURN(e.subwindow, c); wm_utils::SetNetActiveWindow(c->window()); + c->workspace()->DisableFocusFollowsMouse(); c->workspace()->UnsetFocusedClient(); c->workspace()->SetFocusedClient(c->window()); c->workspace()->RaiseAllFloatingClients(); @@ -424,6 +419,7 @@ void WindowManager::OnButtonRelease(const XButtonEvent&) { cookie_.Put(c->window(), {attr.x, attr.y, attr.width, attr.height}); } + c->workspace()->EnableFocusFollowsMouse(); btn_pressed_event_.subwindow = None; XDefineCursor(dpy_, root_window_, cursors_[CURSOR_NORMAL]); } diff --git a/src/workspace.cc b/src/workspace.cc index ec471ad..56bae0d 100644 --- a/src/workspace.cc +++ b/src/workspace.cc @@ -244,51 +244,24 @@ void Workspace::UnsetFocusedClient() const { } } -Client* Workspace::GetFocusedClient() const { - if (!client_tree_.current_node()) { - return nullptr; +void Workspace::DisableFocusFollowsMouse() const { + if (!config_->focus_follows_mouse()) { + return; } - return client_tree_.current_node()->client(); -} -Client* Workspace::GetClient(Window window) const { - // We'll get the corresponding client using client mapper - // whose time complexity is O(1). - auto it = Client::mapper_.find(window); - if (it == Client::mapper_.end()) { - return nullptr; + for (const auto c : GetClients()) { + c->SelectInput(None); } - - // But we have to check if it belongs to current workspace! - Client* c = it->second; - return (c->workspace() == this) ? c : nullptr; } -vector Workspace::GetClients() const { - vector clients; - - for (auto leaf : client_tree_.GetLeaves()) { - if (leaf != client_tree_.root_node()) { - clients.push_back(leaf->client()); - } +void Workspace::EnableFocusFollowsMouse() const { + if (!config_->focus_follows_mouse()) { + return; } - return clients; -} - -vector Workspace::GetFloatingClients() const { - vector clients = GetClients(); - clients.erase(std::remove_if(clients.begin(), clients.end(), - [](Client* c) { return !c->is_floating(); }), - clients.end()); - return clients; -} -vector Workspace::GetTilingClients() const { - vector clients = GetClients(); - clients.erase(std::remove_if(clients.begin(), clients.end(), - [](Client* c) { return c->is_floating(); }), - clients.end()); - return clients; + for (const auto c : GetClients()) { + c->SelectInput(EnterWindowMask); + } } void Workspace::Navigate(Action::Type focus_action_type) { @@ -341,6 +314,54 @@ void Workspace::Navigate(Action::Type focus_action_type) { } } +Client* Workspace::GetFocusedClient() const { + if (!client_tree_.current_node()) { + return nullptr; + } + return client_tree_.current_node()->client(); +} + +Client* Workspace::GetClient(Window window) const { + // We'll get the corresponding client using client mapper + // whose time complexity is O(1). + auto it = Client::mapper_.find(window); + if (it == Client::mapper_.end()) { + return nullptr; + } + + // But we have to check if it belongs to current workspace! + Client* c = it->second; + return (c->workspace() == this) ? c : nullptr; +} + +vector Workspace::GetClients() const { + vector clients; + + for (auto leaf : client_tree_.GetLeaves()) { + if (leaf != client_tree_.root_node()) { + clients.push_back(leaf->client()); + } + } + return clients; +} + +vector Workspace::GetFloatingClients() const { + vector clients = GetClients(); + clients.erase(std::remove_if(clients.begin(), clients.end(), + [](Client* c) { return !c->is_floating(); }), + clients.end()); + return clients; +} + +vector Workspace::GetTilingClients() const { + vector clients = GetClients(); + clients.erase(std::remove_if(clients.begin(), clients.end(), + [](Client* c) { return c->is_floating(); }), + clients.end()); + return clients; +} + + Config* Workspace::config() const { return config_; } diff --git a/src/workspace.h b/src/workspace.h index 7dea248..d577024 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -32,6 +32,9 @@ class Workspace { void SetFocusedClient(Window window); void UnsetFocusedClient() const; + void DisableFocusFollowsMouse() const; + void EnableFocusFollowsMouse() const; + void Navigate(Action::Type navigate_action_type); Client* GetFocusedClient() const; Client* GetClient(Window window) const; From 80b0c80654293082132398b998b737533adf8bc2 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 18:43:05 +0800 Subject: [PATCH 12/18] window_manager: unselectinput when a client is unmapped --- src/client.cc | 1 - src/window_manager.cc | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/client.cc b/src/client.cc index 71e8b55..694b8c6 100644 --- a/src/client.cc +++ b/src/client.cc @@ -25,7 +25,6 @@ Client::Client(Display* dpy, Window window, Workspace* workspace) Client::mapper_[window] = this; SetBorderWidth(workspace->config()->border_width()); SetBorderColor(workspace->config()->unfocused_color()); - SelectInput(EnterWindowMask); } Client::~Client() { diff --git a/src/window_manager.cc b/src/window_manager.cc index 3dffe0e..d03a6aa 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -348,6 +348,7 @@ void WindowManager::OnMapNotify(const XMapEvent& e) { Client* c = nullptr; GET_CLIENT_OR_RETURN(e.window, c); + c->SelectInput(EnterWindowMask); c->set_mapped(true); } @@ -358,6 +359,7 @@ void WindowManager::OnUnmapNotify(const XUnmapEvent& e) { // Some program unmaps their windows but does not remove them, // so if this window has just been unmapped, but it was not unmapped // by the user, then we will remove them for user. + c->SelectInput(None); c->set_mapped(false); if (c->has_unmap_req_from_wm()) { From f73c0787c050c45097ae1af6b745265971d5efde Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 19:01:16 +0800 Subject: [PATCH 13/18] example: config: add focus_follows_mouse --- example/config | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/example/config b/example/config index 3c4fccd..fd38298 100644 --- a/example/config +++ b/example/config @@ -16,12 +16,13 @@ ; `set = ` to set a builtin varaible ; `set $ = ` to define your own variable ; ----------------------------------------------------------------------- -set gap_width = 12 +set gap_width = 10 set border_width = 3 set min_window_width = 100 set min_window_height = 100 -set focused_color = ff4c5d70 +set focused_color = ff596e84 set unfocused_color = ff394859 +set focus_follows_mouse = true set $Mod = Mod4 @@ -31,16 +32,16 @@ set $Mod = Mod4 assign URxvt 1 assign jetbrains-idea 2 assign Google-chrome 3 +assign Firefox 3 assign dolphin 4 assign ark 4 assign Et 5 assign Wps 5 assign Wpp 5 -assign Spotify 6 assign vlc 6 -assign Gimp-2.10 7 +assign Popcorn-Time 6 +assign krita 7 assign tiled 7 -assign Wine,steam.exe 8 assign Steam 8 assign PCSX2 8 assign PPSSPPQt 8 @@ -53,6 +54,9 @@ assign Transmission 9 ; [Floating Rules] ; Applications that should be floating by default ; ----------------------------------------------------------------------- +floating plasmashell true +floating krunner true +floating kcalc true floating systemsettings true floating SimpleScreenRecorder true floating Sxiv true @@ -63,19 +67,22 @@ floating Gcolor2 true floating Steam true floating PCSX2 true floating Wine true -floating Wine,steam.exe true -floating Wine,tesv.exe true floating VirtualBox Machine true floating Vigilante true floating xfreerdp true +floating steam_app_72850 true +floating steam_app_105600 true floating com.hacklympics.main.Main true +floating pyRollCall true ; [Fullscreen Rules] ; Applications that should be fullscreen by default ; ----------------------------------------------------------------------- fullscreen Wine,maplelegendswindowed.exe true -fullscreen Wine,tesv.exe true +fullscreen insurgency_linux true +fullscreen steam_app_72850 tesv_original.exe true +fullscreen postal2-bin true ; [Prohibit Rules] @@ -137,6 +144,7 @@ bindsym XF86AudioMute exec amixer -q -D pulse set Master toggle bindsym XF86AudioRaiseVolume exec amixer -q -D pulse set Master 5%+ unmute bindsym XF86AudioLowerVolume exec amixer -q -D pulse set Master 5%- unmute bindsym XF86PowerOff exec qdbus org.kde.ksmserver /KSMServer org.kde.KSMServerInterface.logout -1 -1 -1 +bindsym $Mod+Escape exec toggle_screen.sh bindsym Control+Shift+3 exec scrotutl -f bindsym Control+Shift+4 exec scrotutl -s @@ -150,7 +158,7 @@ bindsym $Mod+Return goto_workspace 1; exec urxvt ; Applications to execute when WM starts up (DON'T append '&' at the end) ; ----------------------------------------------------------------------- exec pulseaudio --start --log-target=syslog -exec ~/.config/mpd/launch.sh +exec_on_reload ~/.config/mpd/launch.sh exec dunst exec dispad exec displayctl From 624f811a29145acc28f384093c9753bc63cd6ad7 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 22:14:56 +0800 Subject: [PATCH 14/18] window_manager: clean up SetFullscreen() --- src/window_manager.cc | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/window_manager.cc b/src/window_manager.cc index d03a6aa..e3f99e4 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -283,7 +283,9 @@ void WindowManager::ArrangeWindows() const { if (workspaces_[current_]->is_fullscreen()) { UnmapDocks(); - workspaces_[current_]->SetFocusedClient(focused_client->window()); + focused_client->SetBorderWidth(0); + focused_client->MoveResize(0, 0, GetDisplayResolution()); + focused_client->workspace()->SetFocusedClient(focused_client->window()); } else { MapDocks(); workspaces_[current_]->MapAllClients(); @@ -690,15 +692,10 @@ void WindowManager::SetFullscreen(Window window, bool fullscreen) { c->workspace()->set_fullscreen(fullscreen); if (fullscreen) { - UnmapDocks(); c->set_attr_cache(c->GetXWindowAttributes()); c->workspace()->UnmapAllClients(); c->Map(); - c->SetBorderWidth(0); - c->MoveResize(0, 0, GetDisplayResolution()); - c->workspace()->SetFocusedClient(c->window()); } else { - MapDocks(); const XWindowAttributes& attr = c->attr_cache(); c->SetBorderWidth(config_->border_width()); c->MoveResize(attr.x, attr.y, attr.width, attr.height); From ec2bbb2320b31f8b32dcebd0dd0ffe42e910bf97 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 22:16:20 +0800 Subject: [PATCH 15/18] window_manager: clean up --- src/window_manager.cc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/window_manager.cc b/src/window_manager.cc index e3f99e4..fdccb4f 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -355,12 +355,12 @@ void WindowManager::OnMapNotify(const XMapEvent& e) { } void WindowManager::OnUnmapNotify(const XUnmapEvent& e) { + // Some program unmaps their windows but does not destroy them, + // so if this window has just been unmapped, but it was not unmapped + // by the user, then we will remove them for user. Client* c = nullptr; GET_CLIENT_OR_RETURN(e.window, c); - // Some program unmaps their windows but does not remove them, - // so if this window has just been unmapped, but it was not unmapped - // by the user, then we will remove them for user. c->SelectInput(None); c->set_mapped(false); @@ -522,7 +522,6 @@ void WindowManager::Manage(Window window) { } Client* prev_focused_client = workspaces_[target]->GetFocusedClient(); - workspaces_[target]->UnsetFocusedClient(); workspaces_[target]->Add(window); UpdateClientList(); // update NET_CLIENT_LIST From a4f175564493f5fb34fd0d0af26aa9c0eab0e95d Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 22:45:11 +0800 Subject: [PATCH 16/18] window_manager: fix double visioned KDE logout screen --- src/window_manager.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/window_manager.cc b/src/window_manager.cc index fdccb4f..6dab54d 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -694,6 +694,7 @@ void WindowManager::SetFullscreen(Window window, bool fullscreen) { c->set_attr_cache(c->GetXWindowAttributes()); c->workspace()->UnmapAllClients(); c->Map(); + c->SetBorderWidth(0); // Weird workaround for "doubled visioned" KDE logout screen :( } else { const XWindowAttributes& attr = c->attr_cache(); c->SetBorderWidth(config_->border_width()); From f312bc5999e8f8df51b691b73f234bbc4a1f8396 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Wed, 26 Feb 2020 23:31:17 +0800 Subject: [PATCH 17/18] window_manager: clean up OnButton{Press,Release}() --- src/window_manager.cc | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/window_manager.cc b/src/window_manager.cc index 6dab54d..9d00552 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -406,11 +406,11 @@ void WindowManager::OnButtonPress(const XButtonEvent& e) { c->workspace()->RaiseAllFloatingClients(); if (c->is_floating() && !c->is_fullscreen()) { - XDefineCursor(dpy_, root_window_, cursors_[e.button]); - c->Raise(); c->set_attr_cache(c->GetXWindowAttributes()); + btn_pressed_event_ = e; + XDefineCursor(dpy_, root_window_, cursors_[e.button]); } } @@ -418,14 +418,15 @@ void WindowManager::OnButtonRelease(const XButtonEvent&) { Client* c = nullptr; GET_CLIENT_OR_RETURN(btn_pressed_event_.subwindow, c); + c->workspace()->EnableFocusFollowsMouse(); + if (c->is_floating()) { XWindowAttributes attr = wm_utils::GetXWindowAttributes(btn_pressed_event_.subwindow); cookie_.Put(c->window(), {attr.x, attr.y, attr.width, attr.height}); - } - c->workspace()->EnableFocusFollowsMouse(); - btn_pressed_event_.subwindow = None; - XDefineCursor(dpy_, root_window_, cursors_[CURSOR_NORMAL]); + btn_pressed_event_.subwindow = None; + XDefineCursor(dpy_, root_window_, cursors_[CURSOR_NORMAL]); + } } void WindowManager::OnMotionNotify(const XButtonEvent& e) { From 4ded7f4ad5ef3599c5d141106198f04bf9c22528 Mon Sep 17 00:00:00 2001 From: Marco Wang Date: Thu, 27 Feb 2020 11:47:13 +0800 Subject: [PATCH 18/18] fix wrong focused client when switching to a workspace with a fullscreen client --- src/window_manager.cc | 20 +++++++++++++++----- src/workspace.cc | 7 ++++--- src/workspace.h | 2 +- 3 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/window_manager.cc b/src/window_manager.cc index 9d00552..a79627b 100644 --- a/src/window_manager.cc +++ b/src/window_manager.cc @@ -283,6 +283,17 @@ void WindowManager::ArrangeWindows() const { if (workspaces_[current_]->is_fullscreen()) { UnmapDocks(); + // At this point, we have to consider two cases: + // 1. `focused_client` is not mapped yet. + // 2. `focused_client` is already mapped. + // + // If we simply call UnmapAllClients() and focused_client->Map(), + // then the fullscreen window will "blink", which is not desirable. + // + // Therefore, we'll UnmapAllClients() except the focused_client + // but still call focused_client->Map() just in case it's not mapped yet. + workspaces_[current_]->UnmapAllClients(/*except_window=*/focused_client->window()); + focused_client->Map(); focused_client->SetBorderWidth(0); focused_client->MoveResize(0, 0, GetDisplayResolution()); focused_client->workspace()->SetFocusedClient(focused_client->window()); @@ -419,7 +430,7 @@ void WindowManager::OnButtonRelease(const XButtonEvent&) { GET_CLIENT_OR_RETURN(btn_pressed_event_.subwindow, c); c->workspace()->EnableFocusFollowsMouse(); - + if (c->is_floating()) { XWindowAttributes attr = wm_utils::GetXWindowAttributes(btn_pressed_event_.subwindow); cookie_.Put(c->window(), {attr.x, attr.y, attr.width, attr.height}); @@ -635,7 +646,6 @@ void WindowManager::GotoWorkspace(int next) { } workspaces_[current_]->UnmapAllClients(); - workspaces_[next]->MapAllClients(); current_ = next; ArrangeWindows(); @@ -649,7 +659,7 @@ void WindowManager::MoveWindowToWorkspace(Window window, int next) { GET_CLIENT_OR_RETURN(window, c); // Return early if current_ == next or `next` is out of bounds. - if (current_ == next || next < 0 || next >= (int) workspaces_.size()) { + if (current_ == next || next < 0 || next >= (int)workspaces_.size()) { return; } @@ -692,11 +702,11 @@ void WindowManager::SetFullscreen(Window window, bool fullscreen) { c->workspace()->set_fullscreen(fullscreen); if (fullscreen) { + // Save the window's position and size before resizing it to fullscreen. c->set_attr_cache(c->GetXWindowAttributes()); c->workspace()->UnmapAllClients(); - c->Map(); - c->SetBorderWidth(0); // Weird workaround for "doubled visioned" KDE logout screen :( } else { + // Restore the window's position and size. const XWindowAttributes& attr = c->attr_cache(); c->SetBorderWidth(config_->border_width()); c->MoveResize(attr.x, attr.y, attr.width, attr.height); diff --git a/src/workspace.cc b/src/workspace.cc index 56bae0d..0ef47b6 100644 --- a/src/workspace.cc +++ b/src/workspace.cc @@ -208,9 +208,11 @@ void Workspace::MapAllClients() const { } } -void Workspace::UnmapAllClients() const { +void Workspace::UnmapAllClients(Window except_window) const { for (const auto c : GetClients()) { - c->Unmap(); + if (c->window() != except_window) { + c->Unmap(); + } } } @@ -361,7 +363,6 @@ vector Workspace::GetTilingClients() const { return clients; } - Config* Workspace::config() const { return config_; } diff --git a/src/workspace.h b/src/workspace.h index d577024..70678b4 100644 --- a/src/workspace.h +++ b/src/workspace.h @@ -27,7 +27,7 @@ class Workspace { void SetTilingDirection(TilingDirection tiling_direction); void MapAllClients() const; - void UnmapAllClients() const; + void UnmapAllClients(Window except_window = None) const; void RaiseAllFloatingClients() const; void SetFocusedClient(Window window); void UnsetFocusedClient() const;