GitLab's annual major release is around the corner. Along with a lot of new and exciting features, there will be a few breaking changes. Learn more here.

x11client.cpp 187 KB
Newer Older
1
/********************************************************************
2 3 4 5 6 7
 KWin - the KDE window manager
 This file is part of the KDE project.

Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>

8 9 10 11 12 13 14 15 16 17 18 19 20
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************/
21
// own
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
22
#include "x11client.h"
23
// kwin
24 25 26
#ifdef KWIN_BUILD_ACTIVITIES
#include "activities.h"
#endif
27
#include "atoms.h"
28
#include "client_machine.h"
29
#include "composite.h"
30
#include "cursor.h"
31
#include "deleted.h"
32
#include "effects.h"
33
#include "focuschain.h"
34
#include "geometrytip.h"
35
#include "group.h"
36
#include "netinfo.h"
37
#include "screens.h"
38
#include "shadow.h"
39 40 41
#ifdef KWIN_BUILD_TABBOX
#include "tabbox.h"
#endif
42
#include "workspace.h"
43
#include "screenedge.h"
44 45 46 47
#include "decorations/decorationbridge.h"
#include "decorations/decoratedclient.h"
#include <KDecoration2/Decoration>
#include <KDecoration2/DecoratedClient>
48
// KDE
49
#include <KLocalizedString>
50
#include <KStartupInfo>
51 52
#include <KWindowSystem>
#include <KColorScheme>
53 54
// Qt
#include <QApplication>
Martin Gräßlin's avatar
Martin Gräßlin committed
55
#include <QDebug>
56
#include <QDir>
57
#include <QFile>
58
#include <QFileInfo>
59
#include <QMouseEvent>
60
#include <QProcess>
Vlad Zagorodniy's avatar
Vlad Zagorodniy committed
61
// xcb
62
#include <xcb/xcb_icccm.h>
63 64
// system
#include <unistd.h>
65 66
// c++
#include <csignal>
67

68
// Put all externs before the namespace statement to allow the linker
69 70 71 72 73
// to resolve them properly

namespace KWin
{

74 75 76 77 78 79 80 81 82 83 84
const long ClientWinMask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
                           XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
                           XCB_EVENT_MASK_KEYMAP_STATE |
                           XCB_EVENT_MASK_BUTTON_MOTION |
                           XCB_EVENT_MASK_POINTER_MOTION | // need this, too!
                           XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
                           XCB_EVENT_MASK_FOCUS_CHANGE |
                           XCB_EVENT_MASK_EXPOSURE |
                           XCB_EVENT_MASK_STRUCTURE_NOTIFY |
                           XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;

85 86 87 88 89 90
// window types that are supported as normal windows (i.e. KWin actually manages them)
const NET::WindowTypes SUPPORTED_MANAGED_WINDOW_TYPES_MASK = NET::NormalMask | NET::DesktopMask | NET::DockMask
        | NET::ToolbarMask | NET::MenuMask | NET::DialogMask /*| NET::OverrideMask*/ | NET::TopMenuMask
        | NET::UtilityMask | NET::SplashMask | NET::NotificationMask | NET::OnScreenDisplayMask
        | NET::CriticalNotificationMask;

91 92 93 94 95 96 97 98 99
// Creating a client:
//  - only by calling Workspace::createClient()
//      - it creates a new client and calls manage() for it
//
// Destroying a client:
//  - destroyClient() - only when the window itself has been destroyed
//      - releaseWindow() - the window is kept, only the client itself is destroyed

/**
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
100
 * \class Client x11client.h
101
 * \brief The Client class encapsulates a window decoration frame.
102
 */
103

104 105 106
/**
 * This ctor is "dumb" - it only initializes data. All the real initialization
 * is done in manage().
107
 */
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
108
X11Client::X11Client()
109
    : AbstractClient()
110
    , m_client()
111
    , m_wrapper()
112
    , m_frame()
113 114
    , m_activityUpdatesBlocked(false)
    , m_blockedActivityUpdatesRequireTransients(false)
115
    , m_moveResizeGrabWindow()
116
    , move_resize_has_keyboard_grab(false)
117
    , m_managed(false)
118 119
    , m_transientForId(XCB_WINDOW_NONE)
    , m_originalTransientForId(XCB_WINDOW_NONE)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
120
    , shade_below(nullptr)
121
    , m_motif(atoms->motif_wm_hints)
122
    , blocks_compositing(false)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
123
    , shadeHoverTimer(nullptr)
124
    , m_colormap(XCB_COLORMAP_NONE)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
125 126
    , in_group(nullptr)
    , ping_timer(nullptr)
127
    , m_killHelperPID(0)
128
    , m_pingTimestamp(XCB_TIME_CURRENT_TIME)
129
    , m_userTime(XCB_TIME_CURRENT_TIME)   // Not known yet
130
    , allowed_actions()
131 132 133
    , shade_geometry_change(false)
    , sm_stacking_order(-1)
    , activitiesDefined(false)
134
    , sessionActivityOverride(false)
135
    , needsXWindowMove(false)
136
    , m_decoInputExtent()
137
    , m_focusOutTimer(nullptr)
138 139
{
    // TODO: Do all as initialization
140 141 142 143
    m_syncRequest.counter = m_syncRequest.alarm = XCB_NONE;
    m_syncRequest.timeout = m_syncRequest.failsafeTimeout = nullptr;
    m_syncRequest.lastTimestamp = xTime();
    m_syncRequest.isPending = false;
144 145

    // Set the initial mapping state
146
    mapping_state = Withdrawn;
147

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
148
    info = nullptr;
149 150 151

    shade_mode = ShadeNone;
    deleting = false;
152
    m_fullscreenMode = FullScreenNone;
153 154
    hidden = false;
    noborder = false;
155
    app_noborder = false;
156 157 158 159
    ignore_focus_stealing = false;
    check_active_modal = false;

    max_mode = MaximizeRestore;
160

161 162
    //Client to workspace connections require that each
    //client constructed be connected to the workspace wrapper
163

164
    m_frameGeometry = QRect(0, 0, 100, 100);   // So that decorations don't start with size being (0,0)
165

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
166
    connect(clientMachine(), &ClientMachine::localhostChanged, this, &X11Client::updateCaption);
167
    connect(options, &Options::configChanged, this, &X11Client::updateMouseGrab);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
168
    connect(options, &Options::condensedTitleChanged, this, &X11Client::updateCaption);
169

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
170
    connect(this, &X11Client::moveResizeCursorChanged, this, [this] (CursorShape cursor) {
171 172 173 174 175 176 177 178 179 180 181
        xcb_cursor_t nativeCursor = Cursor::x11Cursor(cursor);
        m_frame.defineCursor(nativeCursor);
        if (m_decoInputExtent.isValid())
            m_decoInputExtent.defineCursor(nativeCursor);
        if (isMoveResize()) {
            // changing window attributes doesn't change cursor if there's pointer grab active
            xcb_change_active_pointer_grab(connection(), nativeCursor, xTime(),
                XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW);
        }
    });

182
    // SELI TODO: Initialize xsizehints??
183
}
184

185 186
/**
 * "Dumb" destructor.
187
 */
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
188
X11Client::~X11Client()
189
{
190 191 192 193
    if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive
        ::kill(m_killHelperPID, SIGTERM);
        m_killHelperPID = 0;
    }
194 195 196
    if (m_syncRequest.alarm != XCB_NONE) {
        xcb_sync_destroy_alarm(connection(), m_syncRequest.alarm);
    }
Vlad Zagorodniy's avatar
Vlad Zagorodniy committed
197 198 199
    Q_ASSERT(!isMoveResize());
    Q_ASSERT(m_client == XCB_WINDOW_NONE);
    Q_ASSERT(m_wrapper == XCB_WINDOW_NONE);
Vlad Zagorodniy's avatar
Vlad Zagorodniy committed
200
    Q_ASSERT(m_frame == XCB_WINDOW_NONE);
Vlad Zagorodniy's avatar
Vlad Zagorodniy committed
201
    Q_ASSERT(!check_active_modal);
202 203 204
    for (auto it = m_connections.constBegin(); it != m_connections.constEnd(); ++it) {
        disconnect(*it);
    }
205
}
206

