useractions.cpp 55.5 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
27
28
29

/*

 This file contains things relevant to direct user actions, such as
 responses to global keyboard shortcuts, or selecting actions
 from the window operations menu.

*/

30
///////////////////////////////////////////////////////////////////////////////
31
32
33
// NOTE: if you change the menu, keep
//       plasma-desktop/applets/taskmanager/package/contents/ui/ContextMenu.qml
//       in sync
34
35
//////////////////////////////////////////////////////////////////////////////

36
#include "useractions.h"
37
#include "cursor.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
38
#include "x11client.h"
39
#include "colorcorrection/manager.h"
40
#include "composite.h"
41
#include "input.h"
42
43
#include "workspace.h"
#include "effects.h"
44
#include "platform.h"
45
#include "screens.h"
46
#include "xdgshellclient.h"
47
#include "virtualdesktops.h"
48
49
#include "scripting/scripting.h"

50
#ifdef KWIN_BUILD_ACTIVITIES
51
#include "activities.h"
52
#include <kactivities/info.h>
53
#endif
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
54
#include "appmenu.h"
55

56
#include <KProcess>
57

58
#include <QAction>
59
#include <QCheckBox>
60
#include <QtConcurrentRun>
61
#include <QPointer>
62
63
#include <QPushButton>

64
65
#include <KGlobalAccel>
#include <KLocalizedString>
66
67
68
#include <kconfig.h>
#include <QRegExp>
#include <QMenu>
69
#include <QWidgetAction>
70
71
72
#include <kauthorized.h>

#include "killwindow.h"
73
#ifdef KWIN_BUILD_TABBOX
74
#include "tabbox.h"
75
#endif
76
77
78
79

