1 /*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2012 Martin Gräßlin <mgraesslin@kde.org>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8 */
9 #include "focuschain.h"
10 #include "abstract_client.h"
11 #include "workspace.h"
12
13 namespace KWin
14 {
15
KWIN_SINGLETON_FACTORY_VARIABLE(FocusChain,s_manager)16 KWIN_SINGLETON_FACTORY_VARIABLE(FocusChain, s_manager)
17
18 FocusChain::FocusChain(QObject *parent)
19 : QObject(parent)
20 , m_separateScreenFocus(false)
21 , m_activeClient(nullptr)
22 {
23 }
24
~FocusChain()25 FocusChain::~FocusChain()
26 {
27 s_manager = nullptr;
28 }
29
remove(AbstractClient * client)30 void FocusChain::remove(AbstractClient *client)
31 {
32 for (auto it = m_desktopFocusChains.begin();
33 it != m_desktopFocusChains.end();
34 ++it) {
35 it.value().removeAll(client);
36 }
37 m_mostRecentlyUsed.removeAll(client);
38 }
39
addDesktop(VirtualDesktop * desktop)40 void FocusChain::addDesktop(VirtualDesktop *desktop)
41 {
42 m_desktopFocusChains.insert(desktop, Chain());
43 }
44
removeDesktop(VirtualDesktop * desktop)45 void FocusChain::removeDesktop(VirtualDesktop *desktop)
46 {
47 if (m_currentDesktop == desktop) {
48 m_currentDesktop = nullptr;
49 }
50 m_desktopFocusChains.remove(desktop);
51 }
52
getForActivation(VirtualDesktop * desktop) const53 AbstractClient *FocusChain::getForActivation(VirtualDesktop *desktop) const
54 {
55 return getForActivation(desktop, workspace()->activeOutput());
56 }
57
getForActivation(VirtualDesktop * desktop,AbstractOutput * output) const58 AbstractClient *FocusChain::getForActivation(VirtualDesktop *desktop, AbstractOutput *output) const
59 {
60 auto it = m_desktopFocusChains.constFind(desktop);
61 if (it == m_desktopFocusChains.constEnd()) {
62 return nullptr;
63 }
64 const auto &chain = it.value();
65 for (int i = chain.size() - 1; i >= 0; --i) {
66 auto tmp = chain.at(i);
67 // TODO: move the check into Client
68 if (tmp->isShown(false) && tmp->isOnCurrentActivity()
69 && ( !m_separateScreenFocus || tmp->output() == output)) {
70 return tmp;
71 }
72 }
73 return nullptr;
74 }
75
update(AbstractClient * client,FocusChain::Change change)76 void FocusChain::update(AbstractClient *client, FocusChain::Change change)
77 {
78 if (!client->wantsTabFocus()) {
79 // Doesn't want tab focus, remove
80 remove(client);
81 return;
82 }
83
84 if (client->isOnAllDesktops()) {
85 // Now on all desktops, add it to focus chains it is not already in
86 for (auto it = m_desktopFocusChains.begin();
87 it != m_desktopFocusChains.end();
88 ++it) {
89 auto &chain = it.value();
90 // Making first/last works only on current desktop, don't affect all desktops
91 if (it.key() == m_currentDesktop
92 && (change == MakeFirst || change == MakeLast)) {
93 if (change == MakeFirst) {
94 makeFirstInChain(client, chain);
95 } else {
96 makeLastInChain(client, chain);
97 }
98 } else {
99 insertClientIntoChain(client, chain);
100 }
101 }
102 } else {
103 // Now only on desktop, remove it anywhere else
104 for (auto it = m_desktopFocusChains.begin();
105 it != m_desktopFocusChains.end();
106 ++it) {
107 auto &chain = it.value();
108 if (client->isOnDesktop(it.key())) {
109 updateClientInChain(client, change, chain);
110 } else {
111 chain.removeAll(client);
112 }
113 }
114 }
115
116 // add for most recently used chain
117 updateClientInChain(client, change, m_mostRecentlyUsed);
118 }
119
updateClientInChain(AbstractClient * client,FocusChain::Change change,Chain & chain)120 void FocusChain::updateClientInChain(AbstractClient *client, FocusChain::Change change, Chain &chain)
121 {
122 if (change == MakeFirst) {
123 makeFirstInChain(client, chain);
124 } else if (change == MakeLast) {
125 makeLastInChain(client, chain);
126 } else {
127 insertClientIntoChain(client, chain);
128 }
129 }
130
insertClientIntoChain(AbstractClient * client,Chain & chain)131 void FocusChain::insertClientIntoChain(AbstractClient *client, Chain &chain)
132 {
133 if (chain.contains(client)) {
134 return;
135 }
136 if (m_activeClient && m_activeClient != client &&
137 !chain.empty() && chain.last() == m_activeClient) {
138 // Add it after the active client
139 chain.insert(chain.size() - 1, client);
140 } else {
141 // Otherwise add as the first one
142 chain.append(client);
143 }
144 }
145
moveAfterClient(AbstractClient * client,AbstractClient * reference)146 void FocusChain::moveAfterClient(AbstractClient *client, AbstractClient *reference)
147 {
148 if (!client->wantsTabFocus()) {
149 return;
150 }
151
152 for (auto it = m_desktopFocusChains.begin();
153 it != m_desktopFocusChains.end();
154 ++it) {
155 if (!client->isOnDesktop(it.key())) {
156 continue;
157 }
158 moveAfterClientInChain(client, reference, it.value());
159 }
160 moveAfterClientInChain(client, reference, m_mostRecentlyUsed);
161 }
162
moveAfterClientInChain(AbstractClient * client,AbstractClient * reference,Chain & chain)163 void FocusChain::moveAfterClientInChain(AbstractClient *client, AbstractClient *reference, Chain &chain)
164 {
165 if (!chain.contains(reference)) {
166 return;
167 }
168 if (AbstractClient::belongToSameApplication(reference, client)) {
169 chain.removeAll(client);
170 chain.insert(chain.indexOf(reference), client);
171 } else {
172 chain.removeAll(client);
173 for (int i = chain.size() - 1; i >= 0; --i) {
174 if (AbstractClient::belongToSameApplication(reference, chain.at(i))) {
175 chain.insert(i, client);
176 break;
177 }
178 }
179 }
180 }
181
firstMostRecentlyUsed() const182 AbstractClient *FocusChain::firstMostRecentlyUsed() const
183 {
184 if (m_mostRecentlyUsed.isEmpty()) {
185 return nullptr;
186 }
187 return m_mostRecentlyUsed.first();
188 }
189
nextMostRecentlyUsed(AbstractClient * reference) const190 AbstractClient *FocusChain::nextMostRecentlyUsed(AbstractClient *reference) const
191 {
192 if (m_mostRecentlyUsed.isEmpty()) {
193 return nullptr;
194 }
195 const int index = m_mostRecentlyUsed.indexOf(reference);
196 if (index == -1) {
197 return m_mostRecentlyUsed.first();
198 }
199 if (index == 0) {
200 return m_mostRecentlyUsed.last();
201 }
202 return m_mostRecentlyUsed.at(index - 1);
203 }
204
205 // copied from activation.cpp
isUsableFocusCandidate(AbstractClient * c,AbstractClient * prev) const206 bool FocusChain::isUsableFocusCandidate(AbstractClient *c, AbstractClient *prev) const
207 {
208 return c != prev &&
209 c->isShown(false) && c->isOnCurrentDesktop() && c->isOnCurrentActivity() &&
210 (!m_separateScreenFocus || c->isOnOutput(prev ? prev->output() : workspace()->activeOutput()));
211 }
212
nextForDesktop(AbstractClient * reference,VirtualDesktop * desktop) const213 AbstractClient *FocusChain::nextForDesktop(AbstractClient *reference, VirtualDesktop *desktop) const
214 {
215 auto it = m_desktopFocusChains.constFind(desktop);
216 if (it == m_desktopFocusChains.constEnd()) {
217 return nullptr;
218 }
219 const auto &chain = it.value();
220 for (int i = chain.size() - 1; i >= 0; --i) {
221 auto client = chain.at(i);
222 if (isUsableFocusCandidate(client, reference)) {
223 return client;
224 }
225 }
226 return nullptr;
227 }
228
makeFirstInChain(AbstractClient * client,Chain & chain)229 void FocusChain::makeFirstInChain(AbstractClient *client, Chain &chain)
230 {
231 chain.removeAll(client);
232 if (options->moveMinimizedWindowsToEndOfTabBoxFocusChain()) {
233 if (client->isMinimized()) { // add it before the first minimized ...
234 for (int i = chain.count()-1; i >= 0; --i) {
235 if (chain.at(i)->isMinimized()) {
236 chain.insert(i+1, client);
237 return;
238 }
239 }
240 chain.prepend(client); // ... or at end of chain
241 } else {
242 chain.append(client);
243 }
244 } else {
245 chain.append(client);
246 }
247 }
248
makeLastInChain(AbstractClient * client,Chain & chain)249 void FocusChain::makeLastInChain(AbstractClient *client, Chain &chain)
250 {
251 chain.removeAll(client);
252 chain.prepend(client);
253 }
254
contains(AbstractClient * client,VirtualDesktop * desktop) const255 bool FocusChain::contains(AbstractClient *client, VirtualDesktop *desktop) const
256 {
257 auto it = m_desktopFocusChains.constFind(desktop);
258 if (it == m_desktopFocusChains.constEnd()) {
259 return false;
260 }
261 return it.value().contains(client);
262 }
263
264 } // namespace
265