view.cpp 12.6 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 *  Copyright 2014 Marco Martin <mart@kde.org>
 *
 *  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, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "view.h"

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
21
22
#include <QAction>
#include <QGuiApplication>
23
24
25
26
27
28
#include <QDebug>
#include <QQuickItem>
#include <QQmlContext>
#include <QScreen>
#include <QQmlEngine>
#include <QClipboard>
29
#include <QPlatformSurfaceEvent>
30

31
#include <KAuthorized>
32
33
34
#include <KWindowSystem>
#include <KWindowEffects>
#include <KLocalizedString>
35
#include <KCrash>
36
37
#include <KService>
#include <KIO/CommandLauncherJob>
38
39
40

#include <kdeclarative/qmlobject.h>

41
42
#include <KPackage/Package>
#include <KPackage/PackageLoader>
43

44
45
46
47
48
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/plasmashell.h>

49
50
#include "appadaptor.h"

51
View::View(QWindow *)
52
53
    : PlasmaQuick::Dialog(),
      m_offset(.5),
54
55
      m_floating(false),
      m_retainPriorSearch(false)
56
57
58
{
    setClearBeforeRendering(true);
    setColor(QColor(Qt::transparent));
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
59
    setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
60

61
62
    KCrash::initialize();

Marco Martin's avatar
Marco Martin committed
63
64
    //used only by screen readers
    setTitle(i18n("KRunner"));
65

66
    m_config = KConfigGroup(KSharedConfig::openConfig(), "General");
67
68
69
70
71
72
73
    m_configWatcher = KConfigWatcher::create(KSharedConfig::openConfig());
    connect(m_configWatcher.data(), &KConfigWatcher::configChanged, this, [this](const KConfigGroup &group, const QByteArrayList &names) {
        Q_UNUSED(names);
        if (group.name() == QLatin1String("General")) {
            loadConfig();
        }
    });
74

75
    loadConfig();
76

77
    new AppAdaptor(this);
78
    QDBusConnection::sessionBus().registerObject(QStringLiteral("/App"), this);
79
80
81

    m_qmlObj = new KDeclarative::QmlObject(this);
    m_qmlObj->setInitializationDelayed(true);
82
    connect(m_qmlObj, &KDeclarative::QmlObject::finished, this, &View::objectIncubated);
83

84
    KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
85
    KConfigGroup cg(KSharedConfig::openConfig(), "KDE");
86
87
88
89
90
    const QString packageName = cg.readEntry("LookAndFeelPackage", QString());
    if (!packageName.isEmpty()) {
        package.setPath(packageName);
    }

91
    m_qmlObj->engine()->rootContext()->setContextProperty(QStringLiteral("runnerWindow"), this);
92
    m_qmlObj->setSource(package.fileUrl("runcommandmainscript"));
93
94
    m_qmlObj->completeInitialization();

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
95
    auto screenRemoved = [this](QScreen* screen) {
96
97
98
99
100
101
        if (screen == this->screen()) {
            setScreen(qGuiApp->primaryScreen());
            hide();
        }
    };

Méven Car's avatar
Méven Car committed
102
    auto screenAdded = [this](const QScreen* screen) {
103
        connect(screen, &QScreen::geometryChanged, this, &View::screenGeometryChanged);
104
105
        screenGeometryChanged();
    };
106

Méven Car's avatar
Méven Car committed
107
108
    const auto screens = QGuiApplication::screens();
    for(QScreen* s : screens) {
109
        screenAdded(s);
Méven Car's avatar
Méven Car committed
110
    }
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
111
112
    connect(qGuiApp, &QGuiApplication::screenAdded, this, screenAdded);
    connect(qGuiApp, &QGuiApplication::screenRemoved, this, screenRemoved);
113

Laurent Montel's avatar
Laurent Montel committed
114
    connect(KWindowSystem::self(), &KWindowSystem::workAreaChanged, this, &View::resetScreenPos);
115

116
    connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &View::slotFocusWindowChanged);
117
118
119
120
121
122
}

View::~View()
{
}

123
124
void View::objectIncubated()
{
125
126
127
    auto mainItem = qobject_cast<QQuickItem *>(m_qmlObj->rootObject());
    connect(mainItem, &QQuickItem::widthChanged, this, &View::resetScreenPos);
    setMainItem(mainItem);
128
129
}

130
131
132
133
134
135
136
void View::slotFocusWindowChanged()
{
    if (!QGuiApplication::focusWindow()) {
        setVisible(false);
    }
}

137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
bool View::freeFloating() const
{
    return m_floating;
}

void View::setFreeFloating(bool floating)
{
    if (m_floating == floating) {
        return;
    }

    m_floating = floating;
    if (m_floating) {
        setLocation(Plasma::Types::Floating);
    } else {
        setLocation(Plasma::Types::TopEdge);
    }

    positionOnScreen();
}