namespace KWin
{

80
81
UserActionsMenu::UserActionsMenu(QObject *parent)
    : QObject(parent)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
    , m_menu(nullptr)
    , m_desktopMenu(nullptr)
    , m_screenMenu(nullptr)
    , m_activityMenu(nullptr)
    , m_scriptsMenu(nullptr)
    , m_resizeOperation(nullptr)
    , m_moveOperation(nullptr)
    , m_maximizeOperation(nullptr)
    , m_shadeOperation(nullptr)
    , m_keepAboveOperation(nullptr)
    , m_keepBelowOperation(nullptr)
    , m_fullScreenOperation(nullptr)
    , m_noBorderOperation(nullptr)
    , m_minimizeOperation(nullptr)
    , m_closeOperation(nullptr)
97
98
{
}
99

100
UserActionsMenu::~UserActionsMenu()
101
{
102
103
    discard();
}
104

105
106
107
108
bool UserActionsMenu::isShown() const
{
    return m_menu && m_menu->isVisible();
}
109

110
111
bool UserActionsMenu::hasClient() const
{
Frederik Gladhorn's avatar
Frederik Gladhorn committed
112
    return m_client && isShown();
113
}
114

115
116
117
118
119
120
void UserActionsMenu::close()
{
    if (!m_menu) {
        return;
    }
    m_menu->close();
121
    m_client.clear();
122
}
123

124
bool UserActionsMenu::isMenuClient(const AbstractClient *c) const
125
{
Frederik Gladhorn's avatar
Frederik Gladhorn committed
126
    return c && c == m_client;
127
}
128

129
void UserActionsMenu::show(const QRect &pos, AbstractClient *client)
130
{
Frederik Gladhorn's avatar
Frederik Gladhorn committed
131
    Q_ASSERT(client);
132
    QPointer<AbstractClient> cl(client);
Frederik Gladhorn's avatar
Frederik Gladhorn committed
133
134
135
    // Presumably client will never be nullptr,
    // but play it safe and make sure not to crash.
    if (cl.isNull()) {
136
        return;
Frederik Gladhorn's avatar
Frederik Gladhorn committed
137
138
    }
    if (isShown()) {  // recursion
139
        return;
Frederik Gladhorn's avatar
Frederik Gladhorn committed
140
141
    }
    if (cl->isDesktop() || cl->isDock()) {
142
        return;
Frederik Gladhorn's avatar
Frederik Gladhorn committed
143
144
    }
    if (!KAuthorized::authorizeAction(QStringLiteral("kwin_rmb"))) {
145
        return;
Frederik Gladhorn's avatar
Frederik Gladhorn committed
146
    }
147
148
    m_client = cl;
    init();
149
    m_client->blockActivityUpdates(true);
150
    if (kwinApp()->shouldUseWaylandForCompositing()) {
151
        m_menu->popup(pos.bottomLeft());
152
    } else {
153
        m_menu->exec(pos.bottomLeft());
154
    }
155
156
    if (m_client) {
        m_client->blockActivityUpdates(false);
157
158
    }
}
159

160
161
162
163
164
165
void UserActionsMenu::grabInput()
{
    m_menu->windowHandle()->setMouseGrabEnabled(true);
    m_menu->windowHandle()->setKeyboardGrabEnabled(true);
}

166
void UserActionsMenu::helperDialog(const QString& message, AbstractClient* client)
167
168
169
{
    QStringList args;
    QString type;
170
    auto shortcut = [](const QString &name) {
171
        QAction* action = Workspace::self()->findChild<QAction*>(name);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
172
        Q_ASSERT(action != nullptr);
173
174
175
176
177
        const auto shortcuts = KGlobalAccel::self()->shortcut(action);
        return QStringLiteral("%1 (%2)").arg(action->text())
                             .arg(shortcuts.isEmpty() ? QString() : shortcuts.first().toString(QKeySequence::NativeText));
    };
    if (message == QStringLiteral("noborderaltf3")) {
178
        args << QStringLiteral("--msgbox") << i18n(
179
180
181
182
                 "You have selected to show a window without its border.\n"
                 "Without the border, you will not be able to enable the border "
                 "again using the mouse: use the window operations menu instead, "
                 "activated using the %1 keyboard shortcut.",
183
                 shortcut(QStringLiteral("Window Operations Menu")));
184
        type = QStringLiteral("altf3warning");
Nick Shaforostoff's avatar
Nick Shaforostoff committed
185
    } else if (message == QLatin1String("fullscreenaltf3")) {
186
        args << QStringLiteral("--msgbox") << i18n(
187
188
189
190
191
                 "You have selected to show a window in fullscreen mode.\n"
                 "If the application itself does not have an option to turn the fullscreen "
                 "mode off you will not be able to disable it "
                 "again using the mouse: use the window operations menu instead, "
                 "activated using the %1 keyboard shortcut.",
192
                 shortcut(QStringLiteral("Window Operations Menu")));
193
        type = QStringLiteral("altf3warning");
194
195
196
    } else
        abort();
    if (!type.isEmpty()) {
197
        KConfig cfg(QStringLiteral("kwin_dialogsrc"));
198
199
200
        KConfigGroup cg(&cfg, "Notification Messages");  // Depends on KMessageBox
        if (!cg.readEntry(type, true))
            return;
Nick Shaforostoff's avatar
Nick Shaforostoff committed
201
        args << QStringLiteral("--dontagain") << QLatin1String("kwin_dialogsrc:") + type;
202
    }
203
204
    if (client)
        args << QStringLiteral("--embed") << QString::number(client->windowId());
205
206
207
    QtConcurrent::run([args]() {
        KProcess::startDetached(QStringLiteral("kdialog"), args);
    });
208
}
209

210

211
212
213
QStringList configModules(bool controlCenter)
{
    QStringList args;
214
    args <<  QStringLiteral("kwindecoration");
215
    if (controlCenter)
216
217
218
        args << QStringLiteral("kwinoptions");
    else if (KAuthorized::authorizeControlModule(QStringLiteral("kde-kwinoptions.desktop")))
        args << QStringLiteral("kwinactions") << QStringLiteral("kwinfocus") <<  QStringLiteral("kwinmoving") << QStringLiteral("kwinadvanced")
219
             << QStringLiteral("kwinrules") << QStringLiteral("kwincompositing") << QStringLiteral("kwineffects")
220
#ifdef KWIN_BUILD_TABBOX
221
             << QStringLiteral("kwintabbox")
222
#endif
223
224
             << QStringLiteral("kwinscreenedges")
             << QStringLiteral("kwinscripts")
225
226
227
             ;
    return args;
}
228

229
230
231
232
233
234
void UserActionsMenu::init()
{
    if (m_menu) {
        return;
    }
    m_menu = new QMenu;
235
236
    connect(m_menu, &QMenu::aboutToShow, this, &UserActionsMenu::menuAboutToShow);
    connect(m_menu, &QMenu::triggered, this, &UserActionsMenu::slotWindowOperation, Qt::QueuedConnection);
237
238

    QMenu *advancedMenu = new QMenu(m_menu);
239
    connect(advancedMenu, &QMenu::aboutToShow, [this, advancedMenu]() {
Frederik Gladhorn's avatar
Frederik Gladhorn committed
240
241
242
        if (m_client) {
            advancedMenu->setPalette(m_client->palette());
        }
243
    });
244

245
    auto setShortcut = [](QAction *action, const QString &actionName) {
246
        const auto shortcuts = KGlobalAccel::self()->shortcut(Workspace::self()->findChild<QAction*>(actionName));
247
248
249
250
251
        if (!shortcuts.isEmpty()) {
            action->setShortcut(shortcuts.first());
        }
    };

252
    m_moveOperation = advancedMenu->addAction(i18n("&Move"));
253
    m_moveOperation->setIcon(QIcon::fromTheme(QStringLiteral("transform-move")));
254
    setShortcut(m_moveOperation, QStringLiteral("Window Move"));
255
256
    m_moveOperation->setData(Options::UnrestrictedMoveOp);

257
    m_resizeOperation = advancedMenu->addAction(i18n("&Resize"));
258
    m_resizeOperation->setIcon(QIcon::fromTheme(QStringLiteral("transform-scale")));
259
    setShortcut(m_resizeOperation, QStringLiteral("Window Resize"));
260
261
262
    m_resizeOperation->setData(Options::ResizeOp);

    m_keepAboveOperation = advancedMenu->addAction(i18n("Keep &Above Others"));
263
    m_keepAboveOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-keep-above")));
264
    setShortcut(m_keepAboveOperation, QStringLiteral("Window Above Other Windows"));
265
266
267
268
    m_keepAboveOperation->setCheckable(true);
    m_keepAboveOperation->setData(Options::KeepAboveOp);

    m_keepBelowOperation = advancedMenu->addAction(i18n("Keep &Below Others"));
269
    m_keepBelowOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-keep-below")));
270
    setShortcut(m_keepBelowOperation, QStringLiteral("Window Below Other Windows"));
271
272
273
274
    m_keepBelowOperation->setCheckable(true);
    m_keepBelowOperation->setData(Options::KeepBelowOp);

    m_fullScreenOperation = advancedMenu->addAction(i18n("&Fullscreen"));
275
    m_fullScreenOperation->setIcon(QIcon::fromTheme(QStringLiteral("view-fullscreen")));
276
    setShortcut(m_fullScreenOperation, QStringLiteral("Window Fullscreen"));
277
278
279
    m_fullScreenOperation->setCheckable(true);
    m_fullScreenOperation->setData(Options::FullScreenOp);

280
    m_shadeOperation = advancedMenu->addAction(i18n("&Shade"));
281
    m_shadeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-shade")));
282
    setShortcut(m_shadeOperation, QStringLiteral("Window Shade"));
283
284
285
286
    m_shadeOperation->setCheckable(true);
    m_shadeOperation->setData(Options::ShadeOp);

    m_noBorderOperation = advancedMenu->addAction(i18n("&No Border"));
287
    m_noBorderOperation->setIcon(QIcon::fromTheme(QStringLiteral("edit-none-border")));
288
    setShortcut(m_noBorderOperation, QStringLiteral("Window No Border"));
289
290
291
292
293
    m_noBorderOperation->setCheckable(true);
    m_noBorderOperation->setData(Options::NoBorderOp);

    advancedMenu->addSeparator();

294
    m_shortcutOperation = advancedMenu->addAction(i18n("Set Window Short&cut..."));
295
296
297
    m_shortcutOperation->setIcon(QIcon::fromTheme(QStringLiteral("configure-shortcuts")));
    setShortcut(m_shortcutOperation, QStringLiteral("Setup Window Shortcut"));
    m_shortcutOperation->setData(Options::SetupWindowShortcutOp);
298

299
    QAction *action = advancedMenu->addAction(i18n("Configure Special &Window Settings..."));
300
    action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-actions")));
301
    action->setData(Options::WindowRulesOp);
302
    m_rulesOperation = action;
303

304
    action = advancedMenu->addAction(i18n("Configure S&pecial Application Settings..."));
305
    action->setIcon(QIcon::fromTheme(QStringLiteral("preferences-system-windows-actions")));
306
    action->setData(Options::ApplicationRulesOp);
307
    m_applicationRulesOperation = action;
308
    if (!kwinApp()->config()->isImmutable() &&
309
310
311
            !KAuthorized::authorizeControlModules(configModules(true)).isEmpty()) {
        advancedMenu->addSeparator();
        action = advancedMenu->addAction(i18nc("Entry in context menu of window decoration to open the configuration module of KWin",
312
                                        "Configure W&indow Manager..."));
313
        action->setIcon(QIcon::fromTheme(QStringLiteral("configure")));
314
        connect(action, &QAction::triggered, this,
315
            [this]() {
316
317
                // opens the KWin configuration
                QStringList args;
318
319
320
321
322
323
324
                args << QStringLiteral("--icon") << QStringLiteral("preferences-system-windows");
                const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation,
                                                            QStringLiteral("kservices5/kwinfocus.desktop"));
                if (!path.isEmpty()) {
                    args << QStringLiteral("--desktopfile") << path;
                }
                args << configModules(false);
325
                QProcess *p = new Process(this);
326
327
328
                p->setArguments(args);
                p->setProcessEnvironment(kwinApp()->processStartupEnvironment());
                p->setProgram(QStringLiteral("kcmshell5"));
329
                connect(p, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), p, &QProcess::deleteLater);
330
331
332
                connect(p, &QProcess::errorOccurred, this, [p](QProcess::ProcessError e) {
                    if (e == QProcess::FailedToStart) {
                        qCDebug(KWIN_CORE) << "Failed to start kcmshell5";
333
                    }
334
                });
335
                p->start();
336
337
            }
        );
338
    }