207
// Use destroyClient() or releaseWindow(), Client instances cannot be deleted directly
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
208
void X11Client::deleteClient(X11Client *c)
209
{
210
    delete c;
211 212
}

213 214
/**
 * Releases the window. The client has done its job and the window is still existing.
215
 */
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
216
void X11Client::releaseWindow(bool on_shutdown)
217
{
Vlad Zagorodniy's avatar
Vlad Zagorodniy committed
218
    Q_ASSERT(!deleting);
219
    deleting = true;
220 221
#ifdef KWIN_BUILD_TABBOX
    TabBox::TabBox *tabBox = TabBox::TabBox::self();
222
    if (tabBox->isDisplayed() && tabBox->currentClient() == this) {
223 224 225
        tabBox->nextPrev(true);
    }
#endif
226
    destroyWindowManagementInterface();
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
227
    Deleted* del = nullptr;
228 229 230
    if (!on_shutdown) {
        del = Deleted::create(this);
    }
231
    if (isMoveResize())
232
        emit clientFinishUserMovedResized(this);
233
    emit windowClosed(this, del);
234
    finishCompositing();
235
    RuleBook::self()->discardUsed(this, true);   // Remove ForceTemporarily rules
236
    StackingUpdatesBlocker blocker(workspace());
237
    if (isMoveResize())
238
        leaveMoveResize();
239
    finishWindowRules();
240
    blockGeometryUpdates();
241 242
    if (isOnCurrentDesktop() && isShown(true))
        addWorkspaceRepaint(visibleRect());
243
    // Grab X during the release to make removing of properties, setting to withdrawn state
Volker Krause's avatar
Volker Krause committed
244
    // and repareting to root an atomic operation (https://lists.kde.org/?l=kde-devel&m=116448102901184&w=2)
Luboš Luňák's avatar
Luboš Luňák committed
245
    grabXServer();
Vlad Zagorodniy's avatar
Vlad Zagorodniy committed
246
    exportMappingState(XCB_ICCCM_WM_STATE_WITHDRAWN);
247
    setModal(false);   // Otherwise its mainwindow wouldn't get focus
248
    hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags)
249 250
    if (!on_shutdown)
        workspace()->clientHidden(this);
251
    m_frame.unmap();  // Destroying decoration would cause ugly visual effect
252 253
    destroyDecoration();
    cleanGrouping();
254
    if (!on_shutdown) {
255
        workspace()->removeClient(this);
256
        // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7)
257
        info->setDesktop(0);
258
        info->setState(NET::States(), info->state());  // Reset all state flags
259
    }
Martin Gräßlin's avatar
Martin Gräßlin committed
260
    xcb_connection_t *c = connection();
261 262 263
    m_client.deleteProperty(atoms->kde_net_wm_user_creation_time);
    m_client.deleteProperty(atoms->net_frame_extents);
    m_client.deleteProperty(atoms->kde_net_wm_frame_strut);
264
    m_client.reparent(rootWindow(), m_bufferGeometry.x(), m_bufferGeometry.y());
Martin Gräßlin's avatar
Martin Gräßlin committed
265
    xcb_change_save_set(c, XCB_SET_MODE_DELETE, m_client);
