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