339

340
    m_maximizeOperation = m_menu->addAction(i18n("Ma&ximize"));
341
    m_maximizeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-maximize")));
342
    setShortcut(m_maximizeOperation, QStringLiteral("Window Maximize"));
343
344
345
    m_maximizeOperation->setCheckable(true);
    m_maximizeOperation->setData(Options::MaximizeOp);

346
347
348
349
    m_minimizeOperation = m_menu->addAction(i18n("Mi&nimize"));
    m_minimizeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-minimize")));
    setShortcut(m_minimizeOperation, QStringLiteral("Window Minimize"));
    m_minimizeOperation->setData(Options::MinimizeOp);
350
351

    action = m_menu->addMenu(advancedMenu);
352
    action->setText(i18n("&More Actions"));
353
    action->setIcon(QIcon::fromTheme(QStringLiteral("view-more-symbolic")));
354

355
    m_closeOperation = m_menu->addAction(i18n("&Close"));
356
    m_closeOperation->setIcon(QIcon::fromTheme(QStringLiteral("window-close")));
357
    setShortcut(m_closeOperation, QStringLiteral("Window Close"));
358
    m_closeOperation->setData(Options::CloseOp);
359
360
}

361
void UserActionsMenu::discard()
362
{
363
    delete m_menu;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
364
365
    m_menu = nullptr;
    m_desktopMenu = nullptr;
366
    m_multipleDesktopsMenu = nullptr;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
367
368
369
    m_screenMenu = nullptr;
    m_activityMenu = nullptr;
    m_scriptsMenu = nullptr;
370
371
}