158
void View::loadConfig()
159
160
{
    setFreeFloating(m_config.readEntry("FreeFloating", false));
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
161

162
163
164
165
166
    m_historyEnabled = m_config.readEntry("HistoryEnabled", true);
    QStringList history;
    if (m_historyEnabled) {
        history = m_config.readEntry("history", QStringList());
    }
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
167
168
169
170
    if (m_history != history) {
        m_history = history;
        emit historyChanged();
    }
171
172
173
174
175
    bool retainPriorSearch = m_config.readEntry("RetainPriorSearch", true);
    if (retainPriorSearch != m_retainPriorSearch) {
        m_retainPriorSearch = retainPriorSearch;
        Q_EMIT retainPriorSearchChanged();
    }
176
177
}

178
179
bool View::event(QEvent *event)
{
180
181
182
183
184
    if (KWindowSystem::isPlatformWayland() && event->type() == QEvent::Expose && !dynamic_cast<QExposeEvent*>(event)->region().isNull()) {
        auto surface = KWayland::Client::Surface::fromWindow(this);
        auto shellSurface = KWayland::Client::PlasmaShellSurface::get(surface);
        if (shellSurface && isVisible()) {
            shellSurface->setPanelBehavior(KWayland::Client::PlasmaShellSurface::PanelBehavior::WindowsGoBelow);
185
            shellSurface->setRole(KWayland::Client::PlasmaShellSurface::Role::Panel);
186
187
188
189
            shellSurface->setPanelTakesFocus(true);
        }
    }
    const bool retval = Dialog::event(event);
190
191
192
    // QXcbWindow overwrites the state in its show event. There are plans
    // to fix this in 5.4, but till then we must explicitly overwrite it
    // each time.
193
194
    bool setState = event->type() == QEvent::Show;
    if (event->type() == QEvent::PlatformSurface) {
195
        setState = (static_cast<QPlatformSurfaceEvent*>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated);
196
197
    }
    if (setState) {
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
198
        KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager);
199
    }
200

201
202
203
    return retval;
}

204
205
206
207
208
209
210
void View::resizeEvent(QResizeEvent *event)
{
    if (event->oldSize().width() != event->size().width()) {
        positionOnScreen();
    }
}

211
212
213
214
215
void View::showEvent(QShowEvent *event)
{
    KWindowSystem::setOnAllDesktops(winId(), true);
    Dialog::showEvent(event);
    positionOnScreen();
Vishesh Handa's avatar
Vishesh Handa committed
216
    requestActivate();
217
218
}

219
void View::screenGeometryChanged()
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
{
    if (isVisible()) {
        positionOnScreen();
    }
}

void View::resetScreenPos()
{
    if (isVisible() && !m_floating) {
        positionOnScreen();
    }
}

void View::positionOnScreen()
{
235
236
237
238
    if (!m_requestedVisible) {
        return;
    }

239
240
    QScreen *shownOnScreen = QGuiApplication::primaryScreen();

Méven Car's avatar
Méven Car committed
241
242
    const auto screens = QGuiApplication::screens();
    for (QScreen* screen : screens) {
243
244
245
        if (screen->geometry().contains(QCursor::pos(screen))) {
            shownOnScreen = screen;
            break;
246
        }
247
248
    }

249
250
251
    // in wayland, QScreen::availableGeometry() returns QScreen::geometry()
    // we could get a better value from plasmashell
    // BUG: 386114
252
253
254
255
    auto message = QDBusMessage::createMethodCall("org.kde.plasmashell", "/StrutManager",  "org.kde.PlasmaShell.StrutManager", "availableScreenRect");
    message.setArguments({shownOnScreen->name()});
    QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(message);
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this);
256

Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
257
258
    QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, [this, watcher, shownOnScreen]() {
        watcher->deleteLater();
259
260
261
262
263
264
265
266
267
268
269
        QDBusPendingReply<QRect> reply = *watcher;

        const QRect r = reply.isValid() ? reply.value() : shownOnScreen->availableGeometry();

        if (m_floating && !m_customPos.isNull()) {
            int x = qBound(r.left(), m_customPos.x(), r.right() - width());
            int y = qBound(r.top(), m_customPos.y(), r.bottom() - height());
            setPosition(x, y);
            PlasmaQuick::Dialog::setVisible(true);
            return;
        }
270

271
272
        const int w = width();
        int x = r.left() + (r.width() * m_offset) - (w / 2);
273

274
275
276
277
        int y = r.top();
        if (m_floating) {
            y += r.height() / 3;
        }
278

279
280
        x = qBound(r.left(), x, r.right() - width());
        y = qBound(r.top(), y, r.bottom() - height());
281

282
283
284
285
286
287
288
        setPosition(x, y);
        PlasmaQuick::Dialog::setVisible(true);

        if (m_floating) {
            KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop());
            KWindowSystem::setType(winId(), NET::Normal);
            //Turn the sliding effect off
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
289
            setLocation(Plasma::Types::Floating);
290
291
        } else {
            KWindowSystem::setOnAllDesktops(winId(), true);
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
292
            setLocation(Plasma::Types::TopEdge);
293
        }
