client.cpp 80.3 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
22
23
24
25
26

#include "client.h"

#include <QApplication>
#include <QPainter>
#include <QDateTime>
Laurent Montel's avatar
Laurent Montel committed
27
#include <QProcess>
Philipp Knechtges's avatar
Philipp Knechtges committed
28
#include <QPaintEngine>
Cedric Bellegarde's avatar
Cedric Bellegarde committed
29

30
31
32
33
#ifdef KWIN_BUILD_SCRIPTING
#include <QScriptEngine>
#include <QScriptProgram>
#endif
Cedric Bellegarde's avatar
Cedric Bellegarde committed
34

35
36
37
#include <unistd.h>
#include <kstandarddirs.h>
#include <QWhatsThis>
Luboš Luňák's avatar
Luboš Luňák committed
38
#include <kwindowsystem.h>
39
40
#include <kiconloader.h>
#include <stdlib.h>
41
#include <signal.h>
42
43

#include "bridge.h"
44
#include "composite.h"
45
46
47
48
49
#include "group.h"
#include "workspace.h"
#include "atoms.h"
#include "notifications.h"
#include "rules.h"
50
#include "shadow.h"
51
#include "deleted.h"
52
#include "paintredirector.h"
53
#ifdef KWIN_BUILD_TABBOX
54
#include "tabbox.h"
55
#endif
56
57
58
59

#include <X11/extensions/shape.h>
#include <QX11Info>

60
61
62
63
#ifdef HAVE_XSYNC
#include <X11/extensions/sync.h>
#endif

64
65
66
67
68
#ifdef HAVE_XRENDER
#include <X11/extensions/Xrender.h>
#endif


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