372
void UserActionsMenu::menuAboutToShow()
373
{
374
    if (m_client.isNull() || !m_menu)
375
376
        return;

377
    if (VirtualDesktopManager::self()->count() == 1) {
378
        delete m_desktopMenu;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
379
        m_desktopMenu = nullptr;
380
381
        delete m_multipleDesktopsMenu;
        m_multipleDesktopsMenu = nullptr;
382
    } else {
383
        initDesktopPopup();
384
    }
Frederik Gladhorn's avatar
Frederik Gladhorn committed
385
    if (screens()->count() == 1 || (!m_client->isMovable() && !m_client->isMovableAcrossScreens())) {
386
        delete m_screenMenu;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
387
        m_screenMenu = nullptr;
388
389
390
    } else {
        initScreenPopup();
    }
391

Frederik Gladhorn's avatar
Frederik Gladhorn committed
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
    m_menu->setPalette(m_client->palette());
    m_resizeOperation->setEnabled(m_client->isResizable());
    m_moveOperation->setEnabled(m_client->isMovableAcrossScreens());
    m_maximizeOperation->setEnabled(m_client->isMaximizable());
    m_maximizeOperation->setChecked(m_client->maximizeMode() == MaximizeFull);
    m_shadeOperation->setEnabled(m_client->isShadeable());
    m_shadeOperation->setChecked(m_client->shadeMode() != ShadeNone);
    m_keepAboveOperation->setChecked(m_client->keepAbove());
    m_keepBelowOperation->setChecked(m_client->keepBelow());
    m_fullScreenOperation->setEnabled(m_client->userCanSetFullScreen());
    m_fullScreenOperation->setChecked(m_client->isFullScreen());
    m_noBorderOperation->setEnabled(m_client->userCanSetNoBorder());
    m_noBorderOperation->setChecked(m_client->noBorder());
    m_minimizeOperation->setEnabled(m_client->isMinimizable());
    m_closeOperation->setEnabled(m_client->isCloseable());
    m_shortcutOperation->setEnabled(m_client->rules()->checkShortcut(QString()).isNull());
408

409
410
    // drop the existing scripts menu
    delete m_scriptsMenu;
411
    m_scriptsMenu = nullptr;
412
    // ask scripts whether they want to add entries for the given Client
413
    QList<QAction*> scriptActions = Scripting::self()->actionsForUserActionMenu(m_client.data(), m_scriptsMenu);
414
    if (!scriptActions.isEmpty()) {
415
        m_scriptsMenu = new QMenu(m_menu);
Frederik Gladhorn's avatar
Frederik Gladhorn committed
416
        m_scriptsMenu->setPalette(m_client->palette());
417
418
419
420
421
422
423
        m_scriptsMenu->addActions(scriptActions);

        QAction *action = m_scriptsMenu->menuAction();
        // set it as the first item after desktop
        m_menu->insertAction(m_closeOperation, action);
        action->setText(i18n("&Extensions"));
    }
424

Frederik Gladhorn's avatar
Frederik Gladhorn committed
425
426
    m_rulesOperation->setEnabled(m_client->supportsWindowRules());
    m_applicationRulesOperation->setEnabled(m_client->supportsWindowRules());
427

428
    showHideActivityMenu();
429
}
430

431
void UserActionsMenu::showHideActivityMenu()
432
433
{
#ifdef KWIN_BUILD_ACTIVITIES
434
435
436
    if (!Activities::self()) {
        return;
    }
437
    const QStringList &openActivities_ = Activities::self()->running();
438
    qCDebug(KWIN_CORE) << "activities:" << openActivities_.size();
439
    if (openActivities_.size() < 2) {
440
        delete m_activityMenu;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
441
        m_activityMenu = nullptr;
442
443
444
445
446
447
    } else {
        initActivityPopup();
    }
#endif
}

