Changeset View
Changeset View
Standalone View
Standalone View
group.cpp
Show All 13 Lines | |||||
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | 14 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | 15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | GNU General Public License for more details. | 16 | GNU General Public License for more details. | ||
17 | 17 | | |||
18 | You should have received a copy of the GNU General Public License | 18 | You should have received a copy of the GNU General Public License | ||
19 | along with this program. If not, see <http://www.gnu.org/licenses/>. | 19 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
20 | *********************************************************************/ | 20 | *********************************************************************/ | ||
21 | 21 | | |||
22 | /* | | |||
23 | | ||||
24 | This file contains things relevant to window grouping. | | |||
25 | | ||||
26 | */ | | |||
27 | | ||||
28 | //#define QT_CLEAN_NAMESPACE | 22 | //#define QT_CLEAN_NAMESPACE | ||
29 | 23 | | |||
30 | #include "group.h" | 24 | #include "group.h" | ||
31 | #include <QTextStream> | | |||
32 | #include "workspace.h" | 25 | #include "workspace.h" | ||
33 | #include "x11client.h" | 26 | #include "x11client.h" | ||
34 | #include "effects.h" | 27 | #include "effects.h" | ||
35 | 28 | | |||
36 | #include <kstartupinfo.h> | | |||
37 | #include <KWindowSystem> | 29 | #include <KWindowSystem> | ||
38 | #include <QDebug> | 30 | #include <QDebug> | ||
39 | 31 | | |||
40 | /* | | |||
41 | TODO | | |||
42 | Rename as many uses of 'transient' as possible (hasTransient->hasSubwindow,etc.), | | |||
43 | or I'll get it backwards in half of the cases again. | | |||
44 | */ | | |||
45 | | ||||
46 | namespace KWin | 32 | namespace KWin | ||
47 | { | 33 | { | ||
48 | 34 | | |||
49 | //******************************************** | 35 | //******************************************** | ||
50 | // Group | 36 | // Group | ||
51 | //******************************************** | 37 | //******************************************** | ||
52 | 38 | | |||
53 | Group::Group(xcb_window_t leader_P) | 39 | Group::Group(xcb_window_t leader_P) | ||
▲ Show 20 Lines • Show All 88 Lines • ▼ Show 20 Line(s) | 127 | { | |||
142 | Q_ASSERT(!_members.contains(leader_client)); | 128 | Q_ASSERT(!_members.contains(leader_client)); | ||
143 | leader_client = nullptr; | 129 | leader_client = nullptr; | ||
144 | if (_members.isEmpty()) { | 130 | if (_members.isEmpty()) { | ||
145 | workspace()->removeGroup(this); | 131 | workspace()->removeGroup(this); | ||
146 | delete this; | 132 | delete this; | ||
147 | } | 133 | } | ||
148 | } | 134 | } | ||
149 | 135 | | |||
150 | //*************************************** | | |||
151 | // Workspace | | |||
152 | //*************************************** | | |||
153 | | ||||
154 | Group* Workspace::findGroup(xcb_window_t leader) const | | |||
155 | { | | |||
156 | Q_ASSERT(leader != XCB_WINDOW_NONE); | | |||
157 | for (auto it = groups.constBegin(); | | |||
158 | it != groups.constEnd(); | | |||
159 | ++it) | | |||
160 | if ((*it)->leader() == leader) | | |||
161 | return *it; | | |||
162 | return nullptr; | | |||
163 | } | | |||
164 | | ||||
165 | // Client is group transient, but has no group set. Try to find | | |||
166 | // group with windows with the same client leader. | | |||
167 | Group* Workspace::findClientLeaderGroup(const X11Client *c) const | | |||
168 | { | | |||
169 | Group* ret = nullptr; | | |||
170 | for (auto it = clients.constBegin(); | | |||
171 | it != clients.constEnd(); | | |||
172 | ++it) { | | |||
173 | if (*it == c) | | |||
174 | continue; | | |||
175 | if ((*it)->wmClientLeader() == c->wmClientLeader()) { | | |||
176 | if (ret == nullptr || ret == (*it)->group()) | | |||
177 | ret = (*it)->group(); | | |||
178 | else { | | |||
179 | // There are already two groups with the same client leader. | | |||
180 | // This most probably means the app uses group transients without | | |||
181 | // setting group for its windows. Merging the two groups is a bad | | |||
182 | // hack, but there's no really good solution for this case. | | |||
183 | QList<X11Client *> old_group = (*it)->group()->members(); | | |||
184 | // old_group autodeletes when being empty | | |||
185 | for (int pos = 0; | | |||
186 | pos < old_group.count(); | | |||
187 | ++pos) { | | |||
188 | X11Client *tmp = old_group[ pos ]; | | |||
189 | if (tmp != c) | | |||
190 | tmp->changeClientLeaderGroup(ret); | | |||
191 | } | | |||
192 | } | | |||
193 | } | | |||
194 | } | | |||
195 | return ret; | | |||
196 | } | | |||
197 | | ||||
198 | void Workspace::updateMinimizedOfTransients(AbstractClient* c) | | |||
199 | { | | |||
200 | // if mainwindow is minimized or shaded, minimize transients too | | |||
201 | if (c->isMinimized()) { | | |||
202 | for (auto it = c->transients().constBegin(); | | |||
203 | it != c->transients().constEnd(); | | |||
204 | ++it) { | | |||
205 | if ((*it)->isModal()) | | |||
206 | continue; // there's no reason to hide modal dialogs with the main client | | |||
207 | // but to keep them to eg. watch progress or whatever | | |||
208 | if (!(*it)->isMinimized()) { | | |||
209 | (*it)->minimize(); | | |||
210 | updateMinimizedOfTransients((*it)); | | |||
211 | } | | |||
212 | } | | |||
213 | if (c->isModal()) { // if a modal dialog is minimized, minimize its mainwindow too | | |||
214 | foreach (AbstractClient * c2, c->mainClients()) | | |||
215 | c2->minimize(); | | |||
216 | } | | |||
217 | } else { | | |||
218 | // else unmiminize the transients | | |||
219 | for (auto it = c->transients().constBegin(); | | |||
220 | it != c->transients().constEnd(); | | |||
221 | ++it) { | | |||
222 | if ((*it)->isMinimized()) { | | |||
223 | (*it)->unminimize(); | | |||
224 | updateMinimizedOfTransients((*it)); | | |||
225 | } | | |||
226 | } | | |||
227 | if (c->isModal()) { | | |||
228 | foreach (AbstractClient * c2, c->mainClients()) | | |||
229 | c2->unminimize(); | | |||
230 | } | | |||
231 | } | | |||
232 | } | | |||
233 | | ||||
234 | | ||||
235 | /** | | |||
236 | * Sets the client \a c's transient windows' on_all_desktops property to \a on_all_desktops. | | |||
237 | */ | | |||
238 | void Workspace::updateOnAllDesktopsOfTransients(AbstractClient* c) | | |||
239 | { | | |||
240 | for (auto it = c->transients().constBegin(); | | |||
241 | it != c->transients().constEnd(); | | |||
242 | ++it) { | | |||
243 | if ((*it)->isOnAllDesktops() != c->isOnAllDesktops()) | | |||
244 | (*it)->setOnAllDesktops(c->isOnAllDesktops()); | | |||
245 | } | | |||
246 | } | | |||
247 | | ||||
248 | // A new window has been mapped. Check if it's not a mainwindow for some already existing transient window. | | |||
249 | void Workspace::checkTransients(xcb_window_t w) | | |||
250 | { | | |||
251 | for (auto it = clients.constBegin(); | | |||
252 | it != clients.constEnd(); | | |||
253 | ++it) | | |||
254 | (*it)->checkTransient(w); | | |||
255 | } | | |||
256 | | ||||
257 | | ||||
258 | //**************************************** | | |||
259 | // Toplevel | | |||
260 | //**************************************** | | |||
261 | | ||||
262 | // hacks for broken apps here | | |||
263 | // all resource classes are forced to be lowercase | | |||
264 | bool Toplevel::resourceMatch(const Toplevel* c1, const Toplevel* c2) | | |||
265 | { | | |||
266 | return c1->resourceClass() == c2->resourceClass(); | | |||
267 | } | | |||
268 | | ||||
269 | | ||||
270 | //**************************************** | | |||
271 | // Client | | |||
272 | //**************************************** | | |||
273 | | ||||
274 | bool X11Client::belongToSameApplication(const X11Client *c1, const X11Client *c2, SameApplicationChecks checks) | | |||
275 | { | | |||
276 | bool same_app = false; | | |||
277 | | ||||
278 | // tests that definitely mean they belong together | | |||
279 | if (c1 == c2) | | |||
280 | same_app = true; | | |||
281 | else if (c1->isTransient() && c2->hasTransient(c1, true)) | | |||
282 | same_app = true; // c1 has c2 as mainwindow | | |||
283 | else if (c2->isTransient() && c1->hasTransient(c2, true)) | | |||
284 | same_app = true; // c2 has c1 as mainwindow | | |||
285 | else if (c1->group() == c2->group()) | | |||
286 | same_app = true; // same group | | |||
287 | else if (c1->wmClientLeader() == c2->wmClientLeader() | | |||
288 | && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), | | |||
289 | && c2->wmClientLeader() != c2->window()) // don't use in this test then | | |||
290 | same_app = true; // same client leader | | |||
291 | | ||||
292 | // tests that mean they most probably don't belong together | | |||
293 | else if ((c1->pid() != c2->pid() && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) | | |||
294 | || c1->wmClientMachine(false) != c2->wmClientMachine(false)) | | |||
295 | ; // different processes | | |||
296 | else if (c1->wmClientLeader() != c2->wmClientLeader() | | |||
297 | && c1->wmClientLeader() != c1->window() // if WM_CLIENT_LEADER is not set, it returns window(), | | |||
298 | && c2->wmClientLeader() != c2->window() // don't use in this test then | | |||
299 | && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) | | |||
300 | ; // different client leader | | |||
301 | else if (!resourceMatch(c1, c2)) | | |||
302 | ; // different apps | | |||
303 | else if (!sameAppWindowRoleMatch(c1, c2, checks.testFlag(SameApplicationCheck::RelaxedForActive)) | | |||
304 | && !checks.testFlag(SameApplicationCheck::AllowCrossProcesses)) | | |||
305 | ; // "different" apps | | |||
306 | else if (c1->pid() == 0 || c2->pid() == 0) | | |||
307 | ; // old apps that don't have _NET_WM_PID, consider them different | | |||
308 | // if they weren't found to match above | | |||
309 | else | | |||
310 | same_app = true; // looks like it's the same app | | |||
311 | | ||||
312 | return same_app; | | |||
313 | } | | |||
314 | | ||||
315 | // Non-transient windows with window role containing '#' are always | | |||
316 | // considered belonging to different applications (unless | | |||
317 | // the window role is exactly the same). KMainWindow sets | | |||
318 | // window role this way by default, and different KMainWindow | | |||
319 | // usually "are" different application from user's point of view. | | |||
320 | // This help with no-focus-stealing for e.g. konqy reusing. | | |||
321 | // On the other hand, if one of the windows is active, they are | | |||
322 | // considered belonging to the same application. This is for | | |||
323 | // the cases when opening new mainwindow directly from the application, | | |||
324 | // e.g. 'Open New Window' in konqy ( active_hack == true ). | | |||
325 | bool X11Client::sameAppWindowRoleMatch(const X11Client *c1, const X11Client *c2, bool active_hack) | | |||
326 | { | | |||
327 | if (c1->isTransient()) { | | |||
328 | while (const X11Client *t = dynamic_cast<const X11Client *>(c1->transientFor())) | | |||
329 | c1 = t; | | |||
330 | if (c1->groupTransient()) | | |||
331 | return c1->group() == c2->group(); | | |||
332 | #if 0 | | |||
333 | // if a group transient is in its own group, it didn't possibly have a group, | | |||
334 | // and therefore should be considered belonging to the same app like | | |||
335 | // all other windows from the same app | | |||
336 | || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; | | |||
337 | #endif | | |||
338 | } | | |||
339 | if (c2->isTransient()) { | | |||
340 | while (const X11Client *t = dynamic_cast<const X11Client *>(c2->transientFor())) | | |||
341 | c2 = t; | | |||
342 | if (c2->groupTransient()) | | |||
343 | return c1->group() == c2->group(); | | |||
344 | #if 0 | | |||
345 | || c1->group()->leaderClient() == c1 || c2->group()->leaderClient() == c2; | | |||
346 | #endif | | |||
347 | } | | |||
348 | int pos1 = c1->windowRole().indexOf('#'); | | |||
349 | int pos2 = c2->windowRole().indexOf('#'); | | |||
350 | if ((pos1 >= 0 && pos2 >= 0)) { | | |||
351 | if (!active_hack) // without the active hack for focus stealing prevention, | | |||
352 | return c1 == c2; // different mainwindows are always different apps | | |||
353 | if (!c1->isActive() && !c2->isActive()) | | |||
354 | return c1 == c2; | | |||
355 | else | | |||
356 | return true; | | |||
357 | } | | |||
358 | return true; | | |||
359 | } | | |||
360 | | ||||
361 | /* | | |||
362 | | ||||
363 | Transiency stuff: ICCCM 4.1.2.6, NETWM 7.3 | | |||
364 | | ||||
365 | WM_TRANSIENT_FOR is basically means "this is my mainwindow". | | |||
366 | For NET::Unknown windows, transient windows are considered to be NET::Dialog | | |||
367 | windows, for compatibility with non-NETWM clients. KWin may adjust the value | | |||
368 | of this property in some cases (window pointing to itself or creating a loop, | | |||
369 | keeping NET::Splash windows above other windows from the same app, etc.). | | |||
370 | | ||||
371 | X11Client::transient_for_id is the value of the WM_TRANSIENT_FOR property, after | | |||
372 | possibly being adjusted by KWin. X11Client::transient_for points to the Client | | |||
373 | this Client is transient for, or is NULL. If X11Client::transient_for_id is | | |||
374 | poiting to the root window, the window is considered to be transient | | |||
375 | for the whole window group, as suggested in NETWM 7.3. | | |||
376 | | ||||
377 | In the case of group transient window, X11Client::transient_for is NULL, | | |||
378 | and X11Client::groupTransient() returns true. Such window is treated as | | |||
379 | if it were transient for every window in its window group that has been | | |||
380 | mapped _before_ it (or, to be exact, was added to the same group before it). | | |||
381 | Otherwise two group transients can create loops, which can lead very very | | |||
382 | nasty things (bug #67914 and all its dupes). | | |||
383 | | ||||
384 | X11Client::original_transient_for_id is the value of the property, which | | |||
385 | may be different if X11Client::transient_for_id if e.g. forcing NET::Splash | | |||
386 | to be kept on top of its window group, or when the mainwindow is not mapped | | |||
387 | yet, in which case the window is temporarily made group transient, | | |||
388 | and when the mainwindow is mapped, transiency is re-evaluated. | | |||
389 | | ||||
390 | This can get a bit complicated with with e.g. two Konqueror windows created | | |||
391 | by the same process. They should ideally appear like two independent applications | | |||
392 | to the user. This should be accomplished by all windows in the same process | | |||
393 | having the same window group (needs to be changed in Qt at the moment), and | | |||
394 | using non-group transients poiting to their relevant mainwindow for toolwindows | | |||
395 | etc. KWin should handle both group and non-group transient dialogs well. | | |||
396 | | ||||
397 | In other words: | | |||
398 | - non-transient windows : isTransient() == false | | |||
399 | - normal transients : transientFor() != NULL | | |||
400 | - group transients : groupTransient() == true | | |||
401 | | ||||
402 | - list of mainwindows : mainClients() (call once and loop over the result) | | |||
403 | - list of transients : transients() | | |||
404 | - every window in the group : group()->members() | | |||
405 | */ | | |||
406 | | ||||
407 | Xcb::TransientFor X11Client::fetchTransient() const | | |||
408 | { | | |||
409 | return Xcb::TransientFor(window()); | | |||
410 | } | | |||
411 | | ||||
412 | void X11Client::readTransientProperty(Xcb::TransientFor &transientFor) | | |||
413 | { | | |||
414 | xcb_window_t new_transient_for_id = XCB_WINDOW_NONE; | | |||
415 | if (transientFor.getTransientFor(&new_transient_for_id)) { | | |||
416 | m_originalTransientForId = new_transient_for_id; | | |||
417 | new_transient_for_id = verifyTransientFor(new_transient_for_id, true); | | |||
418 | } else { | | |||
419 | m_originalTransientForId = XCB_WINDOW_NONE; | | |||
420 | new_transient_for_id = verifyTransientFor(XCB_WINDOW_NONE, false); | | |||
421 | } | | |||
422 | setTransient(new_transient_for_id); | | |||
423 | } | | |||
424 | | ||||
425 | void X11Client::readTransient() | | |||
426 | { | | |||
427 | Xcb::TransientFor transientFor = fetchTransient(); | | |||
428 | readTransientProperty(transientFor); | | |||
429 | } | | |||
430 | | ||||
431 | void X11Client::setTransient(xcb_window_t new_transient_for_id) | | |||
432 | { | | |||
433 | if (new_transient_for_id != m_transientForId) { | | |||
434 | removeFromMainClients(); | | |||
435 | X11Client *transient_for = nullptr; | | |||
436 | m_transientForId = new_transient_for_id; | | |||
437 | if (m_transientForId != XCB_WINDOW_NONE && !groupTransient()) { | | |||
438 | transient_for = workspace()->findClient(Predicate::WindowMatch, m_transientForId); | | |||
439 | Q_ASSERT(transient_for != nullptr); // verifyTransient() had to check this | | |||
440 | transient_for->addTransient(this); | | |||
441 | } // checkGroup() will check 'check_active_modal' | | |||
442 | setTransientFor(transient_for); | | |||
443 | checkGroup(nullptr, true); // force, because transiency has changed | | |||
444 | workspace()->updateClientLayer(this); | | |||
445 | workspace()->resetUpdateToolWindowsTimer(); | | |||
446 | emit transientChanged(); | | |||
447 | } | | |||
448 | } | | |||
449 | | ||||
450 | void X11Client::removeFromMainClients() | | |||
451 | { | | |||
452 | if (transientFor()) | | |||
453 | transientFor()->removeTransient(this); | | |||
454 | if (groupTransient()) { | | |||
455 | for (auto it = group()->members().constBegin(); | | |||
456 | it != group()->members().constEnd(); | | |||
457 | ++it) | | |||
458 | (*it)->removeTransient(this); | | |||
459 | } | | |||
460 | } | | |||
461 | | ||||
462 | // *sigh* this transiency handling is madness :( | | |||
463 | // This one is called when destroying/releasing a window. | | |||
464 | // It makes sure this client is removed from all grouping | | |||
465 | // related lists. | | |||
466 | void X11Client::cleanGrouping() | | |||
467 | { | | |||
468 | // qDebug() << "CLEANGROUPING:" << this; | | |||
469 | // for ( auto it = group()->members().begin(); | | |||
470 | // it != group()->members().end(); | | |||
471 | // ++it ) | | |||
472 | // qDebug() << "CL:" << *it; | | |||
473 | // QList<X11Client *> mains; | | |||
474 | // mains = mainClients(); | | |||
475 | // for ( auto it = mains.begin(); | | |||
476 | // it != mains.end(); | | |||
477 | // ++it ) | | |||
478 | // qDebug() << "MN:" << *it; | | |||
479 | removeFromMainClients(); | | |||
480 | // qDebug() << "CLEANGROUPING2:" << this; | | |||
481 | // for ( auto it = group()->members().begin(); | | |||
482 | // it != group()->members().end(); | | |||
483 | // ++it ) | | |||
484 | // qDebug() << "CL2:" << *it; | | |||
485 | // mains = mainClients(); | | |||
486 | // for ( auto it = mains.begin(); | | |||
487 | // it != mains.end(); | | |||
488 | // ++it ) | | |||
489 | // qDebug() << "MN2:" << *it; | | |||
490 | for (auto it = transients().constBegin(); | | |||
491 | it != transients().constEnd(); | | |||
492 | ) { | | |||
493 | if ((*it)->transientFor() == this) { | | |||
494 | removeTransient(*it); | | |||
495 | it = transients().constBegin(); // restart, just in case something more has changed with the list | | |||
496 | } else | | |||
497 | ++it; | | |||
498 | } | | |||
499 | // qDebug() << "CLEANGROUPING3:" << this; | | |||
500 | // for ( auto it = group()->members().begin(); | | |||
501 | // it != group()->members().end(); | | |||
502 | // ++it ) | | |||
503 | // qDebug() << "CL3:" << *it; | | |||
504 | // mains = mainClients(); | | |||
505 | // for ( auto it = mains.begin(); | | |||
506 | // it != mains.end(); | | |||
507 | // ++it ) | | |||
508 | // qDebug() << "MN3:" << *it; | | |||
509 | // HACK | | |||
510 | // removeFromMainClients() did remove 'this' from transient | | |||
511 | // lists of all group members, but then made windows that | | |||
512 | // were transient for 'this' group transient, which again | | |||
513 | // added 'this' to those transient lists :( | | |||
514 | QList<X11Client *> group_members = group()->members(); | | |||
515 | group()->removeMember(this); | | |||
516 | in_group = nullptr; | | |||
517 | for (auto it = group_members.constBegin(); | | |||
518 | it != group_members.constEnd(); | | |||
519 | ++it) | | |||
520 | (*it)->removeTransient(this); | | |||
521 | // qDebug() << "CLEANGROUPING4:" << this; | | |||
522 | // for ( auto it = group_members.begin(); | | |||
523 | // it != group_members.end(); | | |||
524 | // ++it ) | | |||
525 | // qDebug() << "CL4:" << *it; | | |||
526 | m_transientForId = XCB_WINDOW_NONE; | | |||
527 | } | | |||
528 | | ||||
529 | // Make sure that no group transient is considered transient | | |||
530 | // for a window that is (directly or indirectly) transient for it | | |||
531 | // (including another group transients). | | |||
532 | // Non-group transients not causing loops are checked in verifyTransientFor(). | | |||
533 | void X11Client::checkGroupTransients() | | |||
534 | { | | |||
535 | for (auto it1 = group()->members().constBegin(); | | |||
536 | it1 != group()->members().constEnd(); | | |||
537 | ++it1) { | | |||
538 | if (!(*it1)->groupTransient()) // check all group transients in the group | | |||
539 | continue; // TODO optimize to check only the changed ones? | | |||
540 | for (auto it2 = group()->members().constBegin(); | | |||
541 | it2 != group()->members().constEnd(); | | |||
542 | ++it2) { // group transients can be transient only for others in the group, | | |||
543 | // so don't make them transient for the ones that are transient for it | | |||
544 | if (*it1 == *it2) | | |||
545 | continue; | | |||
546 | for (AbstractClient* cl = (*it2)->transientFor(); | | |||
547 | cl != nullptr; | | |||
548 | cl = cl->transientFor()) { | | |||
549 | if (cl == *it1) { | | |||
550 | // don't use removeTransient(), that would modify *it2 too | | |||
551 | (*it2)->removeTransientFromList(*it1); | | |||
552 | continue; | | |||
553 | } | | |||
554 | } | | |||
555 | // if *it1 and *it2 are both group transients, and are transient for each other, | | |||
556 | // make only *it2 transient for *it1 (i.e. subwindow), as *it2 came later, | | |||
557 | // and should be therefore on top of *it1 | | |||
558 | // TODO This could possibly be optimized, it also requires hasTransient() to check for loops. | | |||
559 | if ((*it2)->groupTransient() && (*it1)->hasTransient(*it2, true) && (*it2)->hasTransient(*it1, true)) | | |||
560 | (*it2)->removeTransientFromList(*it1); | | |||
561 | // if there are already windows W1 and W2, W2 being transient for W1, and group transient W3 | | |||
562 | // is added, make it transient only for W2, not for W1, because it's already indirectly | | |||
563 | // transient for it - the indirect transiency actually shouldn't break anything, | | |||
564 | // but it can lead to exponentially expensive operations (#95231) | | |||
565 | // TODO this is pretty slow as well | | |||
566 | for (auto it3 = group()->members().constBegin(); | | |||
567 | it3 != group()->members().constEnd(); | | |||
568 | ++it3) { | | |||
569 | if (*it1 == *it2 || *it2 == *it3 || *it1 == *it3) | | |||
570 | continue; | | |||
571 | if ((*it2)->hasTransient(*it1, false) && (*it3)->hasTransient(*it1, false)) { | | |||
572 | if ((*it2)->hasTransient(*it3, true)) | | |||
573 | (*it2)->removeTransientFromList(*it1); | | |||
574 | if ((*it3)->hasTransient(*it2, true)) | | |||
575 | (*it3)->removeTransientFromList(*it1); | | |||
576 | } | | |||
577 | } | | |||
578 | } | | |||
579 | } | | |||
580 | } | | |||
581 | | ||||
582 | /** | | |||
583 | * Check that the window is not transient for itself, and similar nonsense. | | |||
584 | */ | | |||
585 | xcb_window_t X11Client::verifyTransientFor(xcb_window_t new_transient_for, bool set) | | |||
586 | { | | |||
587 | xcb_window_t new_property_value = new_transient_for; | | |||
588 | // make sure splashscreens are shown above all their app's windows, even though | | |||
589 | // they're in Normal layer | | |||
590 | if (isSplash() && new_transient_for == XCB_WINDOW_NONE) | | |||
591 | new_transient_for = rootWindow(); | | |||
592 | if (new_transient_for == XCB_WINDOW_NONE) { | | |||
593 | if (set) // sometimes WM_TRANSIENT_FOR is set to None, instead of root window | | |||
594 | new_property_value = new_transient_for = rootWindow(); | | |||
595 | else | | |||
596 | return XCB_WINDOW_NONE; | | |||
597 | } | | |||
598 | if (new_transient_for == window()) { // pointing to self | | |||
599 | // also fix the property itself | | |||
600 | qCWarning(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to itself." ; | | |||
601 | new_property_value = new_transient_for = rootWindow(); | | |||
602 | } | | |||
603 | // The transient_for window may be embedded in another application, | | |||
604 | // so kwin cannot see it. Try to find the managed client for the | | |||
605 | // window and fix the transient_for property if possible. | | |||
606 | xcb_window_t before_search = new_transient_for; | | |||
607 | while (new_transient_for != XCB_WINDOW_NONE | | |||
608 | && new_transient_for != rootWindow() | | |||
609 | && !workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { | | |||
610 | Xcb::Tree tree(new_transient_for); | | |||
611 | if (tree.isNull()) { | | |||
612 | break; | | |||
613 | } | | |||
614 | new_transient_for = tree->parent; | | |||
615 | } | | |||
616 | if (X11Client *new_transient_for_client = workspace()->findClient(Predicate::WindowMatch, new_transient_for)) { | | |||
617 | if (new_transient_for != before_search) { | | |||
618 | qCDebug(KWIN_CORE) << "Client " << this << " has WM_TRANSIENT_FOR poiting to non-toplevel window " | | |||
619 | << before_search << ", child of " << new_transient_for_client << ", adjusting."; | | |||
620 | new_property_value = new_transient_for; // also fix the property | | |||
621 | } | | |||
622 | } else | | |||
623 | new_transient_for = before_search; // nice try | | |||
624 | // loop detection | | |||
625 | // group transients cannot cause loops, because they're considered transient only for non-transient | | |||
626 | // windows in the group | | |||
627 | int count = 20; | | |||
628 | xcb_window_t loop_pos = new_transient_for; | | |||
629 | while (loop_pos != XCB_WINDOW_NONE && loop_pos != rootWindow()) { | | |||
630 | X11Client *pos = workspace()->findClient(Predicate::WindowMatch, loop_pos); | | |||
631 | if (pos == nullptr) | | |||
632 | break; | | |||
633 | loop_pos = pos->m_transientForId; | | |||
634 | if (--count == 0 || pos == this) { | | |||
635 | qCWarning(KWIN_CORE) << "Client " << this << " caused WM_TRANSIENT_FOR loop." ; | | |||
636 | new_transient_for = rootWindow(); | | |||
637 | } | | |||
638 | } | | |||
639 | if (new_transient_for != rootWindow() | | |||
640 | && workspace()->findClient(Predicate::WindowMatch, new_transient_for) == nullptr) { | | |||
641 | // it's transient for a specific window, but that window is not mapped | | |||
642 | new_transient_for = rootWindow(); | | |||
643 | } | | |||
644 | if (new_property_value != m_originalTransientForId) | | |||
645 | Xcb::setTransientFor(window(), new_property_value); | | |||
646 | return new_transient_for; | | |||
647 | } | | |||
648 | | ||||
649 | void X11Client::addTransient(AbstractClient* cl) | | |||
650 | { | | |||
651 | AbstractClient::addTransient(cl); | | |||
652 | if (workspace()->mostRecentlyActivatedClient() == this && cl->isModal()) | | |||
653 | check_active_modal = true; | | |||
654 | // qDebug() << "ADDTRANS:" << this << ":" << cl; | | |||
655 | // qDebug() << kBacktrace(); | | |||
656 | // for ( auto it = transients_list.begin(); | | |||
657 | // it != transients_list.end(); | | |||
658 | // ++it ) | | |||
659 | // qDebug() << "AT:" << (*it); | | |||
660 | } | | |||
661 | | ||||
662 | void X11Client::removeTransient(AbstractClient* cl) | | |||
663 | { | | |||
664 | // qDebug() << "REMOVETRANS:" << this << ":" << cl; | | |||
665 | // qDebug() << kBacktrace(); | | |||
666 | // cl is transient for this, but this is going away | | |||
667 | // make cl group transient | | |||
668 | AbstractClient::removeTransient(cl); | | |||
669 | if (cl->transientFor() == this) { | | |||
670 | if (X11Client *c = dynamic_cast<X11Client *>(cl)) { | | |||
671 | c->m_transientForId = XCB_WINDOW_NONE; | | |||
672 | c->setTransientFor(nullptr); // SELI | | |||
673 | // SELI cl->setTransient( rootWindow()); | | |||
674 | c->setTransient(XCB_WINDOW_NONE); | | |||
675 | } | | |||
676 | } | | |||
677 | } | | |||
678 | | ||||
679 | // A new window has been mapped. Check if it's not a mainwindow for this already existing window. | | |||
680 | void X11Client::checkTransient(xcb_window_t w) | | |||
681 | { | | |||
682 | if (m_originalTransientForId != w) | | |||
683 | return; | | |||
684 | w = verifyTransientFor(w, true); | | |||
685 | setTransient(w); | | |||
686 | } | | |||
687 | | ||||
688 | // returns true if cl is the transient_for window for this client, | | |||
689 | // or recursively the transient_for window | | |||
690 | bool X11Client::hasTransient(const AbstractClient* cl, bool indirect) const | | |||
691 | { | | |||
692 | if (const X11Client *c = dynamic_cast<const X11Client *>(cl)) { | | |||
693 | // checkGroupTransients() uses this to break loops, so hasTransient() must detect them | | |||
694 | QList<const X11Client *> set; | | |||
695 | return hasTransientInternal(c, indirect, set); | | |||
696 | } | | |||
697 | return false; | | |||
698 | } | | |||
699 | | ||||
700 | bool X11Client::hasTransientInternal(const X11Client *cl, bool indirect, QList<const X11Client *> &set) const | | |||
701 | { | | |||
702 | if (const X11Client *t = dynamic_cast<const X11Client *>(cl->transientFor())) { | | |||
703 | if (t == this) | | |||
704 | return true; | | |||
705 | if (!indirect) | | |||
706 | return false; | | |||
707 | if (set.contains(cl)) | | |||
708 | return false; | | |||
709 | set.append(cl); | | |||
710 | return hasTransientInternal(t, indirect, set); | | |||
711 | } | | |||
712 | if (!cl->isTransient()) | | |||
713 | return false; | | |||
714 | if (group() != cl->group()) | | |||
715 | return false; | | |||
716 | // cl is group transient, search from top | | |||
717 | if (transients().contains(const_cast< X11Client *>(cl))) | | |||
718 | return true; | | |||
719 | if (!indirect) | | |||
720 | return false; | | |||
721 | if (set.contains(this)) | | |||
722 | return false; | | |||
723 | set.append(this); | | |||
724 | for (auto it = transients().constBegin(); | | |||
725 | it != transients().constEnd(); | | |||
726 | ++it) { | | |||
727 | const X11Client *c = qobject_cast<const X11Client *>(*it); | | |||
728 | if (!c) { | | |||
729 | continue; | | |||
730 | } | | |||
731 | if (c->hasTransientInternal(cl, indirect, set)) | | |||
732 | return true; | | |||
733 | } | | |||
734 | return false; | | |||
735 | } | | |||
736 | | ||||
737 | QList<AbstractClient*> X11Client::mainClients() const | | |||
738 | { | | |||
739 | if (!isTransient()) | | |||
740 | return QList<AbstractClient*>(); | | |||
741 | if (const AbstractClient *t = transientFor()) | | |||
742 | return QList<AbstractClient*>{const_cast< AbstractClient* >(t)}; | | |||
743 | QList<AbstractClient*> result; | | |||
744 | Q_ASSERT(group()); | | |||
745 | for (auto it = group()->members().constBegin(); | | |||
746 | it != group()->members().constEnd(); | | |||
747 | ++it) | | |||
748 | if ((*it)->hasTransient(this, false)) | | |||
749 | result.append(*it); | | |||
750 | return result; | | |||
751 | } | | |||
752 | | ||||
753 | AbstractClient* X11Client::findModal(bool allow_itself) | | |||
754 | { | | |||
755 | for (auto it = transients().constBegin(); | | |||
756 | it != transients().constEnd(); | | |||
757 | ++it) | | |||
758 | if (AbstractClient* ret = (*it)->findModal(true)) | | |||
759 | return ret; | | |||
760 | if (isModal() && allow_itself) | | |||
761 | return this; | | |||
762 | return nullptr; | | |||
763 | } | | |||
764 | | ||||
765 | // X11Client::window_group only holds the contents of the hint, | | |||
766 | // but it should be used only to find the group, not for anything else | | |||
767 | // Argument is only when some specific group needs to be set. | | |||
768 | void X11Client::checkGroup(Group* set_group, bool force) | | |||
769 | { | | |||
770 | Group* old_group = in_group; | | |||
771 | if (old_group != nullptr) | | |||
772 | old_group->ref(); // turn off automatic deleting | | |||
773 | if (set_group != nullptr) { | | |||
774 | if (set_group != in_group) { | | |||
775 | if (in_group != nullptr) | | |||
776 | in_group->removeMember(this); | | |||
777 | in_group = set_group; | | |||
778 | in_group->addMember(this); | | |||
779 | } | | |||
780 | } else if (info->groupLeader() != XCB_WINDOW_NONE) { | | |||
781 | Group* new_group = workspace()->findGroup(info->groupLeader()); | | |||
782 | X11Client *t = qobject_cast<X11Client *>(transientFor()); | | |||
783 | if (t != nullptr && t->group() != new_group) { | | |||
784 | // move the window to the right group (e.g. a dialog provided | | |||
785 | // by different app, but transient for this one, so make it part of that group) | | |||
786 | new_group = t->group(); | | |||
787 | } | | |||
788 | if (new_group == nullptr) // doesn't exist yet | | |||
789 | new_group = new Group(info->groupLeader()); | | |||
790 | if (new_group != in_group) { | | |||
791 | if (in_group != nullptr) | | |||
792 | in_group->removeMember(this); | | |||
793 | in_group = new_group; | | |||
794 | in_group->addMember(this); | | |||
795 | } | | |||
796 | } else { | | |||
797 | if (X11Client *t = qobject_cast<X11Client *>(transientFor())) { | | |||
798 | // doesn't have window group set, but is transient for something | | |||
799 | // so make it part of that group | | |||
800 | Group* new_group = t->group(); | | |||
801 | if (new_group != in_group) { | | |||
802 | if (in_group != nullptr) | | |||
803 | in_group->removeMember(this); | | |||
804 | in_group = t->group(); | | |||
805 | in_group->addMember(this); | | |||
806 | } | | |||
807 | } else if (groupTransient()) { | | |||
808 | // group transient which actually doesn't have a group :( | | |||
809 | // try creating group with other windows with the same client leader | | |||
810 | Group* new_group = workspace()->findClientLeaderGroup(this); | | |||
811 | if (new_group == nullptr) | | |||
812 | new_group = new Group(XCB_WINDOW_NONE); | | |||
813 | if (new_group != in_group) { | | |||
814 | if (in_group != nullptr) | | |||
815 | in_group->removeMember(this); | | |||
816 | in_group = new_group; | | |||
817 | in_group->addMember(this); | | |||
818 | } | | |||
819 | } else { // Not transient without a group, put it in its client leader group. | | |||
820 | // This might be stupid if grouping was used for e.g. taskbar grouping | | |||
821 | // or minimizing together the whole group, but as long as it is used | | |||
822 | // only for dialogs it's better to keep windows from one app in one group. | | |||
823 | Group* new_group = workspace()->findClientLeaderGroup(this); | | |||
824 | if (in_group != nullptr && in_group != new_group) { | | |||
825 | in_group->removeMember(this); | | |||
826 | in_group = nullptr; | | |||
827 | } | | |||
828 | if (new_group == nullptr) | | |||
829 | new_group = new Group(XCB_WINDOW_NONE); | | |||
830 | if (in_group != new_group) { | | |||
831 | in_group = new_group; | | |||
832 | in_group->addMember(this); | | |||
833 | } | | |||
834 | } | | |||
835 | } | | |||
836 | if (in_group != old_group || force) { | | |||
837 | for (auto it = transients().constBegin(); | | |||
838 | it != transients().constEnd(); | | |||
839 | ) { | | |||
840 | auto *c = *it; | | |||
841 | // group transients in the old group are no longer transient for it | | |||
842 | if (c->groupTransient() && c->group() != group()) { | | |||
843 | removeTransientFromList(c); | | |||
844 | it = transients().constBegin(); // restart, just in case something more has changed with the list | | |||
845 | } else | | |||
846 | ++it; | | |||
847 | } | | |||
848 | if (groupTransient()) { | | |||
849 | // no longer transient for ones in the old group | | |||
850 | if (old_group != nullptr) { | | |||
851 | for (auto it = old_group->members().constBegin(); | | |||
852 | it != old_group->members().constEnd(); | | |||
853 | ++it) | | |||
854 | (*it)->removeTransient(this); | | |||
855 | } | | |||
856 | // and make transient for all in the new group | | |||
857 | for (auto it = group()->members().constBegin(); | | |||
858 | it != group()->members().constEnd(); | | |||
859 | ++it) { | | |||
860 | if (*it == this) | | |||
861 | break; // this means the window is only transient for windows mapped before it | | |||
862 | (*it)->addTransient(this); | | |||
863 | } | | |||
864 | } | | |||
865 | // group transient splashscreens should be transient even for windows | | |||
866 | // in group mapped later | | |||
867 | for (auto it = group()->members().constBegin(); | | |||
868 | it != group()->members().constEnd(); | | |||
869 | ++it) { | | |||
870 | if (!(*it)->isSplash()) | | |||
871 | continue; | | |||
872 | if (!(*it)->groupTransient()) | | |||
873 | continue; | | |||
874 | if (*it == this || hasTransient(*it, true)) // TODO indirect? | | |||
875 | continue; | | |||
876 | addTransient(*it); | | |||
877 | } | | |||
878 | } | | |||
879 | if (old_group != nullptr) | | |||
880 | old_group->deref(); // can be now deleted if empty | | |||
881 | checkGroupTransients(); | | |||
882 | checkActiveModal(); | | |||
883 | workspace()->updateClientLayer(this); | | |||
884 | } | | |||
885 | | ||||
886 | // used by Workspace::findClientLeaderGroup() | | |||
887 | void X11Client::changeClientLeaderGroup(Group* gr) | | |||
888 | { | | |||
889 | // transientFor() != NULL are in the group of their mainwindow, so keep them there | | |||
890 | if (transientFor() != nullptr) | | |||
891 | return; | | |||
892 | // also don't change the group for window which have group set | | |||
893 | if (info->groupLeader()) | | |||
894 | return; | | |||
895 | checkGroup(gr); // change group | | |||
896 | } | | |||
897 | | ||||
898 | bool X11Client::check_active_modal = false; | | |||
899 | | ||||
900 | void X11Client::checkActiveModal() | | |||
901 | { | | |||
902 | // if the active window got new modal transient, activate it. | | |||
903 | // cannot be done in AddTransient(), because there may temporarily | | |||
904 | // exist loops, breaking findModal | | |||
905 | X11Client *check_modal = dynamic_cast<X11Client *>(workspace()->mostRecentlyActivatedClient()); | | |||
906 | if (check_modal != nullptr && check_modal->check_active_modal) { | | |||
907 | X11Client *new_modal = dynamic_cast<X11Client *>(check_modal->findModal()); | | |||
908 | if (new_modal != nullptr && new_modal != check_modal) { | | |||
909 | if (!new_modal->isManaged()) | | |||
910 | return; // postpone check until end of manage() | | |||
911 | workspace()->activateClient(new_modal); | | |||
912 | } | | |||
913 | check_modal->check_active_modal = false; | | |||
914 | } | | |||
915 | } | | |||
916 | | ||||
917 | } // namespace | 136 | } // namespace |