namespace KWin
{

75
76
bool Client::s_haveResizeEffect = false;

77
78
79
80
81
82
83
84
85
86
87
88
// 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

/**
 * \class Client client.h
 * \brief The Client class encapsulates a window decoration frame.
 */
89

90
91
92
/**
 * This ctor is "dumb" - it only initializes data. All the real initialization
 * is done in manage().
93
 */
94
95
96
97
98
99
100
101
Client::Client(Workspace* ws)
    : Toplevel(ws)
    , client(None)
    , wrapper(None)
    , decoration(NULL)
    , bridge(new Bridge(this))
    , move_resize_grab_window(None)
    , move_resize_has_keyboard_grab(false)
102
    , m_managed(false)
103
104
105
    , transient_for (NULL)
    , transient_for_id(None)
    , original_transient_for_id(None)
106
    , shade_below(NULL)
107
    , skip_switcher(false)
108
    , blocks_compositing(false)
109
110
111
112
113
    , autoRaiseTimer(NULL)
    , shadeHoverTimer(NULL)
    , delayedMoveResizeTimer(NULL)
    , in_group(NULL)
    , window_group(None)
Thomas Lübking's avatar
Thomas Lübking committed
114
    , tab_group(NULL)
115
116
    , in_layer(UnknownLayer)
    , ping_timer(NULL)
117
    , m_killHelperPID(0)
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
    , user_time(CurrentTime)   // Not known yet
    , allowed_actions(0)
    , block_geometry_updates(0)
    , pending_geometry_update(PendingGeometryNone)
    , shade_geometry_change(false)
    , border_left(0)
    , border_right(0)
    , border_top(0)
    , border_bottom(0)
    , padding_left(0)
    , padding_right(0)
    , padding_top(0)
    , padding_bottom(0)
    , sm_stacking_order(-1)
    , demandAttentionKNotifyTimer(NULL)
    , paintRedirector(0)
134
    , m_firstInTabBox(false)
135
136
    , electricMaximizing(false)
    , activitiesDefined(false)
Chani Armitage's avatar
Chani Armitage committed
137
    , needsSessionInteract(false)
138
    , needsXWindowMove(false)
Cedric Bellegarde's avatar
Cedric Bellegarde committed
139
140
141
#ifdef KWIN_BUILD_KAPPMENU
    , m_menuAvailable(false)
#endif
142
    , input_window(None)
143
144
{
    // TODO: Do all as initialization
Thomas Lübking's avatar
Thomas Lübking committed
145
146
147
148
149
#ifdef HAVE_XSYNC
    syncRequest.counter = syncRequest.alarm = None;
    syncRequest.timeout = syncRequest.failsafeTimeout = NULL;
    syncRequest.isPending = false;
#endif
150
151

    // Set the initial mapping state
152
    mapping_state = Withdrawn;
153
    quick_tile_mode = QuickTileNone;
154
    desk = 0; // No desktop yet
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176

    mode = PositionCenter;
    buttonDown = false;
    moveResizeMode = false;

    info = NULL;

    shade_mode = ShadeNone;
    active = false;
    deleting = false;
    keep_above = false;
    keep_below = false;
    motif_may_move = true;
    motif_may_resize = true;
    motif_may_close = true;
    fullscreen_mode = FullScreenNone;
    skip_taskbar = false;
    original_skip_taskbar = false;
    minimized = false;
    hidden = false;
    modal = false;
    noborder = false;
177
    app_noborder = false;
178
    motif_noborder = false;
179
180
181
182
183
184
185
186
187
188
189
190
191
192
    urgency = false;
    ignore_focus_stealing = false;
    demands_attention = false;
    check_active_modal = false;

    Pdeletewindow = 0;
    Ptakefocus = 0;
    Ptakeactivity = 0;
    Pcontexthelp = 0;
    Pping = 0;
    input = false;
    skip_pager = false;

    max_mode = MaximizeRestore;
193

194
    cmap = None;
195

196
197
    //Client to workspace connections require that each
    //client constructed be connected to the workspace wrapper
198

199
#ifdef KWIN_BUILD_TABBOX
200
    // TabBoxClient
201
    m_tabBoxClient = QSharedPointer<TabBox::TabBoxClientImpl>(new TabBox::TabBoxClientImpl(this));
202
#endif
203

204
205
    geom = QRect(0, 0, 100, 100);   // So that decorations don't start with size being (0,0)
    client_size = QSize(100, 100);
206
    ready_for_painting = false; // wait for first damage or sync reply
207

208
    connect(this, SIGNAL(geometryShapeChanged(KWin::Toplevel*,QRect)), SIGNAL(geometryChanged()));
209
210
    connect(this, SIGNAL(clientMaximizedStateChanged(KWin::Client*,KDecorationDefines::MaximizeMode)), SIGNAL(geometryChanged()));
    connect(this, SIGNAL(clientStepUserMovedResized(KWin::Client*,QRect)), SIGNAL(geometryChanged()));
211
212
    connect(this, SIGNAL(clientStartUserMovedResized(KWin::Client*)), SIGNAL(moveResizedChanged()));
    connect(this, SIGNAL(clientFinishUserMovedResized(KWin::Client*)), SIGNAL(moveResizedChanged()));
213

214
    // SELI TODO: Initialize xsizehints??
215
}
216

217
218
/**
 * "Dumb" destructor.
219
220
 */
Client::~Client()
221
{
222
223
224
225
    if (m_killHelperPID && !::kill(m_killHelperPID, 0)) { // means the process is alive
        ::kill(m_killHelperPID, SIGTERM);
        m_killHelperPID = 0;
    }
226
    //SWrapper::Client::clientRelease(this);
227
#ifdef HAVE_XSYNC
Thomas Lübking's avatar
Thomas Lübking committed
228
229
    if (syncRequest.alarm != None)
        XSyncDestroyAlarm(display(), syncRequest.alarm);
230
#endif
231
    assert(!moveResizeMode);
232
233
    assert(client == None);
    assert(wrapper == None);
234
    //assert( frameId() == None );
235
236
237
    assert(decoration == NULL);
    assert(block_geometry_updates == 0);
    assert(!check_active_modal);
238
    delete bridge;
239
}
240

241
// Use destroyClient() or releaseWindow(), Client instances cannot be deleted directly
242
243
void Client::deleteClient(Client* c, allowed_t)
{
244
    delete c;
245
246
}

247
248
/**
 * Releases the window. The client has done its job and the window is still existing.
249
 */
250
251
252
void Client::releaseWindow(bool on_shutdown)
{
    assert(!deleting);
253
    deleting = true;
254
255
256
257
    Deleted* del = NULL;
    if (!on_shutdown) {
        del = Deleted::create(this);
    }
258
259
    if (moveResizeMode)
        emit clientFinishUserMovedResized(this);
260
    emit windowClosed(this, del);
261
    finishCompositing();
262
263
    workspace()->discardUsedWindowRules(this, true);   // Remove ForceTemporarily rules
    StackingUpdatesBlocker blocker(workspace());
264
    if (moveResizeMode)
265
        leaveMoveResize();
266
267
    finishWindowRules();
    ++block_geometry_updates;
268
269
    if (isOnCurrentDesktop() && isShown(true))
        addWorkspaceRepaint(visibleRect());
270
    // Grab X during the release to make removing of properties, setting to withdrawn state
Luboš Luňák's avatar
Luboš Luňák committed
271
272
    // and repareting to root an atomic operation (http://lists.kde.org/?l=kde-devel&m=116448102901184&w=2)
    grabXServer();
273
274
    exportMappingState(WithdrawnState);
    setModal(false);   // Otherwise its mainwindow wouldn't get focus
275
    hidden = true; // So that it's not considered visible anymore (can't use hideClient(), it would set flags)
276
277
278
    if (!on_shutdown)
        workspace()->clientHidden(this);
    XUnmapWindow(display(), frameId());  // Destroying decoration would cause ugly visual effect
279
280
    destroyDecoration();
    cleanGrouping();
281
282
    if (!on_shutdown) {
        workspace()->removeClient(this, Allowed);
283
        // Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7)
284
        info->setDesktop(0);
285
        desk = 0;
286
        info->setState(0, info->state());  // Reset all state flags
Thomas Lübking's avatar
Thomas Lübking committed
287
288
    } else
        untab();
289
290
291
292
293
294
295
    XDeleteProperty(display(), client, atoms->kde_net_wm_user_creation_time);
    XDeleteProperty(display(), client, atoms->net_frame_extents);
    XDeleteProperty(display(), client, atoms->kde_net_wm_frame_strut);
    XReparentWindow(display(), client, rootWindow(), x(), y());
    XRemoveFromSaveSet(display(), client);
    XSelectInput(display(), client, NoEventMask);
    if (on_shutdown)
296
        // Map the window, so it can be found after another WM is started
297
298
        XMapWindow(display(), client);
    // TODO: Preserve minimized, shaded etc. state?
299
    else // Make sure it's not mapped if the app unmapped it (#65279). The app
300
301
        // may do map+unmap before we initially map the window by calling rawShow() from manage().
        XUnmapWindow(display(), client);
302
    client = None;
303
    XDestroyWindow(display(), wrapper);
304
    wrapper = None;
305
    XDestroyWindow(display(), frameId());
306
307
    //frame = None;
    --block_geometry_updates; // Don't use GeometryUpdatesBlocker, it would now set the geometry
308
309
310
311
    if (!on_shutdown) {
        disownDataPassedToDeleted();
        del->unrefWindow();
    }
Luboš Luňák's avatar
Luboš Luňák committed
312
    checkNonExistentClients();
313
    deleteClient(this, Allowed);
Luboš Luňák's avatar
Luboš Luňák committed
314
    ungrabXServer();
315
}
316

317
318
319
320
/**
 * Like releaseWindow(), but this one is called when the window has been already destroyed
 * (E.g. The application closed it)
 */
321
void Client::destroyClient()
322
323
{
    assert(!deleting);
324
    deleting = true;
325
    Deleted* del = Deleted::create(this);
326
327
    if (moveResizeMode)
        emit clientFinishUserMovedResized(this);
328
    emit windowClosed(this, del);
329
    finishCompositing();
330
331
    workspace()->discardUsedWindowRules(this, true);   // Remove ForceTemporarily rules
    StackingUpdatesBlocker blocker(workspace());
332
    if (moveResizeMode)
333
        leaveMoveResize();
334
335
    finishWindowRules();
    ++block_geometry_updates;
336
337
338
    if (isOnCurrentDesktop() && isShown(true))
        addWorkspaceRepaint(visibleRect());
    setModal(false);
339
    hidden = true; // So that it's not considered visible anymore
340
    workspace()->clientHidden(this);
341
342
    destroyDecoration();
    cleanGrouping();
343
    workspace()->removeClient(this, Allowed);
344
    client = None; // invalidate
345
    XDestroyWindow(display(), wrapper);
346
    wrapper = None;
347
    XDestroyWindow(display(), frameId());
348
349
    //frame = None;
    --block_geometry_updates; // Don't use GeometryUpdatesBlocker, it would now set the geometry
350
351
    disownDataPassedToDeleted();
    del->unrefWindow();
Luboš Luňák's avatar
Luboš Luňák committed
352
    checkNonExistentClients();
353
354
    deleteClient(this, Allowed);
}
355

356
357
358
359
void Client::updateInputWindow()
{
    QRegion region;

360
    if (!noBorder()) {
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
        // This function is implemented as a slot to avoid breaking binary
        // compatibility
        QMetaObject::invokeMethod(decoration, "region", Qt::DirectConnection,
                Q_RETURN_ARG(QRegion, region),
                Q_ARG(KDecorationDefines::Region, KDecorationDefines::ExtendedBorderRegion));
    }

    if (region.isEmpty()) {
        if (input_window) {
            XDestroyWindow(display(), input_window);
            input_window = None;
        }
        return;
    }

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

    // Move the bounding rect to screen coordinates
    bounds.translate(geometry().topLeft());

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

    if (!input_window) {
        XSetWindowAttributes attr;
        attr.event_mask = EnterWindowMask | LeaveWindowMask |
                          ButtonPressMask | ButtonReleaseMask | PointerMotionMask;
        attr.override_redirect = True;

        input_window = XCreateWindow(display(), rootWindow(), bounds.x(), bounds.y(),
                                     bounds.width(), bounds.height(), 0, 0,
                                     InputOnly, 0, CWEventMask | CWOverrideRedirect, &attr);
394
395
        if (mapping_state == Mapped)
            XMapWindow(display(), inputId());
396
397
398
399
400
401
402
403
    } else {
        XMoveResizeWindow(display(), input_window, bounds.x(), bounds.y(),
                          bounds.width(), bounds.height());
    }

    XShapeCombineRegion(display(), input_window, ShapeInput, 0, 0, region.handle(), ShapeSet);
}

404
405
406
407
void Client::updateDecoration(bool check_workspace_pos, bool force)
{
    if (!force &&
            ((decoration == NULL && noBorder()) || (decoration != NULL && !noBorder())))
408
409
        return;
    QRect oldgeom = geometry();
410
411
    blockGeometryUpdates(true);
    if (force)
412
        destroyDecoration();
413
414
415
    if (!noBorder()) {
        setMask(QRegion());  // Reset shape mask
        decoration = workspace()->createDecoration(bridge);
Cedric Bellegarde's avatar
Cedric Bellegarde committed
416
417
418
419
420
421
#ifdef KWIN_BUILD_KAPPMENU
        connect(this, SIGNAL(showRequest()), decoration, SIGNAL(showRequest()));
        connect(this, SIGNAL(appMenuAvailable()), decoration, SIGNAL(appMenuAvailable()));
        connect(this, SIGNAL(appMenuUnavailable()), decoration, SIGNAL(appMenuUnavailable()));
        connect(this, SIGNAL(menuHidden()), decoration, SIGNAL(menuHidden()));
#endif
422
        // TODO: Check decoration's minimum size?
423
        decoration->init();
424
425
        decoration->widget()->installEventFilter(this);
        XReparentWindow(display(), decoration->widget()->winId(), frameId(), 0, 0);
426
        decoration->widget()->lower();
427
        decoration->borders(border_left, border_right, border_top, border_bottom);
428
429
        padding_left = padding_right = padding_top = padding_bottom = 0;
        if (KDecorationUnstable *deco2 = dynamic_cast<KDecorationUnstable*>(decoration))
430
431
432
433
            deco2->padding(padding_left, padding_right, padding_top, padding_bottom);
        XMoveWindow(display(), decoration->widget()->winId(), -padding_left, -padding_top);
        move(calculateGravitation(false));
        plainResize(sizeForClientSize(clientSize()), ForceGeometrySet);
434
435
        if (Compositor::compositing()) {
            paintRedirector = new PaintRedirector(this, decoration->widget());
436
            discardWindowPixmap();
437
        }
438
        emit geometryShapeChanged(this, oldgeom);
439
    } else
440
        destroyDecoration();
441
    if (check_workspace_pos)
442
        checkWorkspacePosition(oldgeom);
443
    updateInputWindow();
444
445
    blockGeometryUpdates(false);
    if (!noBorder())
446
447
        decoration->widget()->show();
    updateFrameExtents();
448
}
449
450

void Client::destroyDecoration()
451
{
452
    QRect oldgeom = geometry();
453
    if (decoration != NULL) {
454
455
        delete decoration;
        decoration = NULL;
456
        paintRedirector = NULL;
457
        QPoint grav = calculateGravitation(true);
458
        border_left = border_right = border_top = border_bottom = 0;
459
460
461
462
        setMask(QRegion());  // Reset shape mask
        plainResize(sizeForClientSize(clientSize()), ForceGeometrySet);
        move(grav);
        if (compositing())
463
            discardWindowPixmap();
464
        if (!deleting) {
465
            emit geometryShapeChanged(this, oldgeom);
466
        }
467
    }
468
469
470
471
    if (inputId()) {
        XDestroyWindow(display(), input_window);
        input_window = None;
    }
472
}
473

474
475
476
bool Client::checkBorderSizes(bool also_resize)
{
    if (decoration == NULL)
477
        return false;
478
479
480

    int new_left = 0, new_right = 0, new_top = 0, new_bottom = 0;
    if (KDecorationUnstable *deco2 = dynamic_cast<KDecorationUnstable*>(decoration))
481
        deco2->padding(new_left, new_right, new_top, new_bottom);
482
    if (padding_left != new_left || padding_top != new_top)
483
        XMoveWindow(display(), decoration->widget()->winId(), -new_left, -new_top);
484
485
486
487
    padding_left = new_left;
    padding_right = new_right;
    padding_top = new_top;
    padding_bottom = new_bottom;
488
489
490
    decoration->borders(new_left, new_right, new_top, new_bottom);
    if (new_left == border_left && new_right == border_right &&
            new_top == border_top && new_bottom == border_bottom)
491
        return false;
492
    if (!also_resize) {
493
494
495
496
497
        border_left = new_left;
        border_right = new_right;
        border_top = new_top;
        border_bottom = new_bottom;
        return true;
498
499
500
    }
    GeometryUpdatesBlocker blocker(this);
    move(calculateGravitation(true));
501
502
503
504
    border_left = new_left;
    border_right = new_right;
    border_top = new_top;
    border_bottom = new_bottom;
505
    move(calculateGravitation(false));
506
    QRect oldgeom = geometry();
507
    plainResize(sizeForClientSize(clientSize()), ForceGeometrySet);
508
    checkWorkspacePosition(oldgeom);
509
    return true;
510
}
511

512
void Client::triggerDecorationRepaint()
513
514
{
    if (decoration != NULL)
515
        decoration->widget()->update();
516
}
517

518
void Client::layoutDecorationRects(QRect &left, QRect &top, QRect &right, QRect &bottom, Client::CoordinateMode mode) const
519
{
520
521
522
523
    QRect r = decoration->widget()->rect();
    if (mode == WindowRelative)
        r.translate(-padding_left, -padding_top);

524
    NETStrut strut = info->frameOverlap();
525
526

    // Ignore the overlap strut when compositing is disabled
527
    if (!compositing() || !Workspace::self()->decorationSupportsFrameOverlap())
528
        strut.left = strut.top = strut.right = strut.bottom = 0;
529
    else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1) {
530
531
532
533
534
        top = QRect(r.x(), r.y(), r.width(), r.height() / 3);
        left = QRect(r.x(), r.y() + top.height(), width() / 2, r.height() / 3);
        right = QRect(r.x() + left.width(), r.y() + top.height(), r.width() - left.width(), left.height());
        bottom = QRect(r.x(), r.y() + top.height() + left.height(), r.width(), r.height() - left.height() - top.height());
        return;
535
    }
536
537
538
539