448
void UserActionsMenu::initDesktopPopup()
449
{
450
    if (kwinApp()->operationMode() == Application::OperationModeWaylandOnly ||
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
451
        kwinApp()->operationMode() == Application::OperationModeXwayland) {
452
453
454
        if (m_multipleDesktopsMenu) {
            return;
        }
455

456
457
458
        m_multipleDesktopsMenu = new QMenu(m_menu);
        connect(m_multipleDesktopsMenu, &QMenu::triggered, this,   &UserActionsMenu::slotToggleOnVirtualDesktop);
        connect(m_multipleDesktopsMenu, &QMenu::aboutToShow, this, &UserActionsMenu::multipleDesktopsPopupAboutToShow);
459

460
461
        QAction *action = m_multipleDesktopsMenu->menuAction();
        // set it as the first item
462
        m_menu->insertAction(m_maximizeOperation, action);
463
        action->setText(i18n("&Desktops"));
464
        action->setIcon(QIcon::fromTheme(QStringLiteral("virtual-desktops")));
465
466
467
468
469
470
471
472
473
474
475

    } else {
        if (m_desktopMenu)
            return;

        m_desktopMenu = new QMenu(m_menu);
        connect(m_desktopMenu, &QMenu::triggered,   this, &UserActionsMenu::slotSendToDesktop);
        connect(m_desktopMenu, &QMenu::aboutToShow, this, &UserActionsMenu::desktopPopupAboutToShow);

        QAction *action = m_desktopMenu->menuAction();
        // set it as the first item
476
477
478
        m_menu->insertAction(m_maximizeOperation, action);
        action->setText(i18n("Move to &Desktop"));
        action->setIcon(QIcon::fromTheme(QStringLiteral("virtual-desktops")));
479
    }
480
}
481

482
void UserActionsMenu::initScreenPopup()
483
{
484
    if (m_screenMenu) {
485
486
487
        return;
    }

488
    m_screenMenu = new QMenu(m_menu);
489
490
    connect(m_screenMenu, &QMenu::triggered,   this, &UserActionsMenu::slotSendToScreen);
    connect(m_screenMenu, &QMenu::aboutToShow, this, &UserActionsMenu::screenPopupAboutToShow);
491

492
    QAction *action = m_screenMenu->menuAction();
493
    // set it as the first item after desktop
494
    m_menu->insertAction(m_activityMenu ? m_activityMenu->menuAction() : m_minimizeOperation, action);
495
496
    action->setText(i18n("Move to &Screen"));
    action->setIcon(QIcon::fromTheme(QStringLiteral("computer")));
497
498
}

499
void UserActionsMenu::initActivityPopup()
500
{
501
    if (m_activityMenu)
502
503
        return;

504
    m_activityMenu = new QMenu(m_menu);
505
506
    connect(m_activityMenu, &QMenu::triggered, this,   &UserActionsMenu::slotToggleOnActivity);
    connect(m_activityMenu, &QMenu::aboutToShow, this, &UserActionsMenu::activityPopupAboutToShow);
507

508
    QAction *action = m_activityMenu->menuAction();
509
    // set it as the first item
510
511
512
    m_menu->insertAction(m_maximizeOperation, action);
    action->setText(i18n("Show in &Activities"));
    action->setIcon(QIcon::fromTheme(QStringLiteral("activities")));
513
}
514

515
void UserActionsMenu::desktopPopupAboutToShow()
516
{
517
    if (!m_desktopMenu)
518
        return;
519
    const VirtualDesktopManager *vds = VirtualDesktopManager::self();
520

521
    m_desktopMenu->clear();
Frederik Gladhorn's avatar
Frederik Gladhorn committed
522
523
524
    if (m_client) {
        m_desktopMenu->setPalette(m_client->palette());
    }
525
526
    QActionGroup *group = new QActionGroup(m_desktopMenu);
    QAction *action = m_desktopMenu->addAction(i18n("&All Desktops"));
527
528
    action->setData(0);
    action->setCheckable(true);
529
    group->addAction(action);
530

Frederik Gladhorn's avatar
Frederik Gladhorn committed
531
    if (m_client && m_client->isOnAllDesktops()) {
532
        action->setChecked(true);
Frederik Gladhorn's avatar
Frederik Gladhorn committed
533
    }
534
    m_desktopMenu->addSeparator();
535

536
    const uint BASE = 10;
537

538
    for (uint i = 1; i <= vds->count(); ++i) {
539
        QString basic_name(QStringLiteral("%1  %2"));
540
        if (i < BASE) {
Nick Shaforostoff's avatar
Nick Shaforostoff committed
541
            basic_name.prepend(QLatin1Char('&'));
542
        }
Nick Shaforostoff's avatar
Nick Shaforostoff committed
543
        action = m_desktopMenu->addAction(basic_name.arg(i).arg(vds->name(i).replace(QLatin1Char('&'), QStringLiteral("&&"))));
544
545
        action->setData(i);
        action->setCheckable(true);
546
        group->addAction(action);
547

Frederik Gladhorn's avatar
Frederik Gladhorn committed
548
        if (m_client && !m_client->isOnAllDesktops() && m_client->isOnDesktop(i)) {
549
            action->setChecked(true);
Frederik Gladhorn's avatar
Frederik Gladhorn committed
550
        }
551
    }
552

553
554
    m_desktopMenu->addSeparator();
    action = m_desktopMenu->addAction(i18nc("Create a new desktop and move there the window", "&New Desktop"));
555
    action->setData(vds->count() + 1);
556

557
    if (vds->count() >= vds->maximum())
558
        action->setEnabled(false);
559
}
560

