You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

448 lines
13 KiB

/*
* 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"
#include <QAction>
#include <QGuiApplication>
#include <QDebug>
#include <QDesktopWidget>
#include <QQuickItem>
#include <QQmlContext>
#include <QScreen>
#include <QQmlEngine>
#include <QClipboard>
#include <QPlatformSurfaceEvent>
#include <KWindowSystem>
#include <KWindowEffects>
#include <KAuthorized>
#include <KGlobalAccel>
#include <KLocalizedString>
#include <KDirWatch>
#include <KCrash/KCrash>
#include <kdeclarative/qmlobject.h>
#include <KPackage/Package>
#include <KPackage/PackageLoader>
#include <KWayland/Client/connection_thread.h>
#include <KWayland/Client/registry.h>
#include <KWayland/Client/surface.h>
#include <KWayland/Client/plasmashell.h>
#include "appadaptor.h"
View::View(QWindow *)
: PlasmaQuick::Dialog(),
m_offset(.5),
m_floating(false),
m_plasmaShell(nullptr)
{
initWayland();
setClearBeforeRendering(true);
setColor(QColor(Qt::transparent));
setFlags(Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
KCrash::setFlags(KCrash::AutoRestart);
//used only by screen readers
setTitle(i18n("KRunner"));
m_config = KConfigGroup(KSharedConfig::openConfig(QStringLiteral("krunnerrc")), "General");
setFreeFloating(m_config.readEntry("FreeFloating", false));
reloadConfig();
new AppAdaptor(this);
QDBusConnection::sessionBus().registerObject(QStringLiteral("/App"), this);
QAction *a = new QAction(0);
QObject::connect(a, &QAction::triggered, this, &View::displayOrHide);
a->setText(i18n("Run Command"));
a->setObjectName(QStringLiteral("run command"));
a->setProperty("componentDisplayName", i18nc("Name for krunner shortcuts category", "Run Command"));
KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << QKeySequence(Qt::ALT + Qt::Key_Space), KGlobalAccel::NoAutoloading);
KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << QKeySequence(Qt::ALT + Qt::Key_Space) << QKeySequence(Qt::ALT + Qt::Key_F2) << Qt::Key_Search);
a = new QAction(0);
QObject::connect(a, &QAction::triggered, this, &View::displayWithClipboardContents);
a->setText(i18n("Run Command on clipboard contents"));
a->setObjectName(QStringLiteral("run command on clipboard contents"));
a->setProperty("componentDisplayName", i18nc("Name for krunner shortcuts category", "Run Command"));
KGlobalAccel::self()->setDefaultShortcut(a, QList<QKeySequence>() << QKeySequence(Qt::ALT+Qt::SHIFT+Qt::Key_F2));
KGlobalAccel::self()->setShortcut(a, QList<QKeySequence>() << QKeySequence(Qt::ALT+Qt::SHIFT+Qt::Key_F2));
m_qmlObj = new KDeclarative::QmlObject(this);
m_qmlObj->setInitializationDelayed(true);
connect(m_qmlObj, &KDeclarative::QmlObject::finished, this, &View::objectIncubated);
KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
KConfigGroup cg(KSharedConfig::openConfig(QStringLiteral("kdeglobals")), "KDE");
const QString packageName = cg.readEntry("LookAndFeelPackage", QString());
if (!packageName.isEmpty()) {
package.setPath(packageName);
}
m_qmlObj->setSource(QUrl::fromLocalFile(package.filePath("runcommandmainscript")));
m_qmlObj->engine()->rootContext()->setContextProperty(QStringLiteral("runnerWindow"), this);
m_qmlObj->completeInitialization();
auto screenRemoved = [this](QScreen* screen) {
if (screen == this->screen()) {
setScreen(qGuiApp->primaryScreen());
hide();
}
};
auto screenAdded = [this](QScreen* screen) {
connect(screen, &QScreen::geometryChanged, this, &View::screenGeometryChanged);
screenGeometryChanged();
};
foreach(QScreen* s, QGuiApplication::screens())
screenAdded(s);
connect(qGuiApp, &QGuiApplication::screenAdded, this, screenAdded);
connect(qGuiApp, &QGuiApplication::screenRemoved, this, screenRemoved);
connect(KWindowSystem::self(), &KWindowSystem::workAreaChanged, this, &View::resetScreenPos);
connect(this, &View::visibleChanged, this, &View::resetScreenPos);
KDirWatch::self()->addFile(m_config.name());
// Catch both, direct changes to the config file ...
connect(KDirWatch::self(), &KDirWatch::dirty, this, &View::reloadConfig);
connect(KDirWatch::self(), &KDirWatch::created, this, &View::reloadConfig);
if (m_floating) {
setLocation(Plasma::Types::Floating);
} else {
setLocation(Plasma::Types::TopEdge);
}
connect(qGuiApp, &QGuiApplication::focusWindowChanged, this, &View::slotFocusWindowChanged);
}
View::~View()
{
}
void View::initWayland()
{
if (!KWindowSystem::isPlatformWayland()) {
return;
}
using namespace KWayland::Client;
auto connection = ConnectionThread::fromApplication(this);
if (!connection) {
return;
}
Registry *registry = new Registry(this);
registry->create(connection);
QObject::connect(registry, &Registry::interfacesAnnounced, this,
[registry, this] {
const auto interface = registry->interface(Registry::Interface::PlasmaShell);
if (interface.name != 0) {
m_plasmaShell = registry->createPlasmaShell(interface.name, interface.version, this);
}
}
);
registry->setup();
connection->roundtrip();
}
void View::objectIncubated()
{
connect(m_qmlObj->rootObject(), SIGNAL(widthChanged()), this, SLOT(resetScreenPos()));
setMainItem(qobject_cast<QQuickItem *>(m_qmlObj->rootObject()));
}
void View::slotFocusWindowChanged()
{
if (!QGuiApplication::focusWindow()) {
setVisible(false);
}
}
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();
}
void View::reloadConfig()
{
m_config.config()->reparseConfiguration();
setFreeFloating(m_config.readEntry("FreeFloating", false));
const QStringList history = m_config.readEntry("history", QStringList());
if (m_history != history) {
m_history = history;
emit historyChanged();
}
}
bool View::event(QEvent *event)
{
// 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.
const bool retval = Dialog::event(event);
bool setState = event->type() == QEvent::Show;
if (event->type() == QEvent::PlatformSurface) {
if (auto e = dynamic_cast<QPlatformSurfaceEvent*>(event)) {
setState = e->surfaceEventType() == QPlatformSurfaceEvent::SurfaceCreated;
}
}
if (setState) {
KWindowSystem::setState(winId(), NET::SkipTaskbar | NET::SkipPager);
}
if (m_plasmaShell && event->type() == QEvent::Expose) {
using namespace KWayland::Client;
auto ee = static_cast<QExposeEvent*>(event);
if (ee->region().isNull()) {
return retval;
}
if (!m_plasmaShellSurface && isVisible()) {
Surface *s = Surface::fromWindow(this);
if (!s) {
return retval;
}
m_plasmaShellSurface = m_plasmaShell->createSurface(s, this);
m_plasmaShellSurface->setPanelBehavior(PlasmaShellSurface::PanelBehavior::WindowsGoBelow);
m_plasmaShellSurface->setPanelTakesFocus(true);
m_plasmaShellSurface->setRole(PlasmaShellSurface::Role::Panel);
//this should be on showEvent, but it was too soon so none of those had any effect
KWindowSystem::setOnAllDesktops(winId(), true);
positionOnScreen();
requestActivate();
//positionOnScreen tried to position it in the position it already had, so no moveevent happens and we need to manually posiyion the surface
m_plasmaShellSurface->setPosition(position());
}
} else if (event->type() == QEvent::Hide) {
delete m_plasmaShellSurface;
} else if (m_plasmaShellSurface && event->type() == QEvent::Move) {
QMoveEvent *me = static_cast<QMoveEvent *>(event);
m_plasmaShellSurface->setPosition(me->pos());
}
return retval;
}
void View::resizeEvent(QResizeEvent *event)
{
if (event->oldSize().width() != event->size().width()) {
positionOnScreen();
}
}
void View::showEvent(QShowEvent *event)
{
KWindowSystem::setOnAllDesktops(winId(), true);
Dialog::showEvent(event);
positionOnScreen();
requestActivate();
}
void View::screenGeometryChanged()
{
if (isVisible()) {
positionOnScreen();
}
}
void View::resetScreenPos()
{
if (isVisible() && !m_floating) {
positionOnScreen();
}
}
void View::positionOnScreen()
{
QScreen *shownOnScreen = QGuiApplication::primaryScreen();
Q_FOREACH (QScreen* screen, QGuiApplication::screens()) {
if (screen->geometry().contains(QCursor::pos(screen))) {
shownOnScreen = screen;
break;
}
}
setScreen(shownOnScreen);
const QRect r = 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);
show();
return;
}
const int w = width();
int x = r.left() + (r.width() * m_offset) - (w / 2);
int y = r.top();
if (m_floating) {
y += r.height() / 3;
}
x = qBound(r.left(), x, r.right() - width());
y = qBound(r.top(), y, r.bottom() - height());
setPosition(x, y);
if (m_floating) {
KWindowSystem::setOnDesktop(winId(), KWindowSystem::currentDesktop());
KWindowSystem::setType(winId(), NET::Normal);
//Turn the sliding effect off
KWindowEffects::slideWindow(winId(), KWindowEffects::NoEdge, 0);
} else {
KWindowSystem::setOnAllDesktops(winId(), true);
KWindowEffects::slideWindow(winId(), KWindowEffects::TopEdge, 0);
}
KWindowSystem::forceActiveWindow(winId());
//qDebug() << "moving to" << m_screenPos[screen];
}
void View::displayOrHide()
{
if (isVisible() && !QGuiApplication::focusWindow()) {
KWindowSystem::forceActiveWindow(winId());
return;
}
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());
m_qmlObj->rootObject()->setProperty("query", QGuiApplication::clipboard()->text(QClipboard::Selection));
}
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()
{
QDBusConnection::sessionBus().asyncCall(
QDBusMessage::createMethodCall(QStringLiteral("org.kde.ksmserver"),
QStringLiteral("/KSMServer"),
QStringLiteral("org.kde.KSMServerInterface"),
QStringLiteral("openSwitchUserDialog"))
);
}
void View::displayConfiguration()
{
QProcess::startDetached(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("plasmasearch"));
}
QStringList View::history() const
{
return m_history;
}
void View::addToHistory(const QString &item)
{
if (item == QLatin1String("SESSIONS")) {
return;
}
if (!KAuthorized::authorize(QStringLiteral("lineedit_text_completion"))) {
return;
}
m_history.removeOne(item);
m_history.prepend(item);
while (m_history.count() > 50) { // make configurable?
m_history.removeLast();
}
emit historyChanged();
writeHistory();
m_config.sync();
}
void View::removeFromHistory(int index)
{
if (index < 0 || index >= m_history.count()) {
return;
}
m_history.removeAt(index);
emit historyChanged();
writeHistory();
}
void View::writeHistory()
{
m_config.writeEntry("history", m_history);
}