    top = QRect(r.x(), r.y(), r.width(), padding_top + border_top + strut.top);
    bottom = QRect(r.x(), r.y() + r.height() - padding_bottom - border_bottom - strut.bottom,
                   r.width(), padding_bottom + border_bottom + strut.bottom);
540
    left = QRect(r.x(), r.y() + top.height(),
541
542
543
                 padding_left + border_left + strut.left, r.height() - top.height() - bottom.height());
    right = QRect(r.x() + r.width() - padding_right - border_right - strut.right, r.y() + top.height(),
                  padding_right + border_right + strut.right, r.height() - top.height() - bottom.height());
544
}
545

546
QRegion Client::decorationPendingRegion() const
547
{
548
549
    if (!paintRedirector)
        return QRegion();
550
551
    return paintRedirector->scheduledRepaintRegion().translated(x() - padding_left, y() - padding_top);
}
552

553
QRect Client::transparentRect() const
554
{
555
556
557
    if (isShade())
        return QRect();

558
    NETStrut strut = info->frameOverlap();
559
560
    // Ignore the strut when compositing is disabled or the decoration doesn't support it
    if (!compositing() || !Workspace::self()->decorationSupportsFrameOverlap())
561
562
        strut.left = strut.top = strut.right = strut.bottom = 0;
    else if (strut.left == -1 && strut.top == -1 && strut.right == -1 && strut.bottom == -1)
563
564
565
        return QRect();

    const QRect r = QRect(clientPos(), clientSize())
566
                    .adjusted(strut.left, strut.top, -strut.right, -strut.bottom);
567
568
569
570
    if (r.isValid())
        return r;

    return QRect();
571
}
572