561
562
563
564
565
566
567
void UserActionsMenu::multipleDesktopsPopupAboutToShow()
{
    if (!m_multipleDesktopsMenu)
        return;
    const VirtualDesktopManager *vds = VirtualDesktopManager::self();

    m_multipleDesktopsMenu->clear();
Frederik Gladhorn's avatar
Frederik Gladhorn committed
568
569
570
    if (m_client) {
        m_multipleDesktopsMenu->setPalette(m_client->palette());
    }
571
572
573
574
575
576
    QAction *action = m_multipleDesktopsMenu->addAction(i18n("&All Desktops"));
    action->setData(0);
    action->setCheckable(true);
    QActionGroup *allDesktopsGroup = new QActionGroup(m_multipleDesktopsMenu);
    allDesktopsGroup->addAction(action);

Frederik Gladhorn's avatar
Frederik Gladhorn committed
577
    if (m_client && m_client->isOnAllDesktops()) {
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
        action->setChecked(true);
    }
    m_multipleDesktopsMenu->addSeparator();


    const uint BASE = 10;

    for (uint i = 1; i <= vds->count(); ++i) {
        QString basic_name(QStringLiteral("%1  %2"));
        if (i < BASE) {
            basic_name.prepend(QLatin1Char('&'));
        }
        QWidgetAction *action = new QWidgetAction(m_multipleDesktopsMenu);
        QCheckBox *box = new QCheckBox(basic_name.arg(i).arg(vds->name(i).replace(QLatin1Char('&'), QStringLiteral("&&"))), m_multipleDesktopsMenu);
        action->setDefaultWidget(box);

        box->setBackgroundRole(m_multipleDesktopsMenu->backgroundRole());
        box->setForegroundRole(m_multipleDesktopsMenu->foregroundRole());
        box->setPalette(m_multipleDesktopsMenu->palette());
        connect(box, &QCheckBox::clicked, action, &QAction::triggered);
        m_multipleDesktopsMenu->addAction(action);
        action->setData(i);

Frederik Gladhorn's avatar
Frederik Gladhorn committed
601
        if (m_client && !m_client->isOnAllDesktops() && m_client->isOnDesktop(i)) {
602
603
604
605
606
607
608
609
610
611
612
613
            box->setChecked(true);
        }
    }

    m_multipleDesktopsMenu->addSeparator();
    action = m_multipleDesktopsMenu->addAction(i18nc("Create a new desktop and move there the window", "&New Desktop"));
    action->setData(vds->count() + 1);

    if (vds->count() >= vds->maximum())
        action->setEnabled(false);
}

614
void UserActionsMenu::screenPopupAboutToShow()
615
{
616
    if (!m_screenMenu) {
617
618
        return;
    }
619
    m_screenMenu->clear();
Frederik Gladhorn's avatar
Frederik Gladhorn committed
620
621
622
623
624

    if (!m_client) {
        return;
    }
    m_screenMenu->setPalette(m_client->palette());
625
    QActionGroup *group = new QActionGroup(m_screenMenu);
626

627
    for (int i = 0; i<screens()->count(); ++i) {
628
        // assumption: there are not more than 9 screens attached.
629
630
        QAction *action = m_screenMenu->addAction(i18nc("@item:inmenu List of all Screens to send a window to. First argument is a number, second the output identifier. E.g. Screen 1 (HDMI1)",
                                                        "Screen &%1 (%2)", (i+1), screens()->name(i)));
631
632
        action->setData(i);
        action->setCheckable(true);
Frederik Gladhorn's avatar
Frederik Gladhorn committed
633
        if (m_client && i == m_client->screen()) {
634
635
636
637
638
639
            action->setChecked(true);
        }
        group->addAction(action);
    }
}