294

295
        KWindowSystem::forceActiveWindow(winId());
296

297
    });
298
299
300
301
}

void View::displayOrHide()
{
302
303
304
305
    if (isVisible() && !QGuiApplication::focusWindow())  {
        KWindowSystem::forceActiveWindow(winId());
        return;
    }
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
    setVisible(!isVisible());
}

void View::display()
{
    setVisible(true);
}

void View::displaySingleRunner(const QString &runnerName)
{
    setVisible(true);

    m_qmlObj->rootObject()->setProperty("runner", runnerName);
    m_qmlObj->rootObject()->setProperty("query", QString());
}

void View::displayWithClipboardContents()
{
    setVisible(true);

    m_qmlObj->rootObject()->setProperty("runner", QString());
Aleix Pol Gonzalez's avatar
Aleix Pol Gonzalez committed
327
    m_qmlObj->rootObject()->setProperty("query", QGuiApplication::clipboard()->text(QClipboard::Selection));
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
}

void View::query(const QString &term)
{
    setVisible(true);

    m_qmlObj->rootObject()->setProperty("runner", QString());
    m_qmlObj->rootObject()->setProperty("query", term);
}

void View::querySingleRunner(const QString &runnerName, const QString &term)
{
    setVisible(true);

    m_qmlObj->rootObject()->setProperty("runner", runnerName);
    m_qmlObj->rootObject()->setProperty("query", term);
}

void View::switchUser()
{
348
349
350
351
352
353
    QDBusConnection::sessionBus().asyncCall(
        QDBusMessage::createMethodCall(QStringLiteral("org.kde.ksmserver"),
                                       QStringLiteral("/KSMServer"),
                                       QStringLiteral("org.kde.KSMServerInterface"),
                                       QStringLiteral("openSwitchUserDialog"))
    );
354
355
}

356
357
void View::displayConfiguration()
{
358
359
360
361
362
363
364
365
366
367
368
369
    const QString systemSettings = QStringLiteral("systemsettings");
    const QStringList kcmToOpen = QStringList(QStringLiteral("kcm_plasmasearch"));
    KIO::CommandLauncherJob *job = nullptr;

    if (KService::serviceByDesktopName(systemSettings)) {
        job = new KIO::CommandLauncherJob(QStringLiteral("systemsettings5"), kcmToOpen);
        job->setDesktopName(systemSettings);
    } else {
        job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), kcmToOpen);
    }

    job->start();
370
371
}

372
373
374
375
376
bool View::canConfigure() const
{
    return KAuthorized::authorizeControlModule(QStringLiteral("kcm_plasmasearch.desktop"));
}

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
377
378
379
380
381
382
383
QStringList View::history() const
{
    return m_history;
}

void View::addToHistory(const QString &item)
{
384
385
386
    if (!m_historyEnabled) {
        return;
    }
387
388
389
390
    if (item.isEmpty()) {
        return;
    }

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
391
392
393
394
    if (item == QLatin1String("SESSIONS")) {
        return;
    }

395
396
    // Mimic shell behavior of not storing lines starting with a space
    if (item.at(0).isSpace()) {
397
398
399
        return;
    }

400
401
402
403
404
    // Avoid removing the same item from the front and prepending it again
    if (!m_history.isEmpty() && m_history.constFirst() == item) {
        return;
    }

405
    if (!KAuthorized::authorize(QStringLiteral("lineedit_text_completion"))) {
406
407
408
        return;
    }

Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
409
410
411
    m_history.removeOne(item);
    m_history.prepend(item);

412
    while (m_history.count() > 50) { // make configurable?
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
413
414
415
416
        m_history.removeLast();
    }

    emit historyChanged();
417
418
419
420
421
422
423
424
425
    writeHistory();
    m_config.sync();
}

void View::removeFromHistory(int index)
{
    if (index < 0 || index >= m_history.count()) {
        return;
    }
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
426

427
428
429
430
431
432
433
434
    m_history.removeAt(index);
    emit historyChanged();

    writeHistory();
}

void View::writeHistory()
{
435
436
437
    if (!m_historyEnabled) {
        return;
    }
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
438
439
    m_config.writeEntry("history", m_history);
}
440
441
442
443
444
445
446
447
448
449
450

void View::setVisible(bool visible)
{
    m_requestedVisible = visible;

    if (visible && !m_floating) {
        positionOnScreen();
    } else {
        PlasmaQuick::Dialog::setVisible(visible);
    }
}
451
452
453
454

bool View::retainPriorSearch() const {
    return m_retainPriorSearch;
}