573
void Client::detectNoBorder()
574
575
{
    if (shape()) {
576
        noborder = true;
577
        app_noborder = true;
578
        return;
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
    }
    switch(windowType()) {
    case NET::Desktop :
    case NET::Dock :
    case NET::TopMenu :
    case NET::Splash :
        noborder = true;
        app_noborder = true;
        break;
    case NET::Unknown :
    case NET::Normal :
    case NET::Toolbar :
    case NET::Menu :
    case NET::Dialog :
    case NET::Utility :
        noborder = false;
        break;
    default:
        abort();
    }
599
600
601
    // NET::Override is some strange beast without clear definition, usually
    // just meaning "noborder", so let's treat it only as such flag, and ignore it as
    // a window type otherwise (SUPPORTED_WINDOW_TYPES_MASK doesn't include it)
602
    if (info->windowType(SUPPORTED_MANAGED_WINDOW_TYPES_MASK | NET::OverrideMask) == NET::Override) {
603
        noborder = true;
604
        app_noborder = true;
605
    }
606
}
607
608

void Client::updateFrameExtents()
609
{
610
611
612
613
614
    NETStrut strut;
    strut.left = border_left;
    strut.right = border_right;
    strut.top = border_top;
    strut.bottom = border_bottom;
615
616
    info->setFrameExtents(strut);
}
617