640
void UserActionsMenu::activityPopupAboutToShow()
641
{
642
    if (!m_activityMenu)
643
644
        return;

645
#ifdef KWIN_BUILD_ACTIVITIES
646
647
648
    if (!Activities::self()) {
        return;
    }
649
    m_activityMenu->clear();
Frederik Gladhorn's avatar
Frederik Gladhorn committed
650
651
652
    if (m_client) {
        m_activityMenu->setPalette(m_client->palette());
    }
653
    QAction *action = m_activityMenu->addAction(i18n("&All Activities"));
654
655
    action->setData(QString());
    action->setCheckable(true);
656
    static QPointer<QActionGroup> allActivitiesGroup;
657
658
659
660
    if (!allActivitiesGroup) {
        allActivitiesGroup = new QActionGroup(m_activityMenu);
    }
    allActivitiesGroup->addAction(action);
661

Frederik Gladhorn's avatar
Frederik Gladhorn committed
662
    if (m_client && m_client->isOnAllActivities()) {
663
        action->setChecked(true);
Frederik Gladhorn's avatar
Frederik Gladhorn committed
664
    }
665
    m_activityMenu->addSeparator();
666

667
    foreach (const QString &id, Activities::self()->running()) {
Aaron J. Seigo's avatar
Aaron J. Seigo committed
668
        KActivities::Info activity(id);
669
        QString name = activity.name();
670
        name.replace('&', "&&");
671
672
673
674
675
        QWidgetAction *action = new QWidgetAction(m_activityMenu);
        QCheckBox *box = new QCheckBox(name, m_activityMenu);
        action->setDefaultWidget(box);
        const QString icon = activity.icon();
        if (!icon.isEmpty())
676
            box->setIcon(QIcon::fromTheme(icon));
677
678
679
        box->setBackgroundRole(m_activityMenu->backgroundRole());
        box->setForegroundRole(m_activityMenu->foregroundRole());
        box->setPalette(m_activityMenu->palette());
680
        connect(box, &QCheckBox::clicked, action, &QAction::triggered);
681
        m_activityMenu->addAction(action);
682
        action->setData(id);
683

Frederik Gladhorn's avatar
Frederik Gladhorn committed
684
        if (m_client && !m_client->isOnAllActivities() && m_client->isOnActivity(id)) {
685
            box->setChecked(true);
Frederik Gladhorn's avatar
Frederik Gladhorn committed
686
        }
687
    }
688
#endif
689
}
690

691
692
693
694
695
696
void UserActionsMenu::slotWindowOperation(QAction *action)
{
    if (!action->data().isValid())
        return;

    Options::WindowOperation op = static_cast< Options::WindowOperation >(action->data().toInt());
697
    QPointer<AbstractClient> c = m_client ? m_client : QPointer<AbstractClient>(Workspace::self()->activeClient());
698
699
700
701
702
    if (c.isNull())
        return;
    QString type;
    switch(op) {
    case Options::FullScreenOp:
703
        if (!c->isFullScreen() && c->userCanSetFullScreen())
704
            type = QStringLiteral("fullscreenaltf3");
705
706
        break;
    case Options::NoBorderOp:
707
        if (!c->noBorder() && c->userCanSetNoBorder())
708
            type = QStringLiteral("noborderaltf3");
709
710
711
        break;
    default:
        break;
Frederik Gladhorn's avatar
Frederik Gladhorn committed
712
    }
713
714
    if (!type.isEmpty())
        helperDialog(type, c);
715
716
    // need to delay performing the window operation as we need to have the
    // user actions menu closed before we destroy the decoration. Otherwise Qt crashes
717
    qRegisterMetaType<Options::WindowOperation>();
718
719
    QMetaObject::invokeMethod(workspace(), "performWindowOperation",
                              Qt::QueuedConnection,
720
                              Q_ARG(KWin::AbstractClient*, c),
721
                              Q_ARG(Options::WindowOperation, op));
722
723
724
725
}

void UserActionsMenu::slotSendToDesktop(QAction *action)
{
726
727
728
729
730
    bool ok = false;
    uint desk = action->data().toUInt(&ok);
    if (!ok) {
        return;
    }
731
    if (m_client.isNull())
732
733
        return;
    Workspace *ws = Workspace::self();
734
    VirtualDesktopManager *vds = VirtualDesktopManager::self();
735
736
    if (desk == 0) {
        // the 'on_all_desktops' menu entry
Frederik Gladhorn's avatar
Frederik Gladhorn committed
737
738
739
        if (m_client) {
            m_client->setOnAllDesktops(!m_client->isOnAllDesktops());
        }
740
        return;
741
742
    } else if (desk > vds->count()) {
        vds->setCount(desk);
743
744
    }

745
    ws->sendClientToDesktop(m_client.data(), desk, false);
746
747
}

748
749
750
751
752
753
754
755
756
757
758
759
760
761
void UserActionsMenu::slotToggleOnVirtualDesktop(QAction *action)
{
    bool ok = false;
    uint desk = action->data().toUInt(&ok);
    if (!ok) {
        return;
    }
    if (m_client.isNull()) {
        return;
    }

    VirtualDesktopManager *vds = VirtualDesktopManager::self();
    if (desk == 0) {
        // the 'on_all_desktops' menu entry
Frederik Gladhorn's avatar
Frederik Gladhorn committed
762
        m_client->setOnAllDesktops(!m_client->isOnAllDesktops());
763
764
765
766
767
768
        return;
    } else if (desk > vds->count()) {
        vds->setCount(desk);
    }

    VirtualDesktop *virtualDesktop = VirtualDesktopManager::self()->desktopForX11Id(desk);
Frederik Gladhorn's avatar
Frederik Gladhorn committed
769
770
    if (m_client->desktops().contains(virtualDesktop)) {
        m_client->leaveDesktop(virtualDesktop);
771
    } else {
Frederik Gladhorn's avatar
Frederik Gladhorn committed
772
        m_client->enterDesktop(virtualDesktop);
773
774
775
    }
}