266
    m_client.selectInput(XCB_EVENT_MASK_NO_EVENT);
267
    if (on_shutdown)
268
        // Map the window, so it can be found after another WM is started
269
        m_client.map();
270
    // TODO: Preserve minimized, shaded etc. state?
271
    else // Make sure it's not mapped if the app unmapped it (#65279). The app
272
        // may do map+unmap before we initially map the window by calling rawShow() from manage().
273
        m_client.unmap();
274
    m_client.reset();
275
    m_wrapper.reset();
276
    m_frame.reset();
277
    unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry
278 279 280 281
    if (!on_shutdown) {
        disownDataPassedToDeleted();
        del->unrefWindow();
    }
282
    deleteClient(this);
Luboš Luňák's avatar
Luboš Luňák committed
283
    ungrabXServer();
284
}
285

286 287 288
/**
 * Like releaseWindow(), but this one is called when the window has been already destroyed
 * (E.g. The application closed it)
289
 */
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
290
void X11Client::destroyClient()
291
{
Vlad Zagorodniy's avatar
Vlad Zagorodniy committed
292
    Q_ASSERT(!deleting);
293
    deleting = true;
294 295
#ifdef KWIN_BUILD_TABBOX
    TabBox::TabBox *tabBox = TabBox::TabBox::self();
296
    if (tabBox && tabBox->isDisplayed() && tabBox->currentClient() == this) {
297 298 299
        tabBox->nextPrev(true);
    }
#endif
300
    destroyWindowManagementInterface();
301
    Deleted* del = Deleted::create(this);
302
    if (isMoveResize())
303
        emit clientFinishUserMovedResized(this);
304
    emit windowClosed(this, del);
305
    finishCompositing(ReleaseReason::Destroyed);
306
    RuleBook::self()->discardUsed(this, true);   // Remove ForceTemporarily rules
307
    StackingUpdatesBlocker blocker(workspace());
308
    if (isMoveResize())
309
        leaveMoveResize();
310
    finishWindowRules();
311
    blockGeometryUpdates();
312 313 314
    if (isOnCurrentDesktop() && isShown(true))
        addWorkspaceRepaint(visibleRect());
    setModal(false);
315
    hidden = true; // So that it's not considered visible anymore
316
    workspace()->clientHidden(this);
317 318
    destroyDecoration();
    cleanGrouping();
319
    workspace()->removeClient(this);
320
    m_client.reset(); // invalidate
321
    m_wrapper.reset();
322
    m_frame.reset();
323
    unblockGeometryUpdates(); // Don't use GeometryUpdatesBlocker, it would now set the geometry
324 325
    disownDataPassedToDeleted();
    del->unrefWindow();
326
    deleteClient(this);
327
}
328

329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974
/**
 * Manages the clients. This means handling the very first maprequest:
 * reparenting, initial geometry, initial state, placement, etc.
 * Returns false if KWin is not going to manage this window.
 */