618
619
620
621
622
623
624
/**
 * Resizes the decoration, and makes sure the decoration widget gets resize event
 * even if the size hasn't changed. This is needed to make sure the decoration
 * re-layouts (e.g. when options()->moveResizeMaximizedWindows() changes,
 * the decoration may turn on/off some borders, but the actual size
 * of the decoration stays the same).
 */
625
626
627
void Client::resizeDecoration(const QSize& s)
{
    if (decoration == NULL)
628
        return;
629
630
    QSize newSize = s + QSize(padding_left + padding_right, padding_top + padding_bottom);
    QSize oldSize = decoration->widget()->size();
631
632
633
634
    decoration->resize(newSize);
    if (oldSize == newSize) {
        QResizeEvent e(newSize, oldSize);
        QApplication::sendEvent(decoration->widget(), &e);
635
636
637
638
    } else if (paintRedirector) { // oldSize != newSize
        paintRedirector->resizePixmaps();
    } else {
        triggerDecorationRepaint();
639
    }
640
    updateInputWindow();
641
}
642
643

bool Client::noBorder() const
644
{
645
    return !workspace()->hasDecorationPlugin() || noborder || isFullScreen();
646
}
647
648

bool Client::userCanSetNoBorder() const
649
{
Thomas Lübking's avatar
Thomas Lübking committed
650
    return !isFullScreen() && !isShade() && !tabGroup();
651
}
652