776
777
778
779
780
781
void UserActionsMenu::slotSendToScreen(QAction *action)
{
    const int screen = action->data().toInt();
    if (m_client.isNull()) {
        return;
    }
782
    if (screen >= screens()->count()) {
783
784
785
        return;
    }

786
    Workspace::self()->sendClientToScreen(m_client.data(), screen);
787
788
789
790
}

void UserActionsMenu::slotToggleOnActivity(QAction *action)
{
791
#ifdef KWIN_BUILD_ACTIVITIES
792
793
794
    if (!Activities::self()) {
        return;
    }
795
796
797
798
799
    QString activity = action->data().toString();
    if (m_client.isNull())
        return;
    if (activity.isEmpty()) {
        // the 'on_all_activities' menu entry
Frederik Gladhorn's avatar
Frederik Gladhorn committed
800
        m_client->setOnAllActivities(!m_client->isOnAllActivities());
801
802
803
        return;
    }

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
804
    X11Client *c = dynamic_cast<X11Client *>(m_client.data());
805
806
807
808
809
    if (!c) {
        return;
    }

    Activities::self()->toggleClientOnActivity(c, activity, false);
810
    if (m_activityMenu && m_activityMenu->isVisible() && m_activityMenu->actions().count()) {
Frederik Gladhorn's avatar
Frederik Gladhorn committed
811
        const bool isOnAll = m_client->isOnAllActivities();
812
813
814
815
816
817
818
819
820
821
822
823
824
        m_activityMenu->actions().at(0)->setChecked(isOnAll);
        if (isOnAll) {
            // toggleClientOnActivity interprets "on all" as "on none" and
            // susequent toggling ("off") would move the client to only that activity.
            // bug #330838 -> set all but "on all" off to "force proper usage"
            for (int i = 1; i < m_activityMenu->actions().count(); ++i) {
                if (QWidgetAction *qwa = qobject_cast<QWidgetAction*>(m_activityMenu->actions().at(i))) {
                    if (QCheckBox *qcb = qobject_cast<QCheckBox*>(qwa->defaultWidget())) {
                        qcb->setChecked(false);
                    }
                }
            }
        }
825
    }
826
827
#else
    Q_UNUSED(action)
828
#endif
829
830
}

831
832
833
834
835
836
//****************************************
// ShortcutDialog
//****************************************
ShortcutDialog::ShortcutDialog(const QKeySequence& cut)
    : _shortcut(cut)
{
837
838
839
    m_ui.setupUi(this);
    m_ui.keySequenceEdit->setKeySequence(cut);
    m_ui.warning->hide();
840
841

    // Listen to changed shortcuts
842
843
844
845
846
    connect(m_ui.keySequenceEdit, &QKeySequenceEdit::editingFinished, this, &ShortcutDialog::keySequenceChanged);
    connect(m_ui.clearButton, &QToolButton::clicked, [this]{
        _shortcut = QKeySequence();
    });
    m_ui.keySequenceEdit->setFocus();
847

848
    setWindowFlags(Qt::Popup | Qt::X11BypassWindowManagerHint);
849
850
851
852
853
854
855
856
857
858
859
860
861
}

void ShortcutDialog::accept()
{
    QKeySequence seq = shortcut();
    if (!seq.isEmpty()) {
        if (seq[0] == Qt::Key_Escape) {
            reject();
            return;
        }
        if (seq[0] == Qt::Key_Space
        || (seq[0] & Qt::KeyboardModifierMask) == 0) {
            // clear
862
            m_ui.keySequenceEdit->clear();
863
            QDialog::accept();
864
865
866
            return;
        }
    }
867
    QDialog::accept();
868
869
870
871
}

void ShortcutDialog::done(int r)
{
872
    QDialog::done(r);
873
874
875
    emit dialogDone(r == Accepted);
}

876
void ShortcutDialog::keySequenceChanged()
877
878
{
    activateWindow(); // where is the kbd focus lost? cause of popup state?
879
    QKeySequence seq = m_ui.keySequenceEdit->keySequence();
880
881
882
883
884
885
886
    if (_shortcut == seq)
        return; // don't try to update the same

    if (seq.isEmpty()) { // clear
        _shortcut = seq;
        return;
    }
887
888
889
890
    if (seq.count() > 1) {
        seq = QKeySequence(seq[0]);
        m_ui.keySequenceEdit->setKeySequence(seq);
    }
891
892
893
894
895
896
897

    // Check if the key sequence is used currently
    QString sc = seq.toString();
    // NOTICE - seq.toString() & the entries in "conflicting" randomly get invalidated after the next call (if no sc has been set & conflicting isn't empty?!)
    QList<KGlobalShortcutInfo> conflicting = KGlobalAccel::getGlobalShortcutsByKey(seq);
    if (!conflicting.isEmpty()) {
        const KGlobalShortcutInfo &conflict = conflicting.at(0);
898
        m_ui.warning->setText(i18nc("'%1' is a keyboard shortcut like 'ctrl+w'",
899
        "<b>%1</b> is already in use", sc));
900
        m_ui.warning->setToolTip(i18nc("keyboard shortcut '%1' is used by action '%2' in application '%3'",