bool X11Client::manage(xcb_window_t w, bool isMapped)
{
    StackingUpdatesBlocker stacking_blocker(workspace());

    Xcb::WindowAttributes attr(w);
    Xcb::WindowGeometry windowGeometry(w);
    if (attr.isNull() || windowGeometry.isNull()) {
        return false;
    }

    // From this place on, manage() must not return false
    blockGeometryUpdates();
    setPendingGeometryUpdate(PendingGeometryForced); // Force update when finishing with geometry changes

    embedClient(w, attr->visual, attr->colormap, windowGeometry->depth);

    m_visual = attr->visual;
    bit_depth = windowGeometry->depth;

    // SELI TODO: Order all these things in some sane manner

    const NET::Properties properties =
        NET::WMDesktop |
        NET::WMState |
        NET::WMWindowType |
        NET::WMStrut |
        NET::WMName |
        NET::WMIconGeometry |
        NET::WMIcon |
        NET::WMPid |
        NET::WMIconName;
    const NET::Properties2 properties2 =
        NET::WM2BlockCompositing |
        NET::WM2WindowClass |
        NET::WM2WindowRole |
        NET::WM2UserTime |
        NET::WM2StartupId |
        NET::WM2ExtendedStrut |
        NET::WM2Opacity |
        NET::WM2FullscreenMonitors |
        NET::WM2FrameOverlap |
        NET::WM2GroupLeader |
        NET::WM2Urgency |
        NET::WM2Input |
        NET::WM2Protocols |
        NET::WM2InitialMappingState |
        NET::WM2IconPixmap |
        NET::WM2OpaqueRegion |
        NET::WM2DesktopFileName |
        NET::WM2GTKFrameExtents;

    auto wmClientLeaderCookie = fetchWmClientLeader();
    auto skipCloseAnimationCookie = fetchSkipCloseAnimation();
    auto showOnScreenEdgeCookie = fetchShowOnScreenEdge();
    auto colorSchemeCookie = fetchColorScheme();
    auto firstInTabBoxCookie = fetchFirstInTabBox();
    auto transientCookie = fetchTransient();
    auto activitiesCookie = fetchActivities();
    auto applicationMenuServiceNameCookie = fetchApplicationMenuServiceName();
    auto applicationMenuObjectPathCookie = fetchApplicationMenuObjectPath();

    m_geometryHints.init(window());
    m_motif.init(window());
    info = new WinInfo(this, m_client, rootWindow(), properties, properties2);

    if (isDesktop() && bit_depth == 32) {
        // force desktop windows to be opaque. It's a desktop after all, there is no window below
        bit_depth = 24;
    }

    // If it's already mapped, ignore hint
    bool init_minimize = !isMapped && (info->initialMappingState() == NET::Iconic);

    m_colormap = attr->colormap;

    getResourceClass();
    readWmClientLeader(wmClientLeaderCookie);
    getWmClientMachine();
    getSyncCounter();
    // First only read the caption text, so that setupWindowRules() can use it for matching,
    // and only then really set the caption using setCaption(), which checks for duplicates etc.
    // and also relies on rules already existing
    cap_normal = readName();
    setupWindowRules(false);
    setCaption(cap_normal, true);

    connect(this, &X11Client::windowClassChanged, this, &X11Client::evaluateWindowRules);

    if (Xcb::Extensions::self()->isShapeAvailable())
        xcb_shape_select_input(connection(), window(), true);
    detectShape(window());
    detectNoBorder();
    fetchIconicName();
    setClientFrameExtents(info->gtkFrameExtents());

    // Needs to be done before readTransient() because of reading the group
    checkGroup();
    updateUrgency();
    updateAllowedActions(); // Group affects isMinimizable()

    setModal((info->state() & NET::Modal) != 0);   // Needs to be valid before handling groups
    readTransientProperty(transientCookie);
    setDesktopFileName(rules()->checkDesktopFile(QByteArray(info->desktopFileName()), true).toUtf8());
    getIcons();
    connect(this, &X11Client::desktopFileNameChanged, this, &X11Client::getIcons);

    m_geometryHints.read();
    getMotifHints();
    getWmOpaqueRegion();
    readSkipCloseAnimation(skipCloseAnimationCookie);

    // TODO: Try to obey all state information from info->state()

    setOriginalSkipTaskbar((info->state() & NET::SkipTaskbar) != 0);
    setSkipPager((info->state() & NET::SkipPager) != 0);
    setSkipSwitcher((info->state() & NET::SkipSwitcher) != 0);
    readFirstInTabBox(firstInTabBoxCookie);

    setupCompositing();

    KStartupInfoId asn_id;
    KStartupInfoData asn_data;
    bool asn_valid = workspace()->checkStartupNotification(window(), asn_id, asn_data);

    // Make sure that the input window is created before we update the stacking order
    updateInputWindow();

    workspace()->updateClientLayer(this);

    SessionInfo* session = workspace()->takeSessionInfo(this);
    if (session) {
        init_minimize = session->minimized;
        noborder = session->noBorder;
    }

    setShortcut(rules()->checkShortcut(session ? session->shortcut : QString(), true));

    init_minimize = rules()->checkMinimize(init_minimize, !isMapped);
    noborder = rules()->checkNoBorder(noborder, !isMapped);

    readActivities(activitiesCookie);

    // Initial desktop placement
    int desk = 0;
    if (session) {
        desk = session->desktop;
        if (session->onAllDesktops)
            desk = NET::OnAllDesktops;
        setOnActivities(session->activities);
    } else {
        // If this window is transient, ensure that it is opened on the
        // same window as its parent.  this is necessary when an application
        // starts up on a different desktop than is currently displayed
        if (isTransient()) {
            auto mainclients = mainClients();
            bool on_current = false;
            bool on_all = false;
            AbstractClient* maincl = nullptr;
            // This is slightly duplicated from Placement::placeOnMainWindow()
            for (auto it = mainclients.constBegin();
                    it != mainclients.constEnd();
                    ++it) {
                if (mainclients.count() > 1 &&      // A group-transient
                    (*it)->isSpecialWindow() &&     // Don't consider toolbars etc when placing
                    !(info->state() & NET::Modal))  // except when it's modal (blocks specials as well)
                    continue;
                maincl = *it;
                if ((*it)->isOnCurrentDesktop())
                    on_current = true;
                if ((*it)->isOnAllDesktops())
                    on_all = true;
            }
            if (on_all)
                desk = NET::OnAllDesktops;
            else if (on_current)
                desk = VirtualDesktopManager::self()->current();
            else if (maincl != nullptr)
                desk = maincl->desktop();

            if (maincl)
                setOnActivities(maincl->activities());
        } else { // a transient shall appear on its leader and not drag that around
            if (info->desktop())
                desk = info->desktop(); // Window had the initial desktop property, force it
            if (desktop() == 0 && asn_valid && asn_data.desktop() != 0)
                desk = asn_data.desktop();
        }
#ifdef KWIN_BUILD_ACTIVITIES
        if (Activities::self() && !isMapped && !noborder && isNormalWindow() && !activitiesDefined) {
            //a new, regular window, when we're not recovering from a crash,
            //and it hasn't got an activity. let's try giving it the current one.
            //TODO: decide whether to keep this before the 4.6 release
            //TODO: if we are keeping it (at least as an option), replace noborder checking
            //with a public API for setting windows to be on all activities.
            //something like KWindowSystem::setOnAllActivities or
            //KActivityConsumer::setOnAllActivities
            setOnActivity(Activities::self()->current(), true);
        }
#endif
    }

    if (desk == 0)   // Assume window wants to be visible on the current desktop
        desk = isDesktop() ? static_cast<int>(NET::OnAllDesktops) : VirtualDesktopManager::self()->current();
    desk = rules()->checkDesktop(desk, !isMapped);
    if (desk != NET::OnAllDesktops)   // Do range check
        desk = qBound(1, desk, static_cast<int>(VirtualDesktopManager::self()->count()));
    setDesktop(desk);
    info->setDesktop(desk);
    workspace()->updateOnAllDesktopsOfTransients(this);   // SELI TODO
    //onAllDesktopsChange(); // Decoration doesn't exist here yet

    QString activitiesList;
    activitiesList = rules()->checkActivity(activitiesList, !isMapped);
    if (!activitiesList.isEmpty())
        setOnActivities(activitiesList.split(QStringLiteral(",")));

    QRect geom(windowGeometry.rect());
    bool placementDone = false;

    if (session)
        geom = session->geometry;

    QRect area;
    bool partial_keep_in_area = isMapped || session;
    if (isMapped || session) {
        area = workspace()->clientArea(FullArea, geom.center(), desktop());
        checkOffscreenPosition(&geom, area);
    } else {
        int screen = asn_data.xinerama() == -1 ? screens()->current() : asn_data.xinerama();
        screen = rules()->checkScreen(screen, !isMapped);
        area = workspace()->clientArea(PlacementArea, screens()->geometry(screen).center(), desktop());
    }

    if (isDesktop())
        // KWin doesn't manage desktop windows
        placementDone = true;

    bool usePosition = false;
    if (isMapped || session || placementDone)
        placementDone = true; // Use geometry
    else if (isTransient() && !isUtility() && !isDialog() && !isSplash())
        usePosition = true;
    else if (isTransient() && !hasNETSupport())
        usePosition = true;
    else if (isDialog() && hasNETSupport()) {
        // If the dialog is actually non-NETWM transient window, don't try to apply placement to it,
        // it breaks with too many things (xmms, display)
        if (mainClients().count() >= 1) {
#if 1
            // #78082 - Ok, it seems there are after all some cases when an application has a good
            // reason to specify a position for its dialog. Too bad other WMs have never bothered
            // with placement for dialogs, so apps always specify positions for their dialogs,
            // including such silly positions like always centered on the screen or under mouse.
            // Using ignoring requested position in window-specific settings helps, and now
            // there's also _NET_WM_FULL_PLACEMENT.
            usePosition = true;
#else
            ; // Force using placement policy
#endif
        } else
            usePosition = true;
    } else if (isSplash())
        ; // Force using placement policy
    else
        usePosition = true;
    if (!rules()->checkIgnoreGeometry(!usePosition, true)) {
        if (m_geometryHints.hasPosition()) {
            placementDone = true;
            // Disobey xinerama placement option for now (#70943)
            area = workspace()->clientArea(PlacementArea, geom.center(), desktop());
        }
    }

    if (isMovable() && (geom.x() > area.right() || geom.y() > area.bottom()))
        placementDone = false; // Weird, do not trust.

    if (placementDone) {
        QPoint position = geom.topLeft();
        // Session contains the position of the frame geometry before gravitating.
        if (!session) {
            position = clientPosToFramePos(position);
        }
        move(position);
    }

    // Create client group if the window will have a decoration
    bool dontKeepInArea = false;
    readColorScheme(colorSchemeCookie);

    readApplicationMenuServiceName(applicationMenuServiceNameCookie);
    readApplicationMenuObjectPath(applicationMenuObjectPathCookie);

    updateDecoration(false);   // Also gravitates
    // TODO: Is CentralGravity right here, when resizing is done after gravitating?
    plainResize(rules()->checkSize(sizeForClientSize(geom.size()), !isMapped));

    QPoint forced_pos = rules()->checkPosition(invalidPoint, !isMapped);
    if (forced_pos != invalidPoint) {
        move(forced_pos);
        placementDone = true;
        // Don't keep inside workarea if the window has specially configured position
        partial_keep_in_area = true;
        area = workspace()->clientArea(FullArea, geom.center(), desktop());
    }
    if (!placementDone) {
        // Placement needs to be after setting size
        Placement::self()->place(this, area);
        // The client may have been moved to another screen, update placement area.
        area = workspace()->clientArea(PlacementArea, this);
        dontKeepInArea = true;
        placementDone = true;
    }

    // bugs #285967, #286146, #183694
    // geometry() now includes the requested size and the decoration and is at the correct screen/position (hopefully)
    // Maximization for oversized windows must happen NOW.
    // If we effectively pass keepInArea(), the window will resizeWithChecks() - i.e. constrained
    // to the combo of all screen MINUS all struts on the edges
    // If only one screen struts, this will affect screens as a side-effect, the window is artificailly shrinked
    // below the screen size and as result no more maximized what breaks KMainWindow's stupid width+1, height+1 hack
    // TODO: get KMainWindow a correct state storage what will allow to store the restore size as well.

    if (!session) { // has a better handling of this
        geom_restore = frameGeometry(); // Remember restore geometry
        if (isMaximizable() && (width() >= area.width() || height() >= area.height())) {
            // Window is too large for the screen, maximize in the
            // directions necessary
            const QSize ss = workspace()->clientArea(ScreenArea, area.center(), desktop()).size();
            const QRect fsa = workspace()->clientArea(FullArea, geom.center(), desktop());
            const QSize cs = clientSize();
            int pseudo_max = ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) |
                             ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0);
            if (width() >= area.width())
                pseudo_max |=  MaximizeHorizontal;
            if (height() >= area.height())
                pseudo_max |=  MaximizeVertical;

            // heuristics:
            // if decorated client is smaller than the entire screen, the user might want to move it around (multiscreen)
            // in this case, if the decorated client is bigger than the screen (+1), we don't take this as an
            // attempt for maximization, but just constrain the size (the window simply wants to be bigger)
            // NOTICE
            // i intended a second check on cs < area.size() ("the managed client ("minus border") is smaller
            // than the workspace") but gtk / gimp seems to store it's size including the decoration,
            // thus a former maximized window wil become non-maximized
            bool keepInFsArea = false;
            if (width() < fsa.width() && (cs.width() > ss.width()+1)) {
                pseudo_max &= ~MaximizeHorizontal;
                keepInFsArea = true;
            }
            if (height() < fsa.height() && (cs.height() > ss.height()+1)) {
                pseudo_max &= ~MaximizeVertical;
                keepInFsArea = true;
            }

            if (pseudo_max != MaximizeRestore) {
                maximize((MaximizeMode)pseudo_max);
                // from now on, care about maxmode, since the maximization call will override mode for fix aspects
                dontKeepInArea |= (max_mode == MaximizeFull);
                geom_restore = QRect(); // Use placement when unmaximizing ...
                if (!(max_mode & MaximizeVertical)) {
                    geom_restore.setY(y());   // ...but only for horizontal direction
                    geom_restore.setHeight(height());
                }
                if (!(max_mode & MaximizeHorizontal)) {
                    geom_restore.setX(x());   // ...but only for vertical direction
                    geom_restore.setWidth(width());
                }
            }
            if (keepInFsArea)
                keepInArea(fsa, partial_keep_in_area);
        }
    }

    if ((!isSpecialWindow() || isToolbar()) && isMovable() && !dontKeepInArea)
        keepInArea(area, partial_keep_in_area);

    updateShape();

    // CT: Extra check for stupid jdk 1.3.1. But should make sense in general
    // if client has initial state set to Iconic and is transient with a parent
    // window that is not Iconic, set init_state to Normal
    if (init_minimize && isTransient()) {
        auto mainclients = mainClients();
        for (auto it = mainclients.constBegin();
                it != mainclients.constEnd();
                ++it)
            if ((*it)->isShown(true))
                init_minimize = false; // SELI TODO: Even e.g. for NET::Utility?
    }
    // If a dialog is shown for minimized window, minimize it too
    if (!init_minimize && isTransient() && mainClients().count() > 0 &&
            workspace()->sessionManager()->state() != SessionState::Saving) {
        bool visible_parent = false;
        // Use allMainClients(), to include also main clients of group transients
        // that have been optimized out in X11Client::checkGroupTransients()
        auto mainclients = allMainClients();
        for (auto it = mainclients.constBegin();
                it != mainclients.constEnd();
                ++it)
            if ((*it)->isShown(true))
                visible_parent = true;
        if (!visible_parent) {
            init_minimize = true;
            demandAttention();
        }
    }

    if (init_minimize)
        minimize(true);   // No animation

    // Other settings from the previous session
    if (session) {
        // Session restored windows are not considered to be new windows WRT rules,
        // I.e. obey only forcing rules
        setKeepAbove(session->keepAbove);
        setKeepBelow(session->keepBelow);
        setOriginalSkipTaskbar(session->skipTaskbar);
        setSkipPager(session->skipPager);
        setSkipSwitcher(session->skipSwitcher);
        setShade(session->shaded ? ShadeNormal : ShadeNone);
        setOpacity(session->opacity);
        geom_restore = session->restore;
        if (session->maximized != MaximizeRestore) {
            maximize(MaximizeMode(session->maximized));
        }
        if (session->fullscreen != FullScreenNone) {
            setFullScreen(true, false);
            geom_fs_restore = session->fsrestore;
        }
        checkOffscreenPosition(&geom_restore, area);
        checkOffscreenPosition(&geom_fs_restore, area);
    } else {
        // Window may want to be maximized
        // done after checking that the window isn't larger than the workarea, so that
        // the restore geometry from the checks above takes precedence, and window
        // isn't restored larger than the workarea
        MaximizeMode maxmode = static_cast<MaximizeMode>(
                                   ((info->state() & NET::MaxVert) ? MaximizeVertical : 0) |
                                   ((info->state() & NET::MaxHoriz) ? MaximizeHorizontal : 0));
        MaximizeMode forced_maxmode = rules()->checkMaximize(maxmode, !isMapped);

        // Either hints were set to maximize, or is forced to maximize,
        // or is forced to non-maximize and hints were set to maximize
        if (forced_maxmode != MaximizeRestore || maxmode != MaximizeRestore)
            maximize(forced_maxmode);

        // Read other initial states
        setShade(rules()->checkShade(info->state() & NET::Shaded ? ShadeNormal : ShadeNone, !isMapped));
        setKeepAbove(rules()->checkKeepAbove(info->state() & NET::KeepAbove, !isMapped));
        setKeepBelow(rules()->checkKeepBelow(info->state() & NET::KeepBelow, !isMapped));
        setOriginalSkipTaskbar(rules()->checkSkipTaskbar(info->state() & NET::SkipTaskbar, !isMapped));
        setSkipPager(rules()->checkSkipPager(info->state() & NET::SkipPager, !isMapped));
        setSkipSwitcher(rules()->checkSkipSwitcher(info->state() & NET::SkipSwitcher, !isMapped));
        if (info->state() & NET::DemandsAttention)
            demandAttention();
        if (info->state() & NET::Modal)
            setModal(true);

        setFullScreen(rules()->checkFullScreen(info->state() & NET::FullScreen, !isMapped), false);
    }

    updateAllowedActions(true);

    // Set initial user time directly
    m_userTime = readUserTimeMapTimestamp(asn_valid ? &asn_id : nullptr, asn_valid ? &asn_data : nullptr, session);
    group()->updateUserTime(m_userTime);   // And do what X11Client::updateUserTime() does

    // This should avoid flicker, because real restacking is done
    // only after manage() finishes because of blocking, but the window is shown sooner
    m_frame.lower();
    if (session && session->stackingOrder != -1) {
        sm_stacking_order = session->stackingOrder;
        workspace()->restoreSessionStackingOrder(this);
    }

    if (compositing())
        // Sending ConfigureNotify is done when setting mapping state below,
        // Getting the first sync response means window is ready for compositing
        sendSyncRequest();
    else
        ready_for_painting = true; // set to true in case compositing is turned on later. bug #160393

    if (isShown(true)) {
        bool allow;
        if (session)
            allow = session->active &&
                    (!workspace()->wasUserInteraction() || workspace()->activeClient() == nullptr ||
                     workspace()->activeClient()->isDesktop());
        else
            allow = workspace()->allowClientActivation(this, userTime(), false);

        const bool isSessionSaving = workspace()->sessionManager()->state() == SessionState::Saving;

        // If session saving, force showing new windows (i.e. "save file?" dialogs etc.)
        // also force if activation is allowed
        if( !isOnCurrentDesktop() && !isMapped && !session && ( allow || isSessionSaving ))
            VirtualDesktopManager::self()->setCurrent( desktop());

        // If the window is on an inactive activity during session saving, temporarily force it to show.
        if( !isMapped && !session && isSessionSaving && !isOnCurrentActivity()) {
            setSessionActivityOverride( true );
            foreach( AbstractClient* c, mainClients()) {
                if (X11Client *mc = dynamic_cast<X11Client *>(c)) {
                    mc->setSessionActivityOverride(true);
                }
            }
        }

        if (isOnCurrentDesktop() && !isMapped && !allow && (!session || session->stackingOrder < 0))
            workspace()->restackClientUnderActive(this);

        updateVisibility();

        if (!isMapped) {
            if (allow && isOnCurrentDesktop()) {
                if (!isSpecialWindow())
                    if (options->focusPolicyIsReasonable() && wantsTabFocus())
                        workspace()->requestFocus(this);
            } else if (!session && !isSpecialWindow())
                demandAttention();
        }
    } else
        updateVisibility();
    Q_ASSERT(mapping_state != Withdrawn);
    m_managed = true;
    blockGeometryUpdates(false);

    if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U) {
        // No known user time, set something old
        m_userTime = xTime() - 1000000;
        if (m_userTime == XCB_TIME_CURRENT_TIME || m_userTime == -1U)   // Let's be paranoid
            m_userTime = xTime() - 1000000 + 10;
    }

    //sendSyntheticConfigureNotify(); // Done when setting mapping state

    delete session;

    discardTemporaryRules();
    applyWindowRules(); // Just in case
    RuleBook::self()->discardUsed(this, false);   // Remove ApplyNow rules
    updateWindowRules(Rules::All); // Was blocked while !isManaged()

    setBlockingCompositing(info->isBlockingCompositing());
    readShowOnScreenEdge(showOnScreenEdgeCookie);

    // Forward all opacity values to the frame in case there'll be other CM running.
    connect(Compositor::self(), &Compositor::compositingToggled, this,
        [this](bool active) {
            if (active) {
                return;
            }
            if (opacity() == 1.0) {
                return;
            }
            NETWinInfo info(connection(), frameId(), rootWindow(), NET::Properties(), NET::Properties2());
            info.setOpacity(static_cast<unsigned long>(opacity() * 0xffffffff));
        }
    );

    // TODO: there's a small problem here - isManaged() depends on the mapping state,
    // but this client is not yet in Workspace's client list at this point, will
    // be only done in addClient()
    emit clientManaging(this);
    return true;
}