653
654
655
void Client::setNoBorder(bool set)
{
    if (!userCanSetNoBorder())
656
        return;
657
658
    set = rules()->checkNoBorder(set);
    if (noborder == set)
659
        return;
660
    noborder = set;
661
    updateDecoration(true, false);
Thomas Lübking's avatar
Thomas Lübking committed
662
    updateWindowRules(Rules::NoBorder);
663
}
664

665
void Client::checkNoBorder()
666
667
668
{
    setNoBorder(app_noborder);
}
669

670
void Client::updateShape()
671
672
673
674
675
{
    if (shape()) {
        // Workaround for #19644 - Shaped windows shouldn't have decoration
        if (!app_noborder) {
            // Only when shape is detected for the first time, still let the user to override
676
            app_noborder = true;
677
            noborder = rules()->checkNoBorder(true);
678
            updateDecoration(true);
Luboš Luňák's avatar
Luboš Luňák committed
679
        }
680
681
        if (noBorder())
            XShapeCombineShape(display(), frameId(), ShapeBounding,
682
                           clientPos().x(), clientPos().y(), window(), ShapeBounding, ShapeSet);
683
684
685
    } else if (app_noborder) {
        XShapeCombineMask(display(), frameId(), ShapeBounding, 0, 0, None, ShapeSet);
        detectNoBorder();
686
        app_noborder = noborder = rules()->checkNoBorder(noborder);
687
688
        updateDecoration(true);
    }
689

690
691
    // Decoration mask (i.e. 'else' here) setting is done in setMask()
    // when the decoration calls it or when the decoration is created/destroyed
692
    updateInputShape();
693
    if (compositing()) {
694
        addRepaintFull();
695
        addWorkspaceRepaint(visibleRect());   // In case shape change removes part of this window
696
    }
697
    emit geometryShapeChanged(this, geometry());
698
}
Luboš Luňák's avatar
Luboš Luňák committed
699

700
701
static Window shape_helper_window = None;

702
void Client::updateInputShape()
703
704
{
    if (hiddenPreview())   // Sets it to none, don't change
705
        return;
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
    if (Extensions::shapeInputAvailable()) {
        // There appears to be no way to find out if a window has input
        // shape set or not, so always propagate the input shape
        // (it's the same like the bounding shape by default).
        // Also, build the shape using a helper window, not directly
        // in the frame window, because the sequence set-shape-to-frame,
        // remove-shape-of-client, add-input-shape-of-client has the problem
        // that after the second step there's a hole in the input shape
        // until the real shape of the client is added and that can make
        // the window lose focus (which is a problem with mouse focus policies)
        // TODO: It seems there is, after all - XShapeGetRectangles() - but maybe this is better
        if (shape_helper_window == None)
            shape_helper_window = XCreateSimpleWindow(display(), rootWindow(),
                                  0, 0, 1, 1, 0, 0, 0);
        XResizeWindow(display(), shape_helper_window, width(), height());
        XShapeCombineShape(display(), shape_helper_window, ShapeInput, 0, 0,
                           frameId(), ShapeBounding, ShapeSet);
        XShapeCombineShape(display(), shape_helper_window, ShapeInput,
                           clientPos().x(), clientPos().y(), window(), ShapeBounding, ShapeSubtract);
        XShapeCombineShape(display(), shape_helper_window, ShapeInput,
                           clientPos().x(), clientPos().y(), window(), ShapeInput, ShapeUnion);
        XShapeCombineShape(display(), frameId(), ShapeInput, 0, 0,
                           shape_helper_window, ShapeInput, ShapeSet);
729
    }
730
}
731

732
733
734
735
void Client::setMask(const QRegion& reg, int mode)
{
    QRegion r = reg.translated(-padding_left, -padding_right) & QRect(0, 0, width(), height());
    if (_mask == r)
736
        return;
737
    _mask = r;
738
    Window shape_window = frameId();
739
740
741
742
743
    if (shape()) {
        // The same way of applying a shape without strange intermediate states like above
        if (shape_helper_window == None)
            shape_helper_window = XCreateSimpleWindow(display(), rootWindow(),
                                  0, 0, 1, 1, 0, 0, 0);
744
        shape_window = shape_helper_window;
745
746
747
748
749
750
    }
    if (_mask.isEmpty())
        XShapeCombineMask(display(), shape_window, ShapeBounding, 0, 0, None, ShapeSet);
    else if (mode == X::Unsorted)
        XShapeCombineRegion(display(), shape_window, ShapeBounding, 0, 0, _mask.handle(), ShapeSet);
    else {
751
        QVector< QRect > rects = _mask.rects();
752
        XRectangle* xrects = new XRectangle[rects.count()];
753
        for (int i = 0; i < rects.count(); ++i) {
754
755
756
757
            xrects[i].x = rects[i].x();
            xrects[i].y = rects[i].y();
            xrects[i].width = rects[i].width();
            xrects[i].height = rects[i].height();
758
        }
759
760
761
762
763
764
        XShapeCombineRectangles(display(), shape_window, ShapeBounding, 0, 0,
                                xrects, rects.count(), ShapeSet, mode);
        delete[] xrects;
    }
    if (shape()) {
        // The rest of the applyign using a temporary window
765
766
        XRectangle rec = { 0, 0, static_cast<unsigned short>(clientSize().width()),
                           static_cast<unsigned short>(clientSize().height()) };
767
768
769
770
771
772
773
        XShapeCombineRectangles(display(), shape_helper_window, ShapeBounding,
                                clientPos().x(), clientPos().y(), &rec, 1, ShapeSubtract, Unsorted);
        XShapeCombineShape(display(), shape_helper_window, ShapeBounding,
                           clientPos().x(), clientPos().y(), window(), ShapeBounding, ShapeUnion);
        XShapeCombineShape(display(), frameId(), ShapeBounding, 0, 0,
                           shape_helper_window, ShapeBounding, ShapeSet);
    }
774
    emit geometryShapeChanged(this, geometry());
Luboš Luňák's avatar
Luboš Luňák committed
775
    updateShape();
776
}
777
778

QRegion Client::mask() const
779
780
781
{
    if (_mask.isEmpty())
        return QRegion(0, 0, width(), height());
782
    return _mask;
783
}
784

785
786
787
void Client::hideClient(bool hide)
{
    if (hidden == hide)
788
789
790
        return;
    hidden = hide;
    updateVisibility();
791
}
792
793
794

/**
 * Returns whether the window is minimizable or not
795
796
 */