// Called only from manage()
void X11Client::embedClient(xcb_window_t w, xcb_visualid_t visualid, xcb_colormap_t colormap, uint8_t depth)
{
    Q_ASSERT(m_client == XCB_WINDOW_NONE);
    Q_ASSERT(frameId() == XCB_WINDOW_NONE);
    Q_ASSERT(m_wrapper == XCB_WINDOW_NONE);
    m_client.reset(w, false);

    const uint32_t zero_value = 0;

    xcb_connection_t *conn = connection();

    // We don't want the window to be destroyed when we quit
    xcb_change_save_set(conn, XCB_SET_MODE_INSERT, m_client);

    m_client.selectInput(zero_value);
    m_client.unmap();
    m_client.setBorderWidth(zero_value);

    // Note: These values must match the order in the xcb_cw_t enum
    const uint32_t cw_values[] = {
        0,                                // back_pixmap
        0,                                // border_pixel
        colormap,                    // colormap
        Cursor::x11Cursor(Qt::ArrowCursor)
    };

    const uint32_t cw_mask = XCB_CW_BACK_PIXMAP | XCB_CW_BORDER_PIXEL |
                             XCB_CW_COLORMAP | XCB_CW_CURSOR;

    const uint32_t common_event_mask = XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
                                       XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
                                       XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE |
                                       XCB_EVENT_MASK_BUTTON_MOTION | XCB_EVENT_MASK_POINTER_MOTION |
                                       XCB_EVENT_MASK_KEYMAP_STATE |
                                       XCB_EVENT_MASK_FOCUS_CHANGE |
                                       XCB_EVENT_MASK_EXPOSURE |
                                       XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT;

    const uint32_t frame_event_mask   = common_event_mask | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_VISIBILITY_CHANGE;
    const uint32_t wrapper_event_mask = common_event_mask | XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY;

    const uint32_t client_event_mask = XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE |
                                       XCB_EVENT_MASK_COLOR_MAP_CHANGE |
                                       XCB_EVENT_MASK_ENTER_WINDOW | XCB_EVENT_MASK_LEAVE_WINDOW |
                                       XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE;

    // Create the frame window
    xcb_window_t frame = xcb_generate_id(conn);
    xcb_create_window(conn, depth, frame, rootWindow(), 0, 0, 1, 1, 0,
                      XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);
    m_frame.reset(frame);

    setWindowHandles(m_client);

    // Create the wrapper window
    xcb_window_t wrapperId = xcb_generate_id(conn);
    xcb_create_window(conn, depth, wrapperId, frame, 0, 0, 1, 1, 0,
                      XCB_WINDOW_CLASS_INPUT_OUTPUT, visualid, cw_mask, cw_values);
    m_wrapper.reset(wrapperId);

    m_client.reparent(m_wrapper);

    // We could specify the event masks when we create the windows, but the original
    // Xlib code didn't.  Let's preserve that behavior here for now so we don't end up
    // receiving any unexpected events from the wrapper creation or the reparenting.
    m_frame.selectInput(frame_event_mask);
    m_wrapper.selectInput(wrapper_event_mask);
    m_client.selectInput(client_event_mask);

    updateMouseGrab();
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
975
void X11Client::updateInputWindow()
976
{
977 978 979
    if (!Xcb::Extensions::self()->isShapeInputAvailable())
        return;

980 981
    QRegion region;

982 983
    if (!noBorder() && isDecorated()) {
        const QMargins &r = decoration()->resizeOnlyBorders();
984 985 986 987
        const int left   = r.left();
        const int top    = r.top();
        const int right  = r.right();
        const int bottom = r.bottom();
Martin Gräßlin's avatar
Martin Gräßlin committed
988 989 990
        if (left != 0 || top != 0 || right != 0 || bottom != 0) {
            region = QRegion(-left,
                             -top,
991 992 993
                             decoration()->size().width() + left + right,
                             decoration()->size().height() + top + bottom);
            region = region.subtracted(decoration()->rect());
Martin Gräßlin's avatar
Martin Gräßlin committed
994
        }
995 996 997
    }

    if (region.isEmpty()) {
998
        m_decoInputExtent.reset();
999 1000 1001 1002 1003 1004 1005
        return;
    }

    QRect bounds = region.boundingRect();
    input_offset = bounds.topLeft();

    // Move the bounding rect to screen coordinates
1006
    bounds.translate(frameGeometry().topLeft());
1007 1008 1009 1010

    // Move the region to input window coordinates
    region.translate(-input_offset);

1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
    if (!m_decoInputExtent.isValid()) {
        const uint32_t mask = XCB_CW_OVERRIDE_REDIRECT | XCB_CW_EVENT_MASK;
        const uint32_t values[] = {true,
            XCB_EVENT_MASK_ENTER_WINDOW   |
            XCB_EVENT_MASK_LEAVE_WINDOW   |
            XCB_EVENT_MASK_BUTTON_PRESS   |
            XCB_EVENT_MASK_BUTTON_RELEASE |
            XCB_EVENT_MASK_POINTER_MOTION
        };
        m_decoInputExtent.create(bounds, XCB_WINDOW_CLASS_INPUT_ONLY, mask, values);
1021
        if (mapping_state == Mapped)
1022
            m_decoInputExtent.map();
1023
    } else {
1024
        m_decoInputExtent.setGeometry(bounds);
1025 1026
    }

1027 1028 1029
    const QVector<xcb_rectangle_t> rects = Xcb::regionToRects(region);
    xcb_shape_rectangles(connection(), XCB_SHAPE_SO_SET, XCB_SHAPE_SK_INPUT, XCB_CLIP_ORDERING_UNSORTED,
                         m_decoInputExtent, 0, 0, rects.count(), rects.constData());
1030 1031
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1032
void X11Client::updateDecoration(bool check_workspace_pos, bool force)
1033 1034
{
    if (!force &&
1035
            ((!isDecorated() && noBorder()) || (isDecorated() && !noBorder())))
1036
        return;
1037
    QRect oldgeom = frameGeometry();
1038
    QRect oldClientGeom = oldgeom.adjusted(borderLeft(), borderTop(), -borderRight(), -borderBottom());
1039 1040
    blockGeometryUpdates(true);
    if (force)
1041
        destroyDecoration();
1042
    if (!noBorder()) {
1043
        createDecoration(oldgeom);
1044
    } else
1045
        destroyDecoration();
1046
    updateShadow();
1047
    if (check_workspace_pos)
1048
        checkWorkspacePosition(oldgeom, -2, oldClientGeom);
1049
    updateInputWindow();
1050
    blockGeometryUpdates(false);
1051
    updateFrameExtents();
1052
}
1053

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1054
void X11Client::createDecoration(const QRect& oldgeom)
1055
{
1056 1057 1058
    KDecoration2::Decoration *decoration = Decoration::DecorationBridge::self()->createDecoration(this);
    if (decoration) {
        QMetaObject::invokeMethod(decoration, "update", Qt::QueuedConnection);
1059
        connect(decoration, &KDecoration2::Decoration::shadowChanged, this, &Toplevel::updateShadow);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1060
        connect(decoration, &KDecoration2::Decoration::resizeOnlyBordersChanged, this, &X11Client::updateInputWindow);
1061
        connect(decoration, &KDecoration2::Decoration::bordersChanged, this,
1062
            [this]() {
1063
                updateFrameExtents();
1064
                GeometryUpdatesBlocker blocker(this);
1065 1066 1067 1068
                // TODO: this is obviously idempotent
                // calculateGravitation(true) would have to operate on the old border sizes
//                 move(calculateGravitation(true));
//                 move(calculateGravitation(false));
1069
                QRect oldgeom = frameGeometry();
1070
                plainResize(sizeForClientSize(clientSize()), ForceGeometrySet);
1071 1072
                if (!isShade())
                    checkWorkspacePosition(oldgeom);
1073 1074 1075
                emit geometryShapeChanged(this, oldgeom);
            }
        );
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1076 1077
        connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::widthChanged, this, &X11Client::updateInputWindow);
        connect(decoratedClient()->decoratedClient(), &KDecoration2::DecoratedClient::heightChanged, this, &X11Client::updateInputWindow);
1078
    }
1079
    setDecoration(decoration);
1080

1081 1082 1083 1084 1085 1086 1087 1088
    move(calculateGravitation(false));
    plainResize(sizeForClientSize(clientSize()), ForceGeometrySet);
    if (Compositor::compositing()) {
        discardWindowPixmap();
    }
    emit geometryShapeChanged(this, oldgeom);
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1089
void X11Client::destroyDecoration()
1090
{
1091
    QRect oldgeom = frameGeometry();
1092