bool Client::isMinimizable() const
797
798
{
    if (isSpecialWindow())
799
        return false;
800
801
802
    if (!rules()->checkMinimize(true))
        return false;

803
804
    if (isTransient()) {
        // #66868 - Let other xmms windows be minimized when the mainwindow is minimized
805
806
        bool shown_mainwindow = false;
        ClientList mainclients = mainClients();
807
808
809
810
        for (ClientList::ConstIterator it = mainclients.constBegin();
                it != mainclients.constEnd();
                ++it)
            if ((*it)->isShown(true))
811
                shown_mainwindow = true;
812
        if (!shown_mainwindow)
813
            return true;
814
    }
815
#if 0
816
    // This is here because kicker's taskbar doesn't provide separate entries
817
    // for windows with an explicitly given parent
818
    // TODO: perhaps this should be redone
819
820
    // Disabled for now, since at least modal dialogs should be minimizable
    // (resulting in the mainwindow being minimized too).
821
    if (transientFor() != NULL)
822
        return false;
823
#endif
824
    if (!wantsTabFocus())   // SELI, TODO: - NET::Utility? why wantsTabFocus() - skiptaskbar? ?
825
826
        return false;
    return true;
827
}
828

Martin Flöser's avatar
Martin Flöser committed
829
830
831
832
833
void Client::setMinimized(bool set)
{
    set ? minimize() : unminimize();
}

834
835
/**
 * Minimizes this client plus its transients
836
 */
837
838
839
void Client::minimize(bool avoid_animation)
{
    if (!isMinimizable() || isMinimized())
840
        return;
841

842
843
844
    if (isShade()) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded
        info->setState(0, NET::Shaded);

845
    Notify::raise(Notify::Minimize);
846
847
848
849
850

    minimized = true;

    updateVisibility();
    updateAllowedActions();
851
    workspace()->updateMinimizedOfTransients(this);
Thomas Lübking's avatar
Thomas Lübking committed
852
    updateWindowRules(Rules::Minimize);
853
    workspace()->updateFocusChains(this, Workspace::FocusChainMakeLast);
854
855
    // TODO: merge signal with s_minimized
    emit clientMinimized(this, !avoid_animation);
856
857

    // Update states of all other windows in this group
Thomas Lübking's avatar
Thomas Lübking committed
858
    if (tabGroup())
859
        tabGroup()->updateStates(this, TabGroup::Minimized);
Martin Flöser's avatar
Martin Flöser committed
860
    emit minimizedChanged();
861
}
862

863
864
865
void Client::unminimize(bool avoid_animation)
{
    if (!isMinimized())
866
        return;
867

Martin Flöser's avatar
Martin Flöser committed
868
869
870
871
    if (rules()->checkMinimize(false)) {
        return;
    }

872
873
874
    if (isShade()) // NETWM restriction - KWindowInfo::isMinimized() == Hidden && !Shaded
        info->setState(NET::Shaded, NET::Shaded);

875
    Notify::raise(Notify::UnMinimize);
876
877
878
    minimized = false;
    updateVisibility();
    updateAllowedActions();
879
    workspace()->updateMinimizedOfTransients(this);
Thomas Lübking's avatar
Thomas Lübking committed
880
    updateWindowRules(Rules::Minimize);
881
    emit clientUnminimized(this, !avoid_animation);
882
883

    // Update states of all other windows in this group
Thomas Lübking's avatar
Thomas Lübking committed
884
    if (tabGroup())
885
        tabGroup()->updateStates(this, TabGroup::Minimized);
Martin Flöser's avatar
Martin Flöser committed
886
    emit minimizedChanged();
887
}
888
889

QRect Client::iconGeometry() const
890
{
891
    NETRect r = info->iconGeometry();
892
893
    QRect geom(r.pos.x, r.pos.y, r.size.width, r.size.height);
    if (geom.isValid())
894
        return geom;
895
896
897
    else {
        // Check all mainwindows of this window (recursively)
        foreach (Client * mainwin, mainClients()) {
898
            geom = mainwin->iconGeometry();
899
            if (geom.isValid())
900
                return geom;
901
        }
902
903
904
        // No mainwindow (or their parents) with icon geometry was found
        return QRect();
    }
905
}
906
907

bool Client::isShadeable() const
908
{
909
    return !isSpecialWindow() && !noBorder() && (rules()->checkShade(ShadeNormal) != rules()->checkShade(ShadeNone));
910
}
911

Martin Flöser's avatar
Martin Flöser committed
912
913
914
915
void Client::setShade(bool set) {
    set ? setShade(ShadeNormal) : setShade(ShadeNone);
}

916
917
void Client::setShade(ShadeMode mode)
{
918
919
    if (mode == ShadeHover && isMove())
        return; // causes geometry breaks and is probably nasty
920
921
    if (isSpecialWindow() || noBorder())
        mode = ShadeNone;
922
923
    mode = rules()->checkShade(mode);
    if (shade_mode == mode)
924
925
926
927
        return;
    bool was_shade = isShade();
    ShadeMode was_shade_mode = shade_mode;
    shade_mode = mode;
928

929
930