diff --git a/AppMenu.cxx b/AppMenu.cxx index f9e2c34..cf1fe29 100644 --- a/AppMenu.cxx +++ b/AppMenu.cxx @@ -1,194 +1,195 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- AppMenu::AppMenu(DesktopPanel *parent) : Launcher(parent, "AppMenu") { button = new QToolButton; // QToolButton is smaller than QPushButton adjustIconSize(); button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); popup = new Menu(button); connect(button, &QToolButton::pressed, this, &AppMenu::showMenu); layout()->addWidget(button); loadConfig(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); connect(parent, &DesktopPanel::rowsChanged, this, &AppMenu::adjustIconSize); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &AppMenu::adjustIconSize); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &AppMenu::fill); connect(QApplication::desktop(), &QDesktopWidget::resized, this, &AppMenu::fill); } //-------------------------------------------------------------------------------- void AppMenu::adjustIconSize() { const int MAX_ROWS = qobject_cast(parentWidget())->getRows(); if ( MAX_ROWS > 1 ) button->setIconSize(QSize(48, 48)); else { int size = KIconLoader::global()->currentSize(KIconLoader::Panel); button->setIconSize(QSize(size, size)); } } //-------------------------------------------------------------------------------- void AppMenu::fill() { KDesktopFile desktopFile(dirPath + "/.directory"); if ( !desktopFile.readIcon().isEmpty() ) button->setIcon(QIcon::fromTheme(desktopFile.readIcon())); else if ( dirPath == QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) ) button->setIcon(QIcon::fromTheme("user-desktop")); else // fallback button->setIcon(QIcon::fromTheme("folder")); QLayoutItem *child; while ( (child = popup->layout()->takeAt(0)) ) { delete child->widget(); delete child; } QDir dir(dirPath); QFileInfoList entries = dir.entryInfoList(QDir::AllEntries | QDir::NoDotDot); int row = 0, col = 0, h = 0; const int maxHeight = QApplication::desktop()->height() - parentWidget()->sizeHint().height(); for (const QFileInfo &info : entries) { QUrl url(QUrl::fromLocalFile(info.absoluteFilePath())); QString name; QIcon icon; KFileItem item(url); if ( item.isDesktopFile() ) { KDesktopFile desktopFile(info.absoluteFilePath()); if ( desktopFile.noDisplay() ) continue; name = desktopFile.readName(); if ( name.isEmpty() ) name = desktopFile.readGenericName(); QString iconName = desktopFile.readIcon(); icon = QIcon::fromTheme(iconName.isEmpty() ? name : iconName); } else if ( info.isDir() ) { if ( info.fileName() == "." ) { name = info.dir().dirName(); icon = button->icon(); } else icon = QIcon::fromTheme("folder"); } else { QMimeDatabase db; icon = QIcon::fromTheme(db.mimeTypeForFile(info.absoluteFilePath()).iconName()); } if ( name.isEmpty() ) name = info.fileName(); IconButton *entryButton = new IconButton(this, icon, 32, name); connect(entryButton, &IconButton::clicked, [this, url]() { popup->close(); new KRun(url, nullptr); }); h += entryButton->sizeHint().height(); if ( h >= maxHeight ) { h = entryButton->sizeHint().height(); col++; row = 0; } static_cast(popup->layout())->addWidget(entryButton, row++, col); } } //-------------------------------------------------------------------------------- void AppMenu::showMenu() { popup->exec(); button->setDown(false); } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- Menu::Menu(QWidget *parent) : QMenu(parent) { QGridLayout *grid = new QGridLayout(this); grid->setContentsMargins(QMargins()); grid->setSpacing(0); } //-------------------------------------------------------------------------------- void Menu::hideEvent(QHideEvent *event) { Q_UNUSED(event); eventLoop.exit(); } //-------------------------------------------------------------------------------- void Menu::exec() { adjustSize(); QPoint p = parentWidget()->mapToGlobal(QPoint(0, 0)); move(p.x(), p.y() - height()); show(); eventLoop.exec(); } //-------------------------------------------------------------------------------- diff --git a/AppMenu.hxx b/AppMenu.hxx index 9bfb17d..9fd30bf 100644 --- a/AppMenu.hxx +++ b/AppMenu.hxx @@ -1,70 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _AppMenu_H_ #define _AppMenu_H_ #include #include #include #include #include #include //-------------------------------------------------------------------------------- class AppMenu : public Launcher { Q_OBJECT public: AppMenu(DesktopPanel *parent); private Q_SLOTS: void adjustIconSize(); void fill() override; void showMenu(); private: QToolButton *button; class Menu *popup; }; //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- class Menu : public QMenu { Q_OBJECT public: Menu(QWidget *parent); void exec(); protected: void hideEvent(QHideEvent *event) override; private: QEventLoop eventLoop; }; //-------------------------------------------------------------------------------- #endif diff --git a/Battery.cxx b/Battery.cxx index f218ac6..e35e8ff 100644 --- a/Battery.cxx +++ b/Battery.cxx @@ -1,206 +1,207 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- Battery::Battery(QWidget *parent) : SysTrayItem(parent) { QList devices = Solid::Device::listFromType(Solid::DeviceInterface::Battery); for (Solid::Device dev : devices) { if ( dev.is() && (dev.as()->type() == Solid::Battery::PrimaryBattery) ) { device = dev; break; } } if ( !device.isValid() ) hide(); else { QDBusConnection::systemBus() .connect("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(upowerPropertiesChanged(QString, QVariantMap, QStringList))); QDBusMessage msg = QDBusMessage::createMethodCall("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.DBus.Properties", "Get"); msg << QLatin1String("org.freedesktop.UPower") << QLatin1String("OnBattery"); QDBusConnection::systemBus().callWithCallback(msg, this, SLOT(onBatteryReply(QDBusMessage)), nullptr); connect(device.as(), &Solid::Battery::chargePercentChanged, this, &Battery::changed); connect(device.as(), &Solid::Battery::chargeStateChanged, this, &Battery::changed); connect(device.as(), &Solid::Battery::timeToFullChanged, this, &Battery::changed); connect(device.as(), &Solid::Battery::timeToEmptyChanged, this, &Battery::changed); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &Battery::changed); changed(); } } //-------------------------------------------------------------------------------- QString Battery::secsToHM(int secs) const { int h = secs / 3600; int m = (secs % 3600) / 60; QString hStr = i18np("%1 hour", "%1 hours", h); QString mStr = i18np("%1 minute", "%1 minutes", m); QString result; if ( h ) result = hStr; if ( m ) { if ( h ) result += ", "; result += mStr; } return result; } //-------------------------------------------------------------------------------- void Battery::onBatteryReply(const QDBusMessage &msg) { setVisible(msg.arguments()[0].value().variant().toBool()); } //-------------------------------------------------------------------------------- void Battery::upowerPropertiesChanged(const QString &interface, const QVariantMap &properties, const QStringList &invalidated) { Q_UNUSED(interface) Q_UNUSED(invalidated) if ( properties.contains("OnBattery") ) { setVisible(properties.value("OnBattery").toBool()); changed(); } } //-------------------------------------------------------------------------------- QIcon Battery::getStatusIcon(int charge, bool isCharging) { QString iconName; int p = qRound(charge / 20.0) * 20; if ( p < 20 ) iconName = "caution"; else if ( p < 40 ) iconName = "low"; else iconName = QString("%1").arg(p, 3, 10, QLatin1Char('0')); iconName = QString("battery%1-%2").arg(isCharging ? "-charging" : "").arg(iconName); return QIcon::fromTheme(iconName); } //-------------------------------------------------------------------------------- void Battery::changed() { Solid::Battery *battery = device.as(); QString tip; switch ( battery->chargeState() ) { case Solid::Battery::NoCharge: tip = i18n("Not Charging"); break; case Solid::Battery::FullyCharged: tip = i18n("Fully Charged"); break; case Solid::Battery::Charging: case Solid::Battery::Discharging: { if ( battery->chargeState() == Solid::Battery::Charging ) { tip = i18n("Charging at %1%", battery->chargePercent()); if ( battery->timeToFull() ) // it can be 0, so we don't know tip += '\n' + i18n("Time until full: ") + secsToHM(battery->timeToFull()); } else { tip = i18n("Discharging at %1%", battery->chargePercent()); if ( battery->timeToEmpty() ) // it can be 0, so we don't know tip += '\n' + i18n("Remaining Time: ") + secsToHM(battery->timeToEmpty()); } break; } } setPixmap(getStatusIcon(battery->chargePercent(), battery->chargeState() == Solid::Battery::Charging).pixmap(size())); setToolTip(tip); } //-------------------------------------------------------------------------------- QWidget *Battery::getDetailsList() { if ( !dialog ) { dialog = new KCMultiDialog(this); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->addModule("powerdevilglobalconfig"); dialog->addModule("powerdevilprofilesconfig"); dialog->adjustSize(); dialog->setWindowTitle(i18n("Power Management")); } return dialog; } //-------------------------------------------------------------------------------- diff --git a/Battery.hxx b/Battery.hxx index 38e81bb..fae61f3 100644 --- a/Battery.hxx +++ b/Battery.hxx @@ -1,54 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _Battery_H_ #define _Battery_H_ #include #include #include #include class QDBusMessage; class Battery : public SysTrayItem { Q_OBJECT public: Battery(QWidget *parent); static QIcon getStatusIcon(int charge, bool isCharging); protected: QWidget *getDetailsList() override; private Q_SLOTS: void onBatteryReply(const QDBusMessage &msg); void upowerPropertiesChanged(const QString &interface, const QVariantMap &properties, const QStringList &invalidated); void changed(); private: QString secsToHM(int secs) const; private: Solid::Device device; QPointer dialog; }; #endif diff --git a/Bluetooth.cxx b/Bluetooth.cxx index 1920a8b..5ad0359 100644 --- a/Bluetooth.cxx +++ b/Bluetooth.cxx @@ -1,101 +1,102 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include //-------------------------------------------------------------------------------- Bluetooth::Bluetooth(QWidget *parent) : SysTrayItem(parent) { manager = new BluezQt::Manager(this); job = manager->init(); job->start(); connect(job, &BluezQt::InitManagerJob::result, this, &Bluetooth::changed); connect(manager, &BluezQt::Manager::adapterAdded, this, &Bluetooth::changed); connect(manager, &BluezQt::Manager::adapterRemoved, this, &Bluetooth::changed); connect(manager, &BluezQt::Manager::allAdaptersRemoved, this, &Bluetooth::changed); connect(manager, &BluezQt::Manager::bluetoothOperationalChanged, this, &Bluetooth::changed); connect(manager, &BluezQt::Manager::operationalChanged, this, &Bluetooth::changed); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &Bluetooth::changed); } //-------------------------------------------------------------------------------- Bluetooth::~Bluetooth() { if ( job ) job->kill(); } //-------------------------------------------------------------------------------- void Bluetooth::changed() { job = nullptr; if ( manager->adapters().isEmpty() || !manager->isOperational() ) { hide(); // no BT return; } show(); if ( manager->isBluetoothOperational() ) { setPixmap(QIcon::fromTheme("preferences-system-bluetooth.png").pixmap(size())); setToolTip(i18n("Bluetooth is operational")); } else { setPixmap(QIcon::fromTheme("preferences-system-bluetooth-inactive.png").pixmap(size())); setToolTip(i18n("Bluetooth is not operational")); } } //-------------------------------------------------------------------------------- QWidget *Bluetooth::getDetailsList() { if ( !dialog ) { dialog = new KCMultiDialog(this); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->addModule("bluedevilglobal"); dialog->addModule("bluedeviladapters"); dialog->addModule("bluedevildevices"); dialog->adjustSize(); dialog->setWindowTitle(i18n("Bluetooth")); } return dialog; } //-------------------------------------------------------------------------------- diff --git a/Bluetooth.hxx b/Bluetooth.hxx index 7caaf9a..23619dd 100644 --- a/Bluetooth.hxx +++ b/Bluetooth.hxx @@ -1,49 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _Bluetooth_H_ #define _Bluetooth_H_ #include #include #include #include class Bluetooth : public SysTrayItem { Q_OBJECT public: Bluetooth(QWidget *parent); ~Bluetooth() override; protected: QWidget *getDetailsList() override; private Q_SLOTS: void changed(); private: BluezQt::Manager *manager; BluezQt::InitManagerJob *job; QPointer dialog; }; #endif diff --git a/ClockWidget.cxx b/ClockWidget.cxx index 6b23f80..052209b 100644 --- a/ClockWidget.cxx +++ b/ClockWidget.cxx @@ -1,243 +1,244 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017,2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- CalendarPopup::CalendarPopup(QWidget *parent) : QFrame(parent) { setWindowFlags(windowFlags() | Qt::Popup); setFrameShape(QFrame::StyledPanel); QVBoxLayout *vbox = new QVBoxLayout(this); vbox->setContentsMargins(QMargins()); cal = new QCalendarWidget; vbox->addWidget(cal); QPushButton *today = new QPushButton(QIcon::fromTheme("go-jump-today"), QString()); vbox->addWidget(today); connect(today, &QPushButton::clicked, this, &CalendarPopup::goToday); } //-------------------------------------------------------------------------------- void CalendarPopup::goToday() { cal->showToday(); cal->setSelectedDate(QDate::currentDate()); } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- ClockWidget::ClockWidget(DesktopPanel *parent) : QFrame(parent), calendar(nullptr) { ensurePolished(); // make sure we already have the css applied timer = new QTimer(this); // if seconds are shown, update every second, else less often if ( timeFormat.contains('s') ) timer->setInterval(1000); else timer->setInterval(5000); timer->start(); connect(timer, &QTimer::timeout, this, &ClockWidget::tick); connect(parent, &DesktopPanel::rowsChanged, this, &ClockWidget::fill); timeLabel = new QLabel(this); dayLabel = new QLabel(this); dateLabel = new QLabel(this); timeLabel->setObjectName("time"); dayLabel->setObjectName("day"); dateLabel->setObjectName("date"); timeLabel->setContentsMargins(QMargins(0, -5, 0, -5)); dayLabel->setContentsMargins(QMargins(0, -5, 0, -5)); dateLabel->setContentsMargins(QMargins(0, -5, 0, -5)); timeLabel->setAlignment(Qt::AlignCenter); dayLabel->setAlignment(Qt::AlignCenter); dateLabel->setAlignment(Qt::AlignCenter); QFont f = font(); f.setPointSizeF(fontInfo().pointSizeF() * 1.5); f.setBold(true); timeLabel->setFont(f); fill(); timeLabel->setVisible(!timeFormat.isEmpty()); dayLabel->setVisible(!dayFormat.isEmpty()); dateLabel->setVisible(!dateFormat.isEmpty()); // context menu QAction *action = new QAction(this); action->setIcon(QIcon::fromTheme("configure")); action->setText(i18n("Select Timezones...")); addAction(action); connect(action, &QAction::triggered, [this]() { ClockWidgetConfigureDialog dialog(parentWidget(), timeZoneIds); dialog.setWindowTitle(i18n("Select Timezones")); dialog.resize(600, 400); if ( dialog.exec() == QDialog::Accepted ) { timeZoneIds = dialog.getSelectedTimeZoneIds(); KConfig config; KConfigGroup group = config.group("ClockWidget"); QStringList list; for (const QByteArray &id : timeZoneIds) list.append(id); group.writeEntry("timeZoneIds", list); tick(); // update tooltip } } ); action = new QAction(this); action->setIcon(QIcon::fromTheme("preferences-system-time")); action->setText(i18n("Configure Date & Time...")); addAction(action); connect(action, &QAction::triggered, [this]() { auto dialog = new KCMultiDialog(parentWidget()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Date & Time")); dialog->addModule("clock"); dialog->adjustSize(); dialog->show(); } ); setContextMenuPolicy(Qt::ActionsContextMenu); // load config KConfig config; KConfigGroup group = config.group("ClockWidget"); QStringList list; list = group.readEntry("timeZoneIds", QStringList()); for (const QString &id : list) timeZoneIds.append(id.toLatin1()); tick(); } //-------------------------------------------------------------------------------- void ClockWidget::fill() { delete layout(); const int MAX_ROWS = qobject_cast(parentWidget())->getRows(); QBoxLayout *box; if ( MAX_ROWS >= 2 ) { box = new QVBoxLayout(this); box->setSpacing(0); } else { box = new QHBoxLayout(this); } box->setContentsMargins(QMargins()); box->addWidget(timeLabel); box->addWidget(dayLabel); box->addWidget(dateLabel); } //-------------------------------------------------------------------------------- void ClockWidget::tick() { QDateTime dateTimeUtc = QDateTime::currentDateTimeUtc(); QDateTime dateTime = dateTimeUtc.toLocalTime(); timeLabel->setText(dateTime.time().toString(timeFormat)); dayLabel->setText(dateTime.date().toString(dayFormat)); dateLabel->setText(dateTime.date().toString(dateFormat)); if ( !timeZoneIds.isEmpty() ) { QString tip = ""; for (const QByteArray &id : timeZoneIds) { QTimeZone timeZone(id); QDateTime dt = dateTimeUtc; dateTime = dt.toTimeZone(timeZone); tip += QString("") .arg(QLatin1String(id)) .arg(dateTime.time().toString(timeFormat)) .arg(dateTime.date().toString(dayFormat)) .arg(dateTime.date().toString(dateFormat)); } tip += "
%1 %2 %3 %4
"; setToolTip(tip); } } //-------------------------------------------------------------------------------- void ClockWidget::mousePressEvent(QMouseEvent *event) { if ( event->button() == Qt::LeftButton ) { if ( !calendar ) calendar = new CalendarPopup(this); calendar->goToday(); QPoint point = mapToGlobal(pos()); QRect screen = QApplication::desktop()->availableGeometry(this); point.setX(std::min(point.x(), screen.x() + screen.width() - calendar->sizeHint().width())); point.setY(point.y() - calendar->sizeHint().height()); calendar->move(point); calendar->show(); } } //-------------------------------------------------------------------------------- diff --git a/ClockWidget.hxx b/ClockWidget.hxx index 6ce9dfb..25a43f9 100644 --- a/ClockWidget.hxx +++ b/ClockWidget.hxx @@ -1,75 +1,76 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _ClockWidget_H_ #define _ClockWidget_H_ #include #include #include #include #include class CalendarPopup : public QFrame { Q_OBJECT public: CalendarPopup(QWidget *parent); public Q_SLOTS: void goToday(); private: QCalendarWidget *cal; }; //-------------------------------------------------------------------------------- class ClockWidget : public QFrame { Q_OBJECT Q_PROPERTY(QString timeFormat MEMBER timeFormat) Q_PROPERTY(QString dayFormat MEMBER dayFormat) Q_PROPERTY(QString dateFormat MEMBER dateFormat) public: ClockWidget(DesktopPanel *parent); protected: void mousePressEvent(QMouseEvent *event) override; private Q_SLOTS: void fill(); void tick(); private: QTimer *timer; QLabel *timeLabel, *dayLabel, *dateLabel; CalendarPopup *calendar; QString timeFormat = QStringLiteral("HH:mm"); QString dayFormat = QStringLiteral("ddd"); QString dateFormat = QStringLiteral("d.MMM yyyy"); QVector timeZoneIds; }; #endif diff --git a/ClockWidgetConfigureDialog.cxx b/ClockWidgetConfigureDialog.cxx index 3829a81..4ecfe1a 100644 --- a/ClockWidgetConfigureDialog.cxx +++ b/ClockWidgetConfigureDialog.cxx @@ -1,101 +1,102 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- ClockWidgetConfigureDialog::ClockWidgetConfigureDialog(QWidget *parent, const QVector &timeZoneIds) : QDialog(parent) { tree = new QTreeWidget; QLineEdit *filter = new KTreeWidgetSearchLine(this, tree); filter->setClearButtonEnabled(true); filter->setPlaceholderText(i18n("Filter")); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); QVBoxLayout *vbox = new QVBoxLayout(this); vbox->addWidget(filter); vbox->addWidget(tree); vbox->addWidget(buttons); // fill tree tree->setHeaderLabels(QStringList() << i18n("Timezone") << i18n("Country")); tree->setRootIsDecorated(false); QList zones = QTimeZone::availableTimeZoneIds(); for (const QByteArray &zone : zones) { QTimeZone timeZone(zone); QTreeWidgetItem *item = new QTreeWidgetItem(tree); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren); item->setCheckState(0, timeZoneIds.contains(zone) ? Qt::Checked : Qt::Unchecked); item->setText(0, timeZone.id()); item->setText(1, QLocale::countryToString(timeZone.country())); // Locate the flag from share/kf5/locale/countries/%1/flag.png QList matchingLocales = QLocale::matchingLocales(QLocale::AnyLanguage, QLocale::AnyScript, timeZone.country()); if ( !matchingLocales.isEmpty() ) { QString flag = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QString("kf5/locale/countries/%1/flag.png").arg(matchingLocales[0].name().mid(3).toLower())); if ( !flag.isEmpty() ) item->setIcon(1, QPixmap(flag)); } } tree->setSortingEnabled(true); tree->sortByColumn(0, Qt::AscendingOrder); tree->header()->resizeSections(QHeaderView::ResizeToContents); } //-------------------------------------------------------------------------------- QVector ClockWidgetConfigureDialog::getSelectedTimeZoneIds() const { QVector timeZoneIds; for (int i = 0; i < tree->topLevelItemCount(); i++) { QTreeWidgetItem *item = tree->topLevelItem(i); if ( item->checkState(0) == Qt::Checked ) timeZoneIds.append(item->text(0).toUtf8()); } return timeZoneIds; } //-------------------------------------------------------------------------------- diff --git a/ClockWidgetConfigureDialog.hxx b/ClockWidgetConfigureDialog.hxx index da2f86e..58fcaa3 100644 --- a/ClockWidgetConfigureDialog.hxx +++ b/ClockWidgetConfigureDialog.hxx @@ -1,39 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _ClockWidgetConfigureDialog_H_ #define _ClockWidgetConfigureDialog_H_ #include class QTreeWidget; class ClockWidgetConfigureDialog : public QDialog { Q_OBJECT public: ClockWidgetConfigureDialog(QWidget *parent, const QVector &timeZoneIds); QVector getSelectedTimeZoneIds() const; private: QTreeWidget *tree; }; #endif diff --git a/ConfigureDesktopDialog.cxx b/ConfigureDesktopDialog.cxx index 9161e0e..7924dfe 100644 --- a/ConfigureDesktopDialog.cxx +++ b/ConfigureDesktopDialog.cxx @@ -1,177 +1,178 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- ConfigureDesktopDialog::ConfigureDesktopDialog(QWidget *parent, const DesktopWidget::Wallpaper &wp) : QDialog(parent), wallpaper(wp) { ui.setupUi(this); ui.iconView->setIconSize(QSize(200, 200)); connect(ui.iconView, &QListWidget::itemClicked, [this](QListWidgetItem *item) { ui.kurlrequester->setUrl(QUrl::fromLocalFile(item->data(Qt::UserRole).toString())); wallpaper.fileName = ui.kurlrequester->url().toLocalFile(); emit changed(); }); showImages(); QPushButton *newstuff = ui.buttonBox->addButton(i18n("Get New Wallpapers..."), QDialogButtonBox::ActionRole); newstuff->setIcon(QIcon::fromTheme("get-hot-new-stuff")); connect(newstuff, &QPushButton::clicked, [this]() { KNS3::DownloadDialog dialog("wallpaper.knsrc", this); dialog.setTitle(i18n("Download Wallpapers")); dialog.exec(); if ( dialog.changedEntries().count() ) showImages(); }); ui.kcolorcombo->setColor(wallpaper.color); ui.kurlrequester->setUrl(QUrl::fromLocalFile(wallpaper.fileName)); connect(ui.kcolorcombo, &KColorCombo::activated, [this](const QColor &col) { wallpaper.color = col; emit changed(); }); connect(ui.kurlrequester, &KUrlRequester::urlSelected, [this](const QUrl &url) { wallpaper.fileName = url.toLocalFile(); emit changed(); }); // older compiler can't use this //connect(ui.kurlrequester, QOverload::of(&KUrlRequester::returnPressed), // [this](const QString &text) { wallpaper.fileName = text; emit changed(); }); connect(ui.kurlrequester, SIGNAL(returnPressed(QString)), this, SLOT(returnPressed(QString))); if ( wallpaper.mode == "Scaled" ) ui.scaledIgnoreRatioButton->setChecked(true); else if ( wallpaper.mode == "ScaledKeepRatio" ) ui.scaledKeepRatioButton->setChecked(true); else if ( wallpaper.mode == "ScaledKeepRatioExpand" ) ui.scaledKeepRatioClipButton->setChecked(true); else ui.origSizeButton->setChecked(true); buttonGroup.addButton(ui.origSizeButton); buttonGroup.addButton(ui.scaledIgnoreRatioButton); buttonGroup.addButton(ui.scaledKeepRatioButton); buttonGroup.addButton(ui.scaledKeepRatioClipButton); connect(&buttonGroup, SIGNAL(buttonClicked(QAbstractButton *)), this, SLOT(buttonClicked(QAbstractButton *))); } //-------------------------------------------------------------------------------- void ConfigureDesktopDialog::returnPressed(const QString &text) { wallpaper.fileName = text; emit changed(); } //-------------------------------------------------------------------------------- void ConfigureDesktopDialog::buttonClicked(QAbstractButton *button) { if ( button == ui.origSizeButton ) wallpaper.mode = ""; else if ( button == ui.scaledIgnoreRatioButton ) wallpaper.mode = "Scaled"; else if ( button == ui.scaledKeepRatioButton ) wallpaper.mode = "ScaledKeepRatio"; else if ( button == ui.scaledKeepRatioClipButton ) wallpaper.mode = "ScaledKeepRatioExpand"; emit changed(); } //-------------------------------------------------------------------------------- void ConfigureDesktopDialog::showImages() { // create filter (list of patterns) for image files we can read QMimeDatabase db; QStringList filterList; foreach (const QByteArray &type, QImageReader::supportedMimeTypes()) { QMimeType mime(db.mimeTypeForName(QString::fromLatin1(type))); if ( mime.isValid() ) { foreach (const QString &pattern, mime.globPatterns()) filterList << pattern; } } ui.iconView->clear(); QStringList dirNames = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "wallpapers", QStandardPaths::LocateDirectory); const QString geometryString = QString("%1x%2") .arg(QApplication::desktop()->width()) .arg(QApplication::desktop()->height()); for (const QString &dirName : dirNames) { // check for file directly in this folder QStringList fileNames = QDir(dirName).entryList(filterList, QDir::Files | QDir::Readable); for (const QString &fileName : fileNames) { QPixmap pixmap(dirName + '/' + fileName); QListWidgetItem *item = new QListWidgetItem(pixmap, fileName, ui.iconView); item->setData(Qt::UserRole, dirName + '/' + fileName); item->setToolTip(QString("%1 (%2x%3)").arg(fileName).arg(pixmap.width()).arg(pixmap.height())); } // check for files in the special subdirs for (const QString &subdir : QDir(dirName).entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable)) { QDir dir(dirName + '/' + subdir + "/contents/images"); QString chosenFilePath; for (const QString &fileName : dir.entryList(filterList, QDir::Files | QDir::Readable)) { chosenFilePath = dir.absoluteFilePath(fileName); if ( fileName.startsWith(geometryString) ) break; // just take one } if ( !chosenFilePath.isEmpty() ) { QPixmap pixmap(chosenFilePath); QListWidgetItem *item = new QListWidgetItem(pixmap, subdir, ui.iconView); item->setData(Qt::UserRole, chosenFilePath); item->setToolTip(QString("%1 (%2x%3)").arg(subdir).arg(pixmap.width()).arg(pixmap.height())); } } } } //-------------------------------------------------------------------------------- diff --git a/ConfigureDesktopDialog.hxx b/ConfigureDesktopDialog.hxx index b763599..0c853ff 100644 --- a/ConfigureDesktopDialog.hxx +++ b/ConfigureDesktopDialog.hxx @@ -1,54 +1,55 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _ConfigureDesktopDialog_H_ #define _ConfigureDesktopDialog_H_ #include #include #include #include class ConfigureDesktopDialog : public QDialog { Q_OBJECT public: ConfigureDesktopDialog(QWidget *parent, const DesktopWidget::Wallpaper &wp); const DesktopWidget::Wallpaper &getWallpaper() const { return wallpaper; } Q_SIGNALS: void changed(); private Q_SLOTS: void returnPressed(const QString &text); void buttonClicked(QAbstractButton *button); private: void showImages(); private: Ui::ConfigureDesktopDialog ui; QButtonGroup buttonGroup; DesktopWidget::Wallpaper wallpaper; }; #endif diff --git a/DBusTypes.cxx b/DBusTypes.cxx index 1b9e698..c64a23c 100644 --- a/DBusTypes.cxx +++ b/DBusTypes.cxx @@ -1,138 +1,139 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include //-------------------------------------------------------------------------------- const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageStruct &pixmap) { pixmap = QPixmap(); if ( argument.currentType() == QDBusArgument::StructureType ) { qint32 w = 0, h = 0; QByteArray data; argument.beginStructure(); argument >> w; argument >> h; argument >> data; argument.endStructure(); // convert data to QPixmap if ( w && h && !data.isEmpty() ) { if ( QSysInfo::ByteOrder == QSysInfo::LittleEndian ) { quint32 *uintBuf = reinterpret_cast(data.data()); for (uint i = 0; i < data.size() / sizeof(quint32); ++i) { *uintBuf = qToBigEndian(*uintBuf); ++uintBuf; } } pixmap = QPixmap::fromImage(QImage(reinterpret_cast(data.constData()), w, h, QImage::Format_ARGB32)); } } return argument; } //-------------------------------------------------------------------------------- const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageVector &icon) { icon = QIcon(); if ( argument.currentType() == QDBusArgument::ArrayType ) { argument.beginArray(); while ( !argument.atEnd() ) { KDbusImageStruct pixmap; argument >> pixmap; if ( !pixmap.isNull() ) icon.addPixmap(pixmap); } argument.endArray(); } return argument; } //-------------------------------------------------------------------------------- const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusToolTipStruct &tip) { tip = KDbusToolTipStruct(); if ( argument.currentType() == QDBusArgument::StructureType ) { argument.beginStructure(); argument >> tip.icon; argument >> tip.image; argument >> tip.title; argument >> tip.subTitle; argument.endStructure(); } return argument; } //-------------------------------------------------------------------------------- // add "dummy" operators we never need since we'll never send icons, but qDBusRegisterMetaType needs them // But we must correctly create the structure information QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageStruct &pixmap) { argument.beginStructure(); argument << qint32(pixmap.width()); argument << qint32(pixmap.height()); argument << QByteArray(); argument.endStructure(); return argument; } //-------------------------------------------------------------------------------- QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageVector &) { argument.beginArray(qMetaTypeId()); argument.endArray(); return argument; } //-------------------------------------------------------------------------------- QDBusArgument &operator<<(QDBusArgument &argument, const KDbusToolTipStruct &tip) { argument.beginStructure(); argument << tip.icon; argument << tip.image; argument << tip.title; argument << tip.subTitle; argument.endStructure(); return argument; } //-------------------------------------------------------------------------------- diff --git a/DBusTypes.hxx b/DBusTypes.hxx index 61e9d2d..aae6092 100644 --- a/DBusTypes.hxx +++ b/DBusTypes.hxx @@ -1,51 +1,52 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef DBusTypes_h #define DBusTypes_h // define types used in kf5_org.kde.StatusNotifierItem.xml #include #include #include #include #include typedef QPixmap KDbusImageStruct; typedef QIcon KDbusImageVector; struct KDbusToolTipStruct { QString icon; KDbusImageVector image; QString title; QString subTitle; }; Q_DECLARE_METATYPE(KDbusToolTipStruct) const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageStruct &pixmap); const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusImageVector &icon); const QDBusArgument &operator>>(const QDBusArgument &argument, KDbusToolTipStruct &tip); QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageStruct &); QDBusArgument &operator<<(QDBusArgument &argument, const KDbusImageVector &); QDBusArgument &operator<<(QDBusArgument &argument, const KDbusToolTipStruct &); #endif diff --git a/DesktopApplet.cxx b/DesktopApplet.cxx index ca09043..e477e73 100644 --- a/DesktopApplet.cxx +++ b/DesktopApplet.cxx @@ -1,154 +1,155 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- DesktopApplet::DesktopApplet(QWidget *parent, const QString &theId, bool addConfigureAction) : QFrame(parent), id(theId) { setFrameShape(QFrame::NoFrame); setContextMenuPolicy(Qt::ActionsContextMenu); if ( addConfigureAction ) { QAction *action = new QAction(this); action->setText(i18n("Configure...")); action->setIcon(QIcon::fromTheme("configure")); addAction(action); connect(action, &QAction::triggered, this, &DesktopApplet::configure); } QAction *action = new QAction(this); action->setIcon(QIcon::fromTheme("preferences-system-windows-move")); action->setText(i18n("Change Size && Position")); addAction(action); connect(action, &QAction::triggered, this, &DesktopApplet::startGeometryChange); action = new QAction(this); action->setIcon(QIcon::fromTheme("window-close")); action->setText(i18n("Remove this Applet")); addAction(action); connect(action, &QAction::triggered, this, &DesktopApplet::removeThisApplet); buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); buttons->adjustSize(); buttons->hide(); connect(buttons, &QDialogButtonBox::clicked, this, &DesktopApplet::finishGeometryChange); } //-------------------------------------------------------------------------------- void DesktopApplet::loadConfig() { KConfig config; KConfigGroup group = config.group(id); setGeometry(group.readEntry("rect", QRect(QPoint(30, 30), sizeHint()))); onDesktop = group.readEntry("onDesktop", int(NET::OnAllDesktops)); QColor textCol = group.readEntry("textCol", QColor(Qt::white)); QColor backCol = group.readEntry("backCol", QColor(32, 56, 92, 190)); QPalette pal = palette(); pal.setColor(foregroundRole(), textCol); pal.setColor(backgroundRole(), backCol); setPalette(pal); } //-------------------------------------------------------------------------------- void DesktopApplet::saveConfig() { KConfig config; KConfigGroup group = config.group(id); group.writeEntry("rect", geometry()); group.writeEntry("onDesktop", onDesktop); group.writeEntry("textCol", palette().color(foregroundRole())); group.writeEntry("backCol", palette().color(backgroundRole())); } //-------------------------------------------------------------------------------- void DesktopApplet::startGeometryChange() { buttons->raise(); buttons->show(); oldRect = geometry(); setWindowFlags(Qt::Window); setWindowTitle(i18n("%1: Change Size & Position", id)); if ( onDesktop == NET::OnAllDesktops ) KWindowSystem::setOnAllDesktops(winId(), true); show(); setGeometry(oldRect); } //-------------------------------------------------------------------------------- void DesktopApplet::finishGeometryChange(QAbstractButton *clicked) { KWindowInfo info(winId(), NET::WMDesktop); if ( info.valid() ) onDesktop = info.desktop(); buttons->hide(); QRect rect = geometry(); setWindowFlags(Qt::Widget); show(); if ( buttons->buttonRole(clicked) == QDialogButtonBox::AcceptRole ) { setGeometry(rect); KConfig config; KConfigGroup group = config.group(id); group.writeEntry("rect", rect); group.writeEntry("onDesktop", onDesktop); } else { setGeometry(oldRect); } } //-------------------------------------------------------------------------------- void DesktopApplet::removeThisApplet() { if ( QMessageBox::question(this, i18n("Remove Applet"), i18n("Really remove this applet?")) == QMessageBox::Yes ) { KConfig config; config.deleteGroup(id); emit removeThis(this); } } //-------------------------------------------------------------------------------- diff --git a/DesktopApplet.hxx b/DesktopApplet.hxx index 8c6c081..853ba5d 100644 --- a/DesktopApplet.hxx +++ b/DesktopApplet.hxx @@ -1,62 +1,63 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _DesktopApplet_H_ #define _DesktopApplet_H_ #include #include #include class DesktopApplet : public QFrame { Q_OBJECT public: DesktopApplet(QWidget *parent, const QString &theId, bool addConfigureAction = true); virtual void loadConfig(); bool isConfiguring() const { return buttons->isVisible(); } const QString &getId() const { return id; } int isOnDesktop(int d) const { return (onDesktop == NET::OnAllDesktops) || (onDesktop == d); } public Q_SLOTS: virtual void configure() { } virtual void saveConfig(); Q_SIGNALS: void removeThis(DesktopApplet *); protected: QString id; private Q_SLOTS: void startGeometryChange(); void finishGeometryChange(QAbstractButton *clicked); void removeThisApplet(); private: QDialogButtonBox *buttons = nullptr; QRect oldRect; int onDesktop = NET::OnAllDesktops; }; #endif diff --git a/DesktopPanel.cxx b/DesktopPanel.cxx index 4e58ba3..1a90df9 100644 --- a/DesktopPanel.cxx +++ b/DesktopPanel.cxx @@ -1,117 +1,118 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- DesktopPanel::DesktopPanel(QWidget *parent) : QFrame(parent, Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus) { setAttribute(Qt::WA_AlwaysShowToolTips); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); setWindowIcon(QIcon::fromTheme("liquidshell")); KWindowSystem::setState(winId(), NET::KeepAbove); KWindowSystem::setOnAllDesktops(winId(), true); KWindowSystem::setType(winId(), NET::Dock); setFrameShape(QFrame::StyledPanel); QHBoxLayout *hboxLayout = new QHBoxLayout(this); hboxLayout->setContentsMargins(QMargins()); hboxLayout->setSpacing(2); hboxLayout->addWidget(new StartMenu(this)); hboxLayout->addWidget(new QuickLaunch(this)); hboxLayout->addWidget(new AppMenu(this)); hboxLayout->addWidget(new Pager(this)); hboxLayout->addWidget(new WindowList(this)); hboxLayout->addWidget(new TaskBar(this)); hboxLayout->addWidget(new LockLogout(this)); hboxLayout->addWidget(new SysLoad(this)); hboxLayout->addWidget(new SysTray(this)); hboxLayout->addWidget(new ClockWidget(this)); // to get notified about num-of-rows changed updateRowCount(); QDBusConnection dbus = QDBusConnection::sessionBus(); dbus.connect(QString(), "/KWin", "org.kde.KWin", "reloadConfig", this, SLOT(updateRowCount())); connect(KWindowSystem::self(), &KWindowSystem::numberOfDesktopsChanged, this, &DesktopPanel::updateRowCount); } //-------------------------------------------------------------------------------- void DesktopPanel::updateRowCount() { NETRootInfo ri(QX11Info::connection(), 0, NET::WM2DesktopLayout); int newRows = std::max(1, ri.desktopLayoutColumnsRows().height()); if ( newRows != rows ) { rows = newRows; emit rowsChanged(rows); } } //-------------------------------------------------------------------------------- bool DesktopPanel::event(QEvent *ev) { bool ret = QFrame::event(ev); if ( ev->type() == QEvent::LayoutRequest ) { if ( sizeHint().height() != height() ) emit resized(); } return ret; } //-------------------------------------------------------------------------------- void DesktopPanel::resizeEvent(QResizeEvent *event) { Q_UNUSED(event); emit resized(); } //-------------------------------------------------------------------------------- diff --git a/DesktopPanel.hxx b/DesktopPanel.hxx index 9d52df3..f80e6e4 100644 --- a/DesktopPanel.hxx +++ b/DesktopPanel.hxx @@ -1,49 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _DesktopPanel_H_ #define _DesktopPanel_H_ #include class DesktopPanel : public QFrame { Q_OBJECT public: DesktopPanel(QWidget *parent); int getRows() const { return rows; } Q_SIGNALS: void resized(); void rowsChanged(int rows); protected: bool event(QEvent *event) override; void resizeEvent(QResizeEvent *event) override; private Q_SLOTS: void updateRowCount(); private: int rows = 2; }; #endif diff --git a/DesktopWidget.cxx b/DesktopWidget.cxx index 7f8c741..4a1d6c0 100644 --- a/DesktopWidget.cxx +++ b/DesktopWidget.cxx @@ -1,395 +1,396 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017, 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- int DesktopWidget::appletNum = 0; //-------------------------------------------------------------------------------- DesktopWidget::DesktopWidget() : QWidget(nullptr, Qt::WindowDoesNotAcceptFocus) { setAttribute(Qt::WA_AlwaysShowToolTips); setAutoFillBackground(true); setFixedSize(QApplication::desktop()->size()); setWindowIcon(QIcon::fromTheme("liquidshell")); KWindowSystem::setType(winId(), NET::Desktop); panel = new DesktopPanel(nullptr); panel->show(); connect(panel, &DesktopPanel::resized, this, &DesktopWidget::placePanel); placePanel(); connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, &DesktopWidget::desktopChanged); connect(KWindowSystem::self(), &KWindowSystem::numberOfDesktopsChanged, this, &DesktopWidget::loadSettings); setContextMenuPolicy(Qt::ActionsContextMenu); QAction *action = new QAction(QIcon::fromTheme("configure"), i18n("Configure Wallpaper..."), this); connect(action, &QAction::triggered, this, &DesktopWidget::configureWallpaper); addAction(action); action = new QAction(i18n("Add Applet"), this); addAction(action); QMenu *menu = new QMenu; action->setMenu(menu); action = menu->addAction(QIcon::fromTheme("weather-clouds"), i18n("Weather")); connect(action, &QAction::triggered, [this]() { addApplet("Weather"); }); action = menu->addAction(QIcon::fromTheme("drive-harddisk"), i18n("Disk Usage")); connect(action, &QAction::triggered, [this]() { addApplet("DiskUsage"); }); action = menu->addAction(QIcon::fromTheme("image-x-generix"), i18n("Picture Frame")); connect(action, &QAction::triggered, [this]() { addApplet("PictureFrame"); }); action = new QAction(QIcon::fromTheme("preferences-desktop-display"), i18n("Configure Display..."), this); connect(action, &QAction::triggered, this, &DesktopWidget::configureDisplay); addAction(action); action = new QAction(QIcon::fromTheme("system-run"), i18n("Run Command..."), this); connect(action, &QAction::triggered, []() { QDBusConnection::sessionBus().send( QDBusMessage::createMethodCall("org.kde.krunner", "/App", "org.kde.krunner.App", "display")); }); addAction(action); action = new QAction(i18n("Help"), this); KHelpMenu *helpMenu = new KHelpMenu(this); helpMenu->action(KHelpMenu::menuHelpContents)->setVisible(false); // no handbook helpMenu->action(KHelpMenu::menuWhatsThis)->setVisible(false); action->setMenu(helpMenu->menu()); addAction(action); // restore applets KConfig config; KConfigGroup group = config.group("DesktopApplets"); QStringList appletNames = group.readEntry("applets", QStringList()); for (const QString &appletName : appletNames) { DesktopApplet *applet = nullptr; if ( appletName.startsWith("Weather_") ) applet = new WeatherApplet(this, appletName); else if ( appletName.startsWith("DiskUsage_") ) applet = new DiskUsageApplet(this, appletName); else if ( appletName.startsWith("PictureFrame_") ) applet = new PictureFrameApplet(this, appletName); if ( applet ) { int num = appletName.mid(appletName.indexOf('_') + 1).toInt(); if ( num > appletNum ) appletNum = num; applet->loadConfig(); applets.append(applet); connect(applet, &DesktopApplet::removeThis, this, &DesktopWidget::removeApplet); } } loadSettings(); connect(QApplication::desktop(), &QDesktopWidget::primaryScreenChanged, this, &DesktopWidget::placePanel); connect(QApplication::desktop(), &QDesktopWidget::screenCountChanged, [this]() { setFixedSize(QApplication::desktop()->size()); desktopChanged(); }); connect(QApplication::desktop(), &QDesktopWidget::resized, [this]() { setFixedSize(QApplication::desktop()->size()); placePanel(); desktopChanged(); }); new OnScreenVolume(this); } //-------------------------------------------------------------------------------- void DesktopWidget::loadSettings() { // simple check for default files matching current desktop size QStringList dirNames = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "wallpapers", QStandardPaths::LocateDirectory); QStringList defaultFiles; const QString geometryString = QString("%1x%2").arg(width()).arg(height()); for (const QString &dirName : dirNames) { for (const QString &subdir : QDir(dirName).entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::Readable)) { QDir dir(dirName + '/' + subdir + "/contents/images"); for (const QString &fileName : dir.entryList(QDir::Files | QDir::Readable)) { if ( fileName.startsWith(geometryString) ) defaultFiles.append(dir.absoluteFilePath(fileName)); } } } KConfig config; for (int i = wallpapers.count() + 1; i <= KWindowSystem::numberOfDesktops(); i++) { KConfigGroup group = config.group(QString("Desktop_%1").arg(i)); Wallpaper wallpaper; wallpaper.color = group.readEntry("color", QColor(Qt::black)); wallpaper.mode = group.readEntry("wallpaperMode", QString()); int idx = defaultFiles.count() ? ((i - 1) % defaultFiles.count()) : -1; wallpaper.fileName = group.readEntry("wallpaper", (idx != -1) ? defaultFiles[idx] : QString()); wallpapers.append(wallpaper); } wallpapers.resize(KWindowSystem::numberOfDesktops()); // if we had more before, reduce size wallpapers.squeeze(); desktopChanged(); } //-------------------------------------------------------------------------------- void DesktopWidget::configureWallpaper() { if ( dialog ) // already open, do nothing return; bool showingDesktop = KWindowSystem::showingDesktop(); KWindowSystem::setShowingDesktop(true); int desktopNum = currentDesktop; Wallpaper origWallpaper = wallpapers[desktopNum]; dialog = new ConfigureDesktopDialog(nullptr, wallpapers[desktopNum]); connect(dialog, &ConfigureDesktopDialog::changed, [=]() { wallpapers[desktopNum] = dialog->getWallpaper(); desktopChanged(); }); connect(dialog, &QDialog::finished, [=](int result) { if ( result == QDialog::Rejected ) { wallpapers[desktopNum] = origWallpaper; desktopChanged(); } else // store in config file { const Wallpaper &wallpaper = wallpapers[desktopNum]; KConfig config; KConfigGroup group = config.group(QString("Desktop_%1").arg(desktopNum + 1)); group.writeEntry("color", wallpaper.color); group.writeEntry("wallpaper", wallpaper.fileName); group.writeEntry("wallpaperMode", wallpaper.mode); } KWindowSystem::setShowingDesktop(showingDesktop); // restore dialog->deleteLater(); dialog = nullptr; }); dialog->show(); // not modal } //-------------------------------------------------------------------------------- void DesktopWidget::configureDisplay() { KCMultiDialog *dialog = new KCMultiDialog(this); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->addModule("kcm_kscreen"); dialog->adjustSize(); dialog->setWindowTitle(i18n("Configure Display")); dialog->show(); // not modal } //-------------------------------------------------------------------------------- void DesktopWidget::placePanel() { int panelHeight = qMin(panel->sizeHint().height(), panel->height()); QRect r = QApplication::desktop()->screenGeometry(); panel->setGeometry(r.x(), r.y() + r.height() - panelHeight, r.width(), panelHeight); KWindowSystem::setStrut(panel->winId(), 0, 0, 0, panelHeight); } //-------------------------------------------------------------------------------- void DesktopWidget::desktopChanged() { // free memory in previous shown desktop if ( (currentDesktop >= 0) && (currentDesktop < wallpapers.count()) ) wallpapers[currentDesktop].pixmaps.clear(); currentDesktop = KWindowSystem::currentDesktop() - 1; // num to index if ( currentDesktop >= wallpapers.count() ) return; Wallpaper &wallpaper = wallpapers[currentDesktop]; if ( wallpaper.color.isValid() ) { QPalette pal = palette(); pal.setColor(backgroundRole(), wallpaper.color); setPalette(pal); } if ( !wallpaper.fileName.isEmpty() ) { QPixmap pixmap(wallpaper.fileName); if ( !pixmap.isNull() ) { for (int i = 0; i < QApplication::desktop()->screenCount(); i++) { QRect r = QApplication::desktop()->screenGeometry(i); QPixmap scaledPixmap = pixmap; if ( wallpaper.mode == "Scaled" ) scaledPixmap = pixmap.scaled(r.size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); else if ( wallpaper.mode == "ScaledKeepRatio" ) scaledPixmap = pixmap.scaled(r.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); else if ( wallpaper.mode == "ScaledKeepRatioExpand" ) scaledPixmap = pixmap.scaled(r.size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); wallpaper.pixmaps.append(scaledPixmap); } } } update(); // show applets for new desktop for (DesktopApplet *applet : applets) { if ( !applet->isConfiguring() ) applet->setVisible(applet->isOnDesktop(currentDesktop + 1)); } } //-------------------------------------------------------------------------------- void DesktopWidget::paintEvent(QPaintEvent *event) { Q_UNUSED(event) if ( currentDesktop < wallpapers.count() ) { Wallpaper &wallpaper = wallpapers[currentDesktop]; QPainter painter(this); for (int i = 0; i < QApplication::desktop()->screenCount(); i++) { if ( i < wallpaper.pixmaps.count() ) { QRect r = QApplication::desktop()->screenGeometry(i); painter.setClipRect(r); painter.drawPixmap(r.x() + (r.width() - wallpaper.pixmaps[i].width()) / 2, r.y() + (r.height() - wallpaper.pixmaps[i].height()) / 2, wallpaper.pixmaps[i]); } } } } //-------------------------------------------------------------------------------- void DesktopWidget::addApplet(const QString &type) { DesktopApplet *applet = nullptr; if ( type == "Weather" ) { applet = new WeatherApplet(this, QString("%1_%2").arg(type).arg(++appletNum)); } else if ( type == "DiskUsage" ) { applet = new DiskUsageApplet(this, QString("%1_%2").arg(type).arg(++appletNum)); } else if ( type == "PictureFrame" ) { applet = new PictureFrameApplet(this, QString("%1_%2").arg(type).arg(++appletNum)); } if ( applet ) { applets.append(applet); applet->loadConfig(); // defaults + size applet->move(QCursor::pos()); applet->saveConfig(); applet->show(); connect(applet, &DesktopApplet::removeThis, this, &DesktopWidget::removeApplet); } saveAppletsList(); } //-------------------------------------------------------------------------------- void DesktopWidget::removeApplet(DesktopApplet *applet) { applets.removeOne(applet); applet->deleteLater(); saveAppletsList(); } //-------------------------------------------------------------------------------- void DesktopWidget::saveAppletsList() { KConfig config; KConfigGroup group = config.group("DesktopApplets"); QStringList appletNames; for (DesktopApplet *applet : applets) appletNames.append(applet->getId()); group.writeEntry("applets", appletNames); } //-------------------------------------------------------------------------------- diff --git a/DesktopWidget.hxx b/DesktopWidget.hxx index 98b17f7..84cf505 100644 --- a/DesktopWidget.hxx +++ b/DesktopWidget.hxx @@ -1,70 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef DESKTOP_WIDGET_H #define DESKTOP_WIDGET_H #include #include #include class DesktopPanel; class DesktopApplet; class ConfigureDesktopDialog; class DesktopWidget : public QWidget { Q_OBJECT public: DesktopWidget(); struct Wallpaper { QString fileName, mode; QColor color; QVector pixmaps; // per screen }; protected: virtual void paintEvent(QPaintEvent *event); private Q_SLOTS: void loadSettings(); void placePanel(); void desktopChanged(); void configureWallpaper(); void configureDisplay(); void addApplet(const QString &type); void removeApplet(DesktopApplet *applet); private: void saveAppletsList(); private: DesktopPanel *panel = nullptr; ConfigureDesktopDialog *dialog = nullptr; QVector wallpapers; int currentDesktop = -1; QVector applets; static int appletNum; }; #endif diff --git a/DeviceList.cxx b/DeviceList.cxx index bccf817..e9a249f 100644 --- a/DeviceList.cxx +++ b/DeviceList.cxx @@ -1,567 +1,568 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017, 2019 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- DeviceItem::DeviceItem(Solid::Device dev, const QVector &deviceActions) : device(dev) { setFrameShape(QFrame::StyledPanel); QVBoxLayout *vbox = new QVBoxLayout(this); QHBoxLayout *hbox = new QHBoxLayout; vbox->addLayout(hbox); QLabel *iconLabel = new QLabel; iconLabel->setPixmap(QIcon::fromTheme(device.icon()).pixmap(32)); iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); newFlagLabel = new QLabel; newFlagLabel->setPixmap(QIcon::fromTheme("emblem-important").pixmap(16)); newFlagLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); newFlagLabel->hide(); textLabel = new QLabel; textLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); hbox->addWidget(iconLabel, 0, Qt::AlignLeft | Qt::AlignVCenter); hbox->addWidget(newFlagLabel, 0, Qt::AlignCenter); hbox->addWidget(textLabel, 0, Qt::AlignVCenter); Solid::StorageAccess *storage = device.as(); if ( storage ) { connect(storage, &Solid::StorageAccess::teardownDone, this, &DeviceItem::teardownDone); connect(storage, &Solid::StorageAccess::setupDone, this, &DeviceItem::setupDone); mountBusyTimer.setInterval(500); connect(&mountBusyTimer, &QTimer::timeout, this, [this]() { mountButton->setVisible(!mountButton->isVisible()); }); mountButton = new QToolButton; mountButton->setIconSize(QSize(32, 32)); connect(mountButton, &QToolButton::clicked, mountButton, [this]() { statusLabel->hide(); mountButton->setEnabled(false); mountBusyTimer.start(); Solid::StorageAccess *storage = device.as(); if ( storage->isAccessible() ) // mounted -> unmount it storage->teardown(); else storage->setup(); } ); hbox->addWidget(mountButton); } statusLabel = new QLabel; statusLabel->setWordWrap(true); statusLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); statusLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); vbox->addWidget(statusLabel); statusLabel->hide(); statusTimer.setSingleShot(true); statusTimer.setInterval(60000); connect(&statusTimer, &QTimer::timeout, statusLabel, &QLabel::hide); fillData(); // append actions for (const DeviceAction &action : deviceActions) { QHBoxLayout *hbox = new QHBoxLayout; hbox->addSpacing(iconLabel->sizeHint().width()); IconButton *button = new IconButton; button->setIcon(QIcon::fromTheme(action.action.icon())); button->setText(action.action.text() + " (" + QFileInfo(action.path).baseName() + ")"); connect(button, &IconButton::clicked, button, [action, this]() { QString command = action.action.exec(); Solid::StorageAccess *storage = device.as(); if ( storage ) { if ( !storage->isAccessible() ) { statusLabel->hide(); storage->setup(); return; } command.replace("%f", storage->filePath()); } if ( device.is() ) command.replace("%d", device.as()->device()); command.replace("%i", device.udi()); KRun::runCommand(command, this); window()->hide(); } ); hbox->addWidget(button); vbox->addLayout(hbox); } } //-------------------------------------------------------------------------------- DeviceItem::DeviceItem(const KdeConnect::Device &dev) { setFrameShape(QFrame::StyledPanel); QVBoxLayout *vbox = new QVBoxLayout(this); QHBoxLayout *hbox = new QHBoxLayout; vbox->addLayout(hbox); QLabel *iconLabel = new QLabel; iconLabel->setPixmap(dev->icon.pixmap(32)); iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); newFlagLabel = new QLabel; newFlagLabel->setPixmap(QIcon::fromTheme("emblem-important").pixmap(16)); newFlagLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); newFlagLabel->hide(); textLabel = new QLabel; textLabel->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); hbox->addWidget(iconLabel, 0, Qt::AlignLeft | Qt::AlignVCenter); hbox->addWidget(newFlagLabel, 0, Qt::AlignCenter); hbox->addWidget(textLabel, 0, Qt::AlignVCenter); statusLabel = new QLabel; hbox->addWidget(statusLabel, 0, Qt::AlignVCenter); QLabel *chargeIcon = new QLabel; hbox->addWidget(chargeIcon, 0, Qt::AlignVCenter); if ( dev->charge >= 0 ) { statusLabel->setText(QString::number(dev->charge) + '%'); chargeIcon->setPixmap(dev->chargeIcon.pixmap(22)); } connect(dev.data(), &KdeConnectDevice::changed, this, [this, chargeIcon, dev]() { textLabel->setText(dev->name); if ( dev->charge >= 0 ) { statusLabel->setText(QString::number(dev->charge) + '%'); chargeIcon->setPixmap(dev->chargeIcon.pixmap(22)); } }); if ( dev->plugins.contains("kdeconnect_findmyphone") ) { QToolButton *ringButton = new QToolButton; ringButton->setIcon(QIcon::fromTheme("preferences-desktop-notification-bell")); connect(ringButton, &QToolButton::clicked, dev.data(), [dev]() { dev->ringPhone(); }); hbox->addWidget(ringButton, 0, Qt::AlignVCenter); } QToolButton *configure = new QToolButton; configure->setIcon(QIcon::fromTheme("configure")); connect(configure, &QToolButton::clicked, configure, [this]() { if ( !dialog ) { dialog = new KCMultiDialog(nullptr); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->addModule("kcm_kdeconnect"); dialog->setWindowTitle(i18n("KDE Connect")); dialog->adjustSize(); } dialog->show(); }); hbox->addWidget(configure, 0, Qt::AlignVCenter); textLabel->setText(dev->name); if ( dev->plugins.contains("kdeconnect_sftp") ) { QHBoxLayout *hbox = new QHBoxLayout; hbox->addSpacing(iconLabel->sizeHint().width()); IconButton *button = new IconButton; button->setIcon(QIcon::fromTheme("system-file-manager")); button->setText(i18n("Open with File Manager")); connect(button, &IconButton::clicked, [dev]() { new KRun(QUrl(QLatin1String("kdeconnect://") + dev->id), nullptr); }); hbox->addWidget(button); vbox->addLayout(hbox); } } //-------------------------------------------------------------------------------- void DeviceItem::markAsNew() { newFlagLabel->show(); QTimer::singleShot(5000, newFlagLabel, &QLabel::hide); } //-------------------------------------------------------------------------------- void DeviceItem::fillData() { Solid::StorageAccess *storage = device.as(); QString text = device.description(); if ( !device.product().isEmpty() && (device.product() != text) ) text += " (" + device.product() + ")"; else if ( !device.vendor().isEmpty() && (device.vendor() != text) ) text += " (" + device.vendor() + ")"; Solid::StorageVolume *volume = device.as(); if ( volume ) text += " " + KIO::convertSize(volume->size()); if ( storage && !storage->filePath().isEmpty() ) text += '\n' + storage->filePath(); textLabel->setText(text); if ( mountButton ) { if ( storage && storage->isAccessible() ) mountButton->setIcon(QIcon::fromTheme("media-eject")); else { if ( device.emblems().count() ) mountButton->setIcon(QIcon::fromTheme(device.emblems()[0])); else mountButton->setIcon(QIcon::fromTheme("emblem-unmounted")); } if ( storage ) { mountButton->setToolTip(storage->isAccessible() ? i18n("Device is mounted.\nClick to unmount/eject") : i18n("Device is unmounted.\nClick to mount")); } } } //-------------------------------------------------------------------------------- QString DeviceItem::errorToString(Solid::ErrorType error) { switch ( error ) { case Solid::UnauthorizedOperation: return i18n("Unauthorized Operation"); case Solid::DeviceBusy: return i18n("Device Busy"); case Solid::OperationFailed: return i18n("Operation Failed"); case Solid::UserCanceled: return i18n("User Canceled"); case Solid::InvalidOption: return i18n("Invalid Option"); case Solid::MissingDriver: return i18n("Missing Driver"); default: return QString(); } } //-------------------------------------------------------------------------------- void DeviceItem::mountDone(Action action, Solid::ErrorType error, QVariant errorData, const QString &udi) { Q_UNUSED(udi) mountBusyTimer.stop(); mountButton->setEnabled(true); mountButton->setVisible(true); if ( error == Solid::NoError ) { fillData(); if ( action == Unmount ) { statusLabel->setText(i18n("The device can now be safely removed")); statusLabel->show(); statusTimer.start(); } } else { QString text = (action == Mount) ? i18n("Mount failed:") : i18n("Unmount failed:"); statusLabel->setText("" + text + "" + errorToString(error) + "
" + errorData.toString()); statusLabel->show(); statusTimer.start(); } } //-------------------------------------------------------------------------------- void DeviceItem::teardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi) { mountDone(Unmount, error, errorData, udi); } //-------------------------------------------------------------------------------- void DeviceItem::setupDone(Solid::ErrorType error, QVariant errorData, const QString &udi) { mountDone(Mount, error, errorData, udi); } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- DeviceList::DeviceList(QWidget *parent) : QScrollArea(parent) { setWindowFlags(windowFlags() | Qt::Tool); setFrameShape(QFrame::StyledPanel); setAttribute(Qt::WA_AlwaysShowToolTips); setWidgetResizable(true); loadActions(); QWidget *widget = new QWidget; vbox = new QVBoxLayout(widget); vbox->setContentsMargins(QMargins()); vbox->addStretch(); vbox->setSizeConstraint(QLayout::SetMinAndMaxSize); setWidget(widget); predicate = Solid::Predicate(Solid::DeviceInterface::StorageAccess); predicate |= Solid::Predicate(Solid::DeviceInterface::StorageDrive); predicate |= Solid::Predicate(Solid::DeviceInterface::StorageVolume); predicate |= Solid::Predicate(Solid::DeviceInterface::OpticalDrive); predicate |= Solid::Predicate(Solid::DeviceInterface::OpticalDisc); predicate |= Solid::Predicate(Solid::DeviceInterface::PortableMediaPlayer); predicate |= Solid::Predicate(Solid::DeviceInterface::Camera); QList devices = Solid::Device::listFromQuery(predicate); for (Solid::Device device : devices) addDevice(device); connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, this, &DeviceList::deviceAdded); connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceRemoved, this, &DeviceList::deviceRemoved); connect(&kdeConnect, &KdeConnect::deviceAdded, this, &DeviceList::kdeConnectDeviceAdded); connect(&kdeConnect, &KdeConnect::deviceRemoved, this, &DeviceList::deviceRemoved); } //-------------------------------------------------------------------------------- QSize DeviceList::sizeHint() const { // avoid horizontal scrollbar when the list is higher than 2/3 of the screen // where a vertical scrollbar will be shown, reducing the available width // leading to also getting a horizontal scrollbar QSize s = widget()->sizeHint() + QSize(2 * frameWidth(), 2 * frameWidth()); s.setWidth(s.width() + verticalScrollBar()->sizeHint().width()); return s; } //-------------------------------------------------------------------------------- void DeviceList::loadActions() { actions.clear(); const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "solid/actions", QStandardPaths::LocateDirectory); for (const QString &dirPath : dirs) { QDir dir(dirPath); for (const QString &file : dir.entryList(QStringList(QLatin1String("*.desktop")), QDir::Files)) { QString path = dir.absoluteFilePath(file); KDesktopFile cfg(path); const QString predicateString = cfg.desktopGroup().readEntry("X-KDE-Solid-Predicate"); QList actionList = KDesktopFileActions::userDefinedServices(path, true); if ( !actionList.isEmpty() && !predicateString.isEmpty() ) actions.append(DeviceAction(path, Solid::Predicate::fromString(predicateString), actionList[0])); } } } //-------------------------------------------------------------------------------- DeviceItem *DeviceList::addDevice(Solid::Device device) { if ( items.contains(device.udi()) ) { //qDebug() << device.udi() << "already known"; return nullptr; } QVector deviceActions; for (const DeviceAction &action : actions) { if ( action.predicate.matches(device) ) deviceActions.append(action); } Solid::StorageVolume *storage = device.as(); if ( !storage ) // storage can at least be mounted; others need some specific actions { if ( deviceActions.isEmpty() ) { //qDebug() << device.udi() << "no action found"; return nullptr; } } else if ( storage->usage() != Solid::StorageVolume::FileSystem ) { //qDebug() << device.udi() << "storage no filesystem"; return nullptr; } // show only removable devices if ( device.is() && !device.as()->isRemovable() ) { //qDebug() << device.udi() << "not Removable"; return nullptr; } // show only removable devices if ( device.parent().is() && !device.parent().as()->isRemovable() ) { //qDebug() << device.parent().udi() << "parent() not Removable"; return nullptr; } DeviceItem *item = new DeviceItem(device, deviceActions); vbox->insertWidget(vbox->count() - 1, item); // insert before stretch items.insert(device.udi(), item); return item; } //-------------------------------------------------------------------------------- void DeviceList::deviceAdded(const QString &dev) { Solid::Device device(dev); if ( !predicate.matches(device) ) return; DeviceItem *item = addDevice(device); // when we added a new device, make sure the DeviceNotifier shows and places this window if ( item ) { item->markAsNew(); item->show(); verticalScrollBar()->setValue(verticalScrollBar()->maximum()); emit deviceWasAdded(); } } //-------------------------------------------------------------------------------- void DeviceList::deviceRemoved(const QString &dev) { if ( items.contains(dev) ) { delete items.take(dev); emit deviceWasRemoved(); } } //-------------------------------------------------------------------------------- void DeviceList::kdeConnectDeviceAdded(const KdeConnect::Device &device) { if ( items.contains(device->id) ) { //qDebug() << device->id << "already known"; return; } DeviceItem *item = new DeviceItem(device); vbox->insertWidget(vbox->count() - 1, item); // insert before stretch items.insert(device->id, item); item->markAsNew(); item->show(); verticalScrollBar()->setValue(verticalScrollBar()->maximum()); // when we added a new device, make sure the DeviceNotifier shows and places this window emit deviceWasAdded(); } //-------------------------------------------------------------------------------- diff --git a/DeviceList.hxx b/DeviceList.hxx index dc37f10..7175636 100644 --- a/DeviceList.hxx +++ b/DeviceList.hxx @@ -1,119 +1,120 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017, 2019 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _DeviceList_H_ #define _DeviceList_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- struct DeviceAction { DeviceAction() { } DeviceAction(const QString &filePath, Solid::Predicate p, KServiceAction a) : path(filePath), predicate(p), action(a) { } QString path; Solid::Predicate predicate; KServiceAction action; }; //-------------------------------------------------------------------------------- class DeviceItem : public QFrame { Q_OBJECT public: DeviceItem(Solid::Device dev, const QVector &deviceActions); DeviceItem(const KdeConnect::Device &dev); void markAsNew(); private: static QString errorToString(Solid::ErrorType error); void fillData(); enum Action { Mount, Unmount }; void mountDone(Action action, Solid::ErrorType error, QVariant errorData, const QString &udi); private Q_SLOTS: void teardownDone(Solid::ErrorType error, QVariant errorData, const QString &udi); void setupDone(Solid::ErrorType error, QVariant errorData, const QString &udi); private: Solid::Device device; QToolButton *mountButton = nullptr; QLabel *textLabel = nullptr, *statusLabel = nullptr, *newFlagLabel = nullptr; QTimer statusTimer, mountBusyTimer; QPointer dialog; }; //-------------------------------------------------------------------------------- class DeviceList : public QScrollArea { Q_OBJECT public: DeviceList(QWidget *parent); bool isEmpty() const { return items.isEmpty(); } QSize sizeHint() const override; Q_SIGNALS: void deviceWasAdded(); void deviceWasRemoved(); private Q_SLOTS: void deviceAdded(const QString &dev); void deviceRemoved(const QString &dev); void kdeConnectDeviceAdded(const KdeConnect::Device &dev); private: void loadActions(); DeviceItem *addDevice(Solid::Device device); private: QVBoxLayout *vbox; QMap items; Solid::Predicate predicate; QVector actions; KdeConnect kdeConnect; }; #endif diff --git a/DeviceNotifier.cxx b/DeviceNotifier.cxx index 6bcdd38..e0c1178 100644 --- a/DeviceNotifier.cxx +++ b/DeviceNotifier.cxx @@ -1,94 +1,95 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017, 2019 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include //-------------------------------------------------------------------------------- DeviceNotifier::DeviceNotifier(QWidget *parent) : SysTrayItem(parent, "device-notifier") { setToolTip(i18n("Device Notifier")); deviceList = new DeviceList(this); deviceList->setWindowTitle(i18n("Device List")); if ( deviceList->isEmpty() ) hide(); connect(deviceList, &DeviceList::deviceWasRemoved, this, &DeviceNotifier::checkDeviceList); connect(deviceList, &DeviceList::deviceWasAdded, [this]() { if ( !deviceList->isVisible() ) timer.start(); // auto-hide showDetailsList(); }); // if the user did not activate the device list window, auto-hide it timer.setInterval(4000); timer.setSingleShot(true); connect(&timer, &QTimer::timeout, deviceList, &DeviceList::hide); deviceList->installEventFilter(this); } //-------------------------------------------------------------------------------- QWidget *DeviceNotifier::getDetailsList() { deviceList->adjustSize(); deviceList->resize(deviceList->size().expandedTo(QSize(300, 100))); setVisible(!deviceList->isEmpty()); return deviceList; } //-------------------------------------------------------------------------------- void DeviceNotifier::checkDeviceList() { if ( deviceList->isEmpty() ) { deviceList->hide(); hide(); } else if ( deviceList->isVisible() ) showDetailsList(); // reposition } //-------------------------------------------------------------------------------- bool DeviceNotifier::eventFilter(QObject *watched, QEvent *event) { Q_UNUSED(watched) if ( event->type() == QEvent::WindowActivate ) timer.stop(); return false; } //-------------------------------------------------------------------------------- diff --git a/DeviceNotifier.hxx b/DeviceNotifier.hxx index dc81001..6076871 100644 --- a/DeviceNotifier.hxx +++ b/DeviceNotifier.hxx @@ -1,46 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _DeviceNotifier_H_ #define _DeviceNotifier_H_ #include #include class DeviceList; class DeviceNotifier : public SysTrayItem { Q_OBJECT public: DeviceNotifier(QWidget *parent); protected: QWidget *getDetailsList() override; bool eventFilter(QObject *watched, QEvent *event) override; private Q_SLOTS: void checkDeviceList(); private: DeviceList *deviceList = nullptr; QTimer timer; }; #endif diff --git a/DiskUsageApplet.cxx b/DiskUsageApplet.cxx index 972b165..238d1ee 100644 --- a/DiskUsageApplet.cxx +++ b/DiskUsageApplet.cxx @@ -1,153 +1,154 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- DiskUsageApplet::DiskUsageApplet(QWidget *parent, const QString &theId) : DesktopApplet(parent, theId) { setAutoFillBackground(true); connect(&timer, &QTimer::timeout, this, &DiskUsageApplet::fill); timer.setInterval(10000); timer.start(); new QGridLayout(this); fill(); } //-------------------------------------------------------------------------------- void DiskUsageApplet::fill() { QGridLayout *grid = static_cast(layout()); for (SizeInfo &info : partitionMap) info.used = false; QList partitions = Solid::Device::listFromType(Solid::DeviceInterface::StorageVolume); int row = grid->rowCount(); for (const Solid::Device &partition : partitions) { const Solid::StorageVolume *volume = partition.as(); if ( !volume || volume->isIgnored() || (volume->usage() != Solid::StorageVolume::FileSystem) ) continue; const Solid::StorageAccess *storage = partition.as(); if ( !storage || !storage->isAccessible() ) continue; QProgressBar *progress; QLabel *sizeLabel; KDiskFreeSpaceInfo info = KDiskFreeSpaceInfo::freeSpaceInfo(storage->filePath()); if ( !partitionMap.contains(info.mountPoint()) ) { progress = new QProgressBar(this); QLabel *label = new QLabel(info.mountPoint(), this); grid->addWidget(label, row, 0); grid->addWidget(progress, row, 1); grid->addWidget(sizeLabel = new QLabel(this), row, 2); row++; SizeInfo sizeInfo; sizeInfo.label = label; sizeInfo.progress = progress; sizeInfo.sizeLabel = sizeLabel; sizeInfo.used = true; partitionMap.insert(storage->filePath(), sizeInfo); // workaround Qt bug label->setPalette(palette()); sizeLabel->setPalette(palette()); } else { partitionMap[info.mountPoint()].used = true; SizeInfo sizeInfo = partitionMap[info.mountPoint()]; progress = sizeInfo.progress; sizeLabel = sizeInfo.sizeLabel; } progress->setValue(std::round(double(info.used()) / double(info.size()) * 100.0)); sizeLabel->setText(i18n("%1 free / %2", KIO::convertSize(info.available()), KIO::convertSize(info.size()))); } // remove entries which are no longer used QMutableMapIterator iter(partitionMap); while ( iter.hasNext() ) { iter.next(); if ( !iter.value().used ) { delete iter.value().label; delete iter.value().progress; delete iter.value().sizeLabel; iter.remove(); } } } //-------------------------------------------------------------------------------- void DiskUsageApplet::configure() { if ( dialog ) { dialog->raise(); dialog->activateWindow(); return; } dialog = new DiskUsageAppletConfigureDialog(this); dialog->setWindowTitle(i18n("Configure DiskUsage Applet")); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); connect(dialog.data(), &QDialog::accepted, this, &DiskUsageApplet::saveConfig); } //-------------------------------------------------------------------------------- diff --git a/DiskUsageApplet.hxx b/DiskUsageApplet.hxx index e0adae8..fd77b3d 100644 --- a/DiskUsageApplet.hxx +++ b/DiskUsageApplet.hxx @@ -1,59 +1,60 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _DiskUsageApplet_H_ #define _DiskUsageApplet_H_ #include #include #include #include #include class QProgressBar; class QLabel; class DiskUsageApplet : public DesktopApplet { Q_OBJECT public: DiskUsageApplet(QWidget *parent, const QString &theId); public Q_SLOTS: void configure() override; private Q_SLOTS: void fill(); private: QTimer timer; struct SizeInfo { QLabel *label; QProgressBar *progress; QLabel *sizeLabel; bool used; }; QMap partitionMap; QPointer dialog; }; #endif diff --git a/DiskUsageAppletConfigureDialog.cxx b/DiskUsageAppletConfigureDialog.cxx index 1299278..7988196 100644 --- a/DiskUsageAppletConfigureDialog.cxx +++ b/DiskUsageAppletConfigureDialog.cxx @@ -1,45 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include //-------------------------------------------------------------------------------- DiskUsageAppletConfigureDialog::DiskUsageAppletConfigureDialog(DiskUsageApplet *parent) : QDialog(parent), applet(parent) { ui.setupUi(this); ui.textColor->setColor(applet->palette().color(applet->foregroundRole())); ui.backgroundColor->setColor(applet->palette().color(applet->backgroundRole())); } //-------------------------------------------------------------------------------- void DiskUsageAppletConfigureDialog::accept() { QPalette pal = applet->palette(); pal.setColor(applet->foregroundRole(), ui.textColor->color()); pal.setColor(applet->backgroundRole(), ui.backgroundColor->color()); applet->setPalette(pal); QDialog::accept(); } //-------------------------------------------------------------------------------- diff --git a/DiskUsageAppletConfigureDialog.hxx b/DiskUsageAppletConfigureDialog.hxx index 844a02a..eea5af8 100644 --- a/DiskUsageAppletConfigureDialog.hxx +++ b/DiskUsageAppletConfigureDialog.hxx @@ -1,42 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _DiskUsageAppletConfigureDialog_H_ #define _DiskUsageAppletConfigureDialog_H_ #include #include class DiskUsageApplet; class DiskUsageAppletConfigureDialog : public QDialog { Q_OBJECT public: DiskUsageAppletConfigureDialog(DiskUsageApplet *parent); private Q_SLOTS: void accept() override; private: DiskUsageApplet *applet; Ui::DiskUsageAppletConfigureDialog ui; }; #endif diff --git a/IconButton.cxx b/IconButton.cxx index 2746058..ba41bb6 100644 --- a/IconButton.cxx +++ b/IconButton.cxx @@ -1,78 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include //-------------------------------------------------------------------------------- IconButton::IconButton(QWidget *parent) : QToolButton(parent) { setAutoRaise(true); setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum); QHBoxLayout *hbox = new QHBoxLayout(this); iconLabel = new QLabel; iconLabel->setFixedSize(iconSize()); iconLabel->setContextMenuPolicy(Qt::PreventContextMenu); hbox->addWidget(iconLabel); textLabel = new QLabel; hbox->addWidget(textLabel); } //-------------------------------------------------------------------------------- IconButton::IconButton(QWidget *parent, const QIcon &icon, int theIconSize, const QString &name) : IconButton(parent) { if ( theIconSize != -1 ) setIconSize(QSize(theIconSize, theIconSize)); iconLabel->setFixedSize(iconSize()); iconLabel->setPixmap(icon.pixmap(iconSize())); textLabel->setText(name); } //-------------------------------------------------------------------------------- void IconButton::setText(const QString &txt) { textLabel->setText(txt); } //-------------------------------------------------------------------------------- void IconButton::setIcon(const QIcon &icon) { iconLabel->setPixmap(icon.pixmap(iconSize())); } //-------------------------------------------------------------------------------- QSize IconButton::sizeHint() const { return layout()->sizeHint(); } //-------------------------------------------------------------------------------- diff --git a/IconButton.hxx b/IconButton.hxx index e756520..d5ec43b 100644 --- a/IconButton.hxx +++ b/IconButton.hxx @@ -1,46 +1,47 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _IconButton_H_ #define _IconButton_H_ #include #include // button class to ensure the positioning of the icon and text independent of the style class IconButton : public QToolButton { Q_OBJECT public: IconButton(QWidget *parent = nullptr); IconButton(QWidget *parent, const QIcon &icon, int iconSize, const QString &name); void setText(const QString &txt); void setIcon(const QIcon &icon); QSize sizeHint() const override; private: QLabel *iconLabel = nullptr; QLabel *textLabel = nullptr; }; #endif diff --git a/KdeConnect.cxx b/KdeConnect.cxx index 30180cc..5c46059 100644 --- a/KdeConnect.cxx +++ b/KdeConnect.cxx @@ -1,245 +1,246 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- KdeConnect::KdeConnect() { getDevices(); QDBusConnection::sessionBus().connect("org.kde.kdeconnect", "/modules/kdeconnect", "org.kde.kdeconnect.daemon", "deviceAdded", this, SLOT(deviceAddedSlot(QString))); QDBusConnection::sessionBus().connect("org.kde.kdeconnect", "/modules/kdeconnect", "org.kde.kdeconnect.daemon", "deviceRemoved", this, SLOT(deviceRemovedSlot(QString))); QDBusConnection::sessionBus().connect("org.kde.kdeconnect", "/modules/kdeconnect", "org.kde.kdeconnect.daemon", "deviceVisibilityChanged", this, SLOT(deviceVisibilityChanged(QString, bool))); } //-------------------------------------------------------------------------------- void KdeConnect::getDevices() { QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", "/modules/kdeconnect", "org.kde.kdeconnect.daemon", "devices"); msg << true << true; // onlyReachable, onlyPaired QDBusConnection::sessionBus().callWithCallback(msg, this, SLOT(gotDevices(QDBusMessage))); } //-------------------------------------------------------------------------------- void KdeConnect::gotDevices(const QDBusMessage &msg) { QStringList deviceList = msg.arguments()[0].toStringList(); //qDebug() << __FUNCTION__ << deviceList; for (const QString &device : deviceList) deviceAddedSlot(device); } //-------------------------------------------------------------------------------- void KdeConnect::deviceVisibilityChanged(const QString &dev, bool visible) { //qDebug() << __FUNCTION__ << dev << visible; if ( visible ) deviceAddedSlot(dev); else deviceRemovedSlot(dev); } //-------------------------------------------------------------------------------- void KdeConnect::deviceAddedSlot(const QString &dev) { //qDebug() << __FUNCTION__ << dev; if ( devices.contains(dev) ) return; const QString devicePath = "/modules/kdeconnect/devices/" + dev; QDBusInterface interface("org.kde.kdeconnect", devicePath, "org.freedesktop.DBus.Properties"); QDBusReply reply; reply = interface.call("Get", "org.kde.kdeconnect.device", "isTrusted"); if ( !reply.value().toBool() ) return; // only show paired devices Device device; device->id = dev; reply = interface.call("Get", "org.kde.kdeconnect.device", "name"); device->name = reply.value().toString(); reply = interface.call("Get", "org.kde.kdeconnect.device", "type"); if ( reply.value().toString() == "smartphone" ) device->icon = QIcon::fromTheme("smartphone"); else if ( reply.value().toString() == "tablet" ) device->icon = QIcon::fromTheme("tablet"); else { reply = interface.call("Get", "org.kde.kdeconnect.device", "statusIconName"); device->icon = QIcon::fromTheme(reply.value().toString()); } QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", devicePath, "org.kde.kdeconnect.device", "loadedPlugins"); QDBusReply strings; strings = QDBusConnection::sessionBus().call(msg); device->plugins = strings.value(); msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", devicePath, "org.kde.kdeconnect.device.battery", "charge"); QDBusReply chargeReply = QDBusConnection::sessionBus().call(msg); if ( chargeReply.isValid() ) device->charge = chargeReply.value(); msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", devicePath, "org.kde.kdeconnect.device.battery", "isCharging"); QDBusReply isChargingReply = QDBusConnection::sessionBus().call(msg); if ( isChargingReply.isValid() ) device->isCharging = isChargingReply.value(); device->chargeChangedSlot(device->charge); QDBusConnection::sessionBus().connect("org.kde.kdeconnect", devicePath, "org.kde.kdeconnect.device.battery", "chargeChanged", device.data(), SLOT(chargeChangedSlot(int))); QDBusConnection::sessionBus().connect("org.kde.kdeconnect", devicePath, "org.kde.kdeconnect.device.battery", "stateChanged", device.data(), SLOT(stateChangedSlot(bool))); QDBusConnection::sessionBus().connect("org.kde.kdeconnect", devicePath, "org.kde.kdeconnect.device.battery", "nameChanged", device.data(), SLOT(nameChangedSlot(const QString &))); devices.insert(dev, device); emit deviceAdded(device); } //-------------------------------------------------------------------------------- void KdeConnect::deviceRemovedSlot(const QString &dev) { if ( !devices.contains(dev) ) return; devices.remove(dev); emit deviceRemoved(dev); } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- void KdeConnectDevice::ringPhone() { QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kdeconnect", "/modules/kdeconnect/devices/" + id + "/findmyphone", "org.kde.kdeconnect.device.findmyphone", "ring"); QDBusConnection::sessionBus().call(msg); } //-------------------------------------------------------------------------------- void KdeConnectDevice::chargeChangedSlot(int c) { //qDebug() << __FUNCTION__ << name << c; charge = c; if ( charge < 0 ) return; calcChargeIcon(); if ( isCharging ) return; const int LIMIT = 40; if ( charge < LIMIT ) // I want to keep charge above 40% { if ( !warned ) { warned = true; notif = new KNotification("device needs charging", KNotification::Persistent); notif->setTitle(i18n("Device needs charging")); notif->setText(i18n("Device charge of '%1' is at %2%.\nYou should charge it.", name, charge)); notif->setIconName("battery-040"); notif->sendEvent(); } } else { warned = false; if ( notif ) notif->close(); } } //-------------------------------------------------------------------------------- void KdeConnectDevice::stateChangedSlot(bool c) { //qDebug() << __FUNCTION__ << c; isCharging = c; calcChargeIcon(); if ( isCharging && notif ) notif->close(); } //-------------------------------------------------------------------------------- void KdeConnectDevice::calcChargeIcon() { chargeIcon = Battery::getStatusIcon(charge, isCharging); emit changed(); } //-------------------------------------------------------------------------------- void KdeConnectDevice::nameChangedSlot(const QString &newName) { //qDebug() << __FUNCTION__ << newName; name = newName; emit changed(); } //-------------------------------------------------------------------------------- diff --git a/KdeConnect.hxx b/KdeConnect.hxx index b84f490..578279c 100644 --- a/KdeConnect.hxx +++ b/KdeConnect.hxx @@ -1,95 +1,96 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _KdeConnect_H_ #define _KdeConnect_H_ #include #include #include #include #include #include class QDBusMessage; //-------------------------------------------------------------------------------- class KdeConnectDevice : public QObject { Q_OBJECT Q_SIGNALS: void changed(); public Q_SLOTS: void chargeChangedSlot(int c); public: void ringPhone(); QString id; QString name; QIcon icon, chargeIcon; QStringList plugins; int charge = -1; bool isCharging = false; bool warned = false; private Q_SLOTS: void stateChangedSlot(bool c); void nameChangedSlot(const QString &newName); private: void calcChargeIcon(); QPointer notif; }; //-------------------------------------------------------------------------------- class KdeConnect : public QObject { Q_OBJECT public: KdeConnect(); struct Device : public QSharedPointer { Device() : QSharedPointer(new KdeConnectDevice) {} }; Q_SIGNALS: void deviceAdded(const Device &device); void deviceRemoved(const QString &devId); private Q_SLOTS: void getDevices(); void gotDevices(const QDBusMessage &msg); void deviceAddedSlot(const QString &dev); void deviceRemovedSlot(const QString &dev); void deviceVisibilityChanged(const QString &dev, bool visible); private: QMap devices; }; #endif diff --git a/Launcher.cxx b/Launcher.cxx index f7f610d..48c3f25 100644 --- a/Launcher.cxx +++ b/Launcher.cxx @@ -1,102 +1,103 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- Launcher::Launcher(QWidget *parent, const QString &theId) : QWidget(parent), id(theId) { new QVBoxLayout(this); layout()->setContentsMargins(QMargins()); } //-------------------------------------------------------------------------------- void Launcher::loadConfig(const QString &defaultDir) { KConfig config; KConfigGroup group = config.group(id); setDir(group.readEntry(QString("dirPath"), defaultDir)); } //-------------------------------------------------------------------------------- void Launcher::setDir(const QString &theDirPath) { if ( !dirWatcher.directories().isEmpty() ) dirWatcher.removePaths(dirWatcher.directories()); dirPath = theDirPath; fill(); if ( !dirPath.isEmpty() ) { dirWatcher.addPath(dirPath); connect(&dirWatcher, &QFileSystemWatcher::directoryChanged, this, &Launcher::fill); } } //-------------------------------------------------------------------------------- void Launcher::contextMenuEvent(QContextMenuEvent *event) { Q_UNUSED(event) QMenu menu; QAction *action = menu.addAction(QIcon::fromTheme("configure"), i18n("Configure...")); connect(action, &QAction::triggered, [this]() { QString path = QFileDialog::getExistingDirectory(this, QString(), dirPath); if ( !path.isEmpty() ) { KConfig config; KConfigGroup group = config.group(id); group.writeEntry(QString("dirPath"), path); setDir(path); } } ); if ( !dirPath.isEmpty() ) { action = menu.addAction(QIcon::fromTheme("folder"), i18n("Open Folder")); connect(action, &QAction::triggered, [this]() { new KRun(QUrl::fromLocalFile(dirPath), nullptr); }); } menu.exec(QCursor::pos()); } //-------------------------------------------------------------------------------- diff --git a/Launcher.hxx b/Launcher.hxx index 3210616..eb8156a 100644 --- a/Launcher.hxx +++ b/Launcher.hxx @@ -1,47 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _Launcher_H_ #define _Launcher_H_ #include #include class Launcher : public QWidget { Q_OBJECT public: Launcher(QWidget *parent, const QString &theId); protected: void contextMenuEvent(QContextMenuEvent *event) override; void loadConfig(const QString &defaultDir = QString()); void setDir(const QString &theDirPath); private Q_SLOTS: virtual void fill() = 0; protected: QString id; QString dirPath; QFileSystemWatcher dirWatcher; }; #endif diff --git a/LockLogout.cxx b/LockLogout.cxx index 6b1fd47..764cefd 100644 --- a/LockLogout.cxx +++ b/LockLogout.cxx @@ -1,94 +1,95 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include //-------------------------------------------------------------------------------- LockLogout::LockLogout(DesktopPanel *parent) : QWidget(parent) { setObjectName("LockLogout"); QGridLayout *grid = new QGridLayout(this); grid->setContentsMargins(QMargins()); grid->setSpacing(2); lock = new QToolButton; logout = new QToolButton; lock->setIcon(QIcon::fromTheme("system-lock-screen")); logout->setIcon(QIcon::fromTheme("system-shutdown")); lock->setIconSize(QSize(22, 22)); logout->setIconSize(QSize(22, 22)); lock->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); logout->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); connect(lock, &QToolButton::clicked, []() { QDBusConnection::sessionBus().send( QDBusMessage::createMethodCall("org.freedesktop.ScreenSaver", "/ScreenSaver", "org.freedesktop.ScreenSaver", "Lock")); }); connect(logout, &QToolButton::clicked, []() { QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface", "logout"); msg << -1 << 0 << 0; // plasma-workspace/libkworkspace/kworkspace.h QDBusConnection::sessionBus().send(msg); }); fill(parent->getRows()); connect(parent, &DesktopPanel::rowsChanged, this, &LockLogout::fill); } //-------------------------------------------------------------------------------- void LockLogout::fill(int rows) { QGridLayout *grid = static_cast(layout()); delete grid->takeAt(0); delete grid->takeAt(1); if ( rows == 1 ) { grid->addWidget(lock, 0, 0); grid->addWidget(logout, 0, 1); } else { grid->addWidget(lock, 0, 0); grid->addWidget(logout, 1, 0); } } //-------------------------------------------------------------------------------- diff --git a/LockLogout.hxx b/LockLogout.hxx index 02fd408..fcf0ab4 100644 --- a/LockLogout.hxx +++ b/LockLogout.hxx @@ -1,42 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _LockLogout_H_ #define _LockLogout_H_ #include #include #include class LockLogout : public QWidget { Q_OBJECT public: LockLogout(DesktopPanel *parent); private Q_SLOTS: void fill(int rows); private: QToolButton *lock, *logout; }; #endif diff --git a/Network.cxx b/Network.cxx index a29c7c1..fb6f0ac 100644 --- a/Network.cxx +++ b/Network.cxx @@ -1,158 +1,159 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017,2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- Network::Network(QWidget *parent) : SysTrayItem(parent) { blinkTimer.setInterval(500); connect(&blinkTimer, &QTimer::timeout, [this]() { blinkState = !blinkState; setPixmap(blinkState ? origPixmap : QPixmap()); }); checkState(); connect(NetworkManager::notifier(), &NetworkManager::Notifier::statusChanged, this, &Network::checkState); connect(NetworkManager::notifier(), &NetworkManager::Notifier::connectivityChanged, this, &Network::checkState); connect(NetworkManager::notifier(), &NetworkManager::Notifier::primaryConnectionChanged, this, &Network::checkState); connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionRemoved, this, &Network::checkState); connect(NetworkManager::notifier(), &NetworkManager::Notifier::activeConnectionsChanged, this, &Network::checkState); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &Network::checkState); QDBusConnection::sessionBus().send( QDBusMessage::createMethodCall("org.kde.kded5", "/modules/networkmanagement", "org.kde.plasmanetworkmanagement", "init")); } //-------------------------------------------------------------------------------- void Network::checkState() { if ( NetworkManager::status() == NetworkManager::Unknown ) { // if the system does not have NM running, hide the icon hide(); return; } show(); if ( NetworkManager::status() == NetworkManager::Connecting ) blinkTimer.start(); else blinkTimer.stop(); if ( (NetworkManager::connectivity() != NetworkManager::Full) || !NetworkManager::primaryConnection() || !NetworkManager::primaryConnection()->connection() ) { setPixmap(origPixmap = QIcon::fromTheme("network-disconnect").pixmap(size())); setToolTip(i18n("No Network Connection")); return; } NetworkManager::ActiveConnection::Ptr conn(NetworkManager::primaryConnection()); //connect(conn.data(), &NetworkManager::ActiveConnection::vpnChanged, this, &Network::checkState); QString tip = i18n("Full Network Connectivity (%1)", conn->connection()->name()); NetworkManager::Device::Ptr device; if ( conn->devices().count() ) { device = NetworkManager::findNetworkInterface(conn->devices()[0]); if ( device && device->ipV4Config().addresses().count() ) tip += "\n" + i18n("IPv4 Address: %1", device->ipV4Config().addresses()[0].ip().toString()); } QPixmap pixmap; if ( conn->type() == NetworkManager::ConnectionSettings::Wireless ) { NetworkManager::WirelessDevice::Ptr dev = qobject_cast(device); if ( dev ) { NetworkManager::AccessPoint::Ptr accessPoint = dev->activeAccessPoint(); int signalStrength = accessPoint.isNull() ? 0 : accessPoint->signalStrength(); int x = std::round(signalStrength / 25.0) * 25; pixmap = QIcon::fromTheme(QString("network-wireless-connected-%1").arg(x)).pixmap(size()); if ( !accessPoint.isNull() ) tip += "\n" + i18n("SSID: %1", accessPoint->ssid()); tip += "\n" + i18n("Signal Strength: %1", signalStrength); } } else { pixmap = QIcon::fromTheme("network-connect").pixmap(size()); } //qDebug() << conn << "type" << conn->type() << "vpn" << conn->vpn(); bool vpnActive = false; for (const NetworkManager::ActiveConnection::Ptr &ac : NetworkManager::activeConnections()) { if ( ac->vpn() ) { vpnActive = true; break; } } if ( vpnActive ) { pixmap = QIcon::fromTheme("security-high").pixmap(size()); tip += "\n" + i18n("VPN active"); } setPixmap(origPixmap = pixmap); setToolTip(tip); } //-------------------------------------------------------------------------------- QWidget *Network::getDetailsList() { if ( !networkList ) { networkList = new NetworkList(this); networkList->setAttribute(Qt::WA_DeleteOnClose); connect(networkList.data(), &NetworkList::changed, this, &Network::showDetailsList); // reposition } return networkList.data(); } //-------------------------------------------------------------------------------- diff --git a/Network.hxx b/Network.hxx index d6fa397..8ca9af5 100644 --- a/Network.hxx +++ b/Network.hxx @@ -1,50 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _Network_H_ #define _Network_H_ // network management (status display, activate, switch, ...) #include #include #include #include class Network : public SysTrayItem { Q_OBJECT public: Network(QWidget *parent); protected: QWidget *getDetailsList() override; private Q_SLOTS: void checkState(); private: QTimer blinkTimer; bool blinkState = false; QPixmap origPixmap; QPointer networkList; }; #endif diff --git a/NetworkList.cxx b/NetworkList.cxx index 946502e..0d696c9 100644 --- a/NetworkList.cxx +++ b/NetworkList.cxx @@ -1,286 +1,287 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- NetworkButton::NetworkButton(NetworkManager::Connection::Ptr c, NetworkManager::Device::Ptr dev) : connection(c), device(dev) { setCheckable(true); if ( connection ) { for (const NetworkManager::ActiveConnection::Ptr &ac : NetworkManager::activeConnections()) { if ( ac->uuid() == c->uuid() ) { setChecked(true); break; } } connect(this, &NetworkButton::toggled, this, &NetworkButton::toggleNetworkStatus); } else setEnabled(false); } //-------------------------------------------------------------------------------- void NetworkButton::toggleNetworkStatus(bool on) { if ( on ) { switch ( connection->settings()->connectionType() ) { case NetworkManager::ConnectionSettings::Wired: { NetworkManager::activateConnection(connection->path(), connection->settings()->interfaceName(), QString()); break; } case NetworkManager::ConnectionSettings::Wireless: { NetworkManager::activateConnection(connection->path(), device->uni(), QString()); break; } case NetworkManager::ConnectionSettings::Vpn: { NetworkManager::ActiveConnection::Ptr conn(NetworkManager::primaryConnection()); if ( conn && !conn->devices().isEmpty() ) NetworkManager::activateConnection(connection->path(), conn->devices()[0], QString()); break; } default: ; // TODO } } else { for (const NetworkManager::ActiveConnection::Ptr &ac : NetworkManager::activeConnections()) { if ( ac->uuid() == connection->uuid() ) { NetworkManager::deactivateConnection(ac->path()); break; } } } } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- NetworkList::NetworkList(QWidget *parent) : QFrame(parent) { setWindowFlags(windowFlags() | Qt::Popup); setFrameShape(QFrame::StyledPanel); QVBoxLayout *vbox = new QVBoxLayout(this); QHBoxLayout *hbox = new QHBoxLayout; vbox->addLayout(hbox); network = new QToolButton; network->setIcon(QIcon::fromTheme("network-wired")); network->setCheckable(true); connect(network, &QToolButton::toggled, [](bool on) { NetworkManager::setNetworkingEnabled(on); }); connect(NetworkManager::notifier(), &NetworkManager::Notifier::networkingEnabledChanged, this, &NetworkList::statusUpdate); hbox->addWidget(network); wireless = new QToolButton; wireless->setIcon(QIcon::fromTheme("network-wireless")); wireless->setCheckable(true); connect(wireless, &QToolButton::toggled, [](bool on) { NetworkManager::setWirelessEnabled(on); }); connect(NetworkManager::notifier(), &NetworkManager::Notifier::wirelessEnabledChanged, this, &NetworkList::statusUpdate); hbox->addWidget(wireless); statusUpdate(); hbox->addStretch(); QToolButton *configure = new QToolButton; configure->setIcon(QIcon::fromTheme("configure")); connect(configure, &QToolButton::clicked, this, &NetworkList::openConfigureDialog); hbox->addWidget(configure); // show connections connectionsVbox = new QVBoxLayout; connectionsVbox->setContentsMargins(QMargins()); vbox->addLayout(connectionsVbox); fillConnections(); QTimer *checkConnectionsTimer = new QTimer(this); checkConnectionsTimer->setInterval(1000); connect(checkConnectionsTimer, &QTimer::timeout, this, &NetworkList::fillConnections); checkConnectionsTimer->start(); } //-------------------------------------------------------------------------------- void NetworkList::openConfigureDialog() { hide(); // newer plasma has already a KCM KService::Ptr service = KService::serviceByDesktopName("kcm_networkmanagement"); if ( service ) KRun::runApplication(*service, QList(), this); else KRun::run("kde5-nm-connection-editor", QList(), this); } //-------------------------------------------------------------------------------- void NetworkList::statusUpdate() { network->setChecked(NetworkManager::isNetworkingEnabled()); wireless->setChecked(NetworkManager::isWirelessEnabled()); } //-------------------------------------------------------------------------------- void NetworkList::fillConnections() { if ( underMouse() ) // avoid to delete the widget we are possibly hovering over return; QLayoutItem *child; while ( (child = connectionsVbox->takeAt(0)) ) { delete child->widget(); delete child; } NetworkManager::Connection::List allConnections = NetworkManager::listConnections(); for (const NetworkManager::Connection::Ptr c : allConnections) { if ( !c->isValid() ) continue; if ( (c->settings()->connectionType() == NetworkManager::ConnectionSettings::Wired) && !c->uuid().isEmpty() ) { NetworkButton *net = new NetworkButton(c); net->setText(c->name()); net->setIcon(QIcon::fromTheme("network-wired")); connectionsVbox->addWidget(net); } else if ( c->settings()->connectionType() == NetworkManager::ConnectionSettings::Vpn ) { NetworkButton *vpn = new NetworkButton(c); vpn->setText(c->name()); vpn->setIcon(QIcon::fromTheme("security-high")); connectionsVbox->addWidget(vpn); } } // show available wifi networks if ( NetworkManager::isWirelessEnabled() ) { for (const NetworkManager::Device::Ptr &device : NetworkManager::networkInterfaces()) { if ( device->type() != NetworkManager::Device::Wifi ) continue; NetworkManager::WirelessDevice::Ptr wifiDevice = device.objectCast(); for (const NetworkManager::WirelessNetwork::Ptr &network : wifiDevice->networks()) { NetworkManager::AccessPoint::Ptr accessPoint = network->referenceAccessPoint(); if ( !accessPoint ) continue; // check if we have a connection for this SSID bool haveConnection = false; NetworkManager::Connection::Ptr conn; for (const NetworkManager::Connection::Ptr c : allConnections) { if ( c->isValid() && (c->settings()->connectionType() == NetworkManager::ConnectionSettings::Wireless) ) { NetworkManager::Setting::Ptr setting = c->settings()->setting(NetworkManager::Setting::Wireless); NetworkManager::WirelessSetting::Ptr s = setting.staticCast(); if ( s->ssid() == network->ssid() ) { haveConnection = true; conn = c; break; } } } if ( haveConnection ) { NetworkButton *net = new NetworkButton(conn, device); net->setText(QString("%1 (%2%)").arg(network->ssid()).arg(network->signalStrength())); net->setIcon(QIcon::fromTheme("network-wireless")); connectionsVbox->addWidget(net); } else { NetworkButton *net = new NetworkButton; net->setText(QString("%1 (%2%)").arg(network->ssid()).arg(network->signalStrength())); net->setIcon(QIcon::fromTheme("network-wireless")); net->setEnabled(false); // TODO: allow to add a new connection. See NetworkManager::addAndActivateConnection connectionsVbox->addWidget(net); } /* NetworkManager::WirelessSecurityType security = NetworkManager::findBestWirelessSecurity(wifiDevice->wirelessCapabilities(), true, wifiDevice->mode() == NetworkManager::WirelessDevice::Adhoc, accessPoint->capabilities(), accessPoint->wpaFlags(), accessPoint->rsnFlags()); if ( (security != NetworkManager::UnknownSecurity) && (security != NetworkManager::NoneSecurity) ) net->setIcon(QIcon::fromTheme("object-locked")); else net->setIcon(QIcon::fromTheme("object-unlocked")); */ } } } connectionsVbox->addStretch(); adjustSize(); emit changed(); } //-------------------------------------------------------------------------------- diff --git a/NetworkList.hxx b/NetworkList.hxx index e16ec3a..11d16dc 100644 --- a/NetworkList.hxx +++ b/NetworkList.hxx @@ -1,68 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _NetworkList_H_ #define _NetworkList_H_ #include #include #include class NetworkList : public QFrame { Q_OBJECT public: NetworkList(QWidget *parent); Q_SIGNALS: void changed(); private Q_SLOTS: void statusUpdate(); void fillConnections(); void openConfigureDialog(); private: QToolButton *network, *wireless; QVBoxLayout *connectionsVbox; }; //-------------------------------------------------------------------------------- #include #include class NetworkButton : public IconButton { Q_OBJECT public: NetworkButton(NetworkManager::Connection::Ptr c = NetworkManager::Connection::Ptr(), NetworkManager::Device::Ptr dev = NetworkManager::Device::Ptr()); private Q_SLOTS: void toggleNetworkStatus(bool on); private: NetworkManager::Connection::Ptr connection; NetworkManager::Device::Ptr device; }; #endif diff --git a/NotificationList.cxx b/NotificationList.cxx index 8b59d40..27aa4ef 100644 --- a/NotificationList.cxx +++ b/NotificationList.cxx @@ -1,316 +1,317 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017,2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const Qt::WindowFlags POPUP_FLAGS = Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint; //-------------------------------------------------------------------------------- NotifyItem::NotifyItem(QWidget *parent, uint theId, const QString &app, const QString &summary, const QString &body, const QIcon &icon, const QStringList &actions) : QFrame(parent, POPUP_FLAGS), id(theId), appName(app) { setAttribute(Qt::WA_ShowWithoutActivating); // avoid focus stealing setFrameShape(QFrame::StyledPanel); QMargins margins = contentsMargins(); margins.setRight(0); // allow the close button to reach to the right screen border - easier to click setContentsMargins(margins); QVBoxLayout *vbox = new QVBoxLayout; timeLabel = new QLabel; iconLabel = new QLabel; vbox->addWidget(timeLabel, 0, Qt::AlignTop | Qt::AlignHCenter); vbox->addWidget(iconLabel, 0, Qt::AlignTop | Qt::AlignHCenter); QVBoxLayout *centerBox = new QVBoxLayout; QHBoxLayout *hbox = new QHBoxLayout(this); margins = hbox->contentsMargins(); margins.setRight(0); // allow the close button to reach to the right screen border - easier to click hbox->setContentsMargins(margins); textLabel = new QLabel; QToolButton *closeButton = new QToolButton; // easier to click with larger size closeButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); closeButton->setMinimumWidth(40); closeButton->setAutoRaise(true); closeButton->setIcon(QIcon::fromTheme("window-close")); connect(closeButton, &QToolButton::clicked, this, &NotifyItem::deleteLater); textLabel->setWordWrap(true); textLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); textLabel->setOpenExternalLinks(true); textLabel->setMinimumWidth(300); textLabel->setMaximumWidth(600); textLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); centerBox->addWidget(textLabel); if ( actions.count() ) { QHBoxLayout *actionBox = new QHBoxLayout; centerBox->addLayout(actionBox); for (int i = 0; i < actions.count(); i++) { if ( ((i % 2) != 0) && !actions[i].isEmpty() ) // id { QPushButton *button = new QPushButton; button->setText(actions[i]); actionBox->addWidget(button); QString key = actions[i - 1]; connect(button, &QPushButton::clicked, this, [this, key]() { QDBusMessage msg = QDBusMessage::createSignal("/org/freedesktop/Notifications", "org.freedesktop.Notifications", "ActionInvoked"); msg << id << key; QDBusConnection::sessionBus().send(msg); }); } } } hbox->addLayout(vbox); hbox->addLayout(centerBox); hbox->addWidget(closeButton); iconLabel->setFixedSize(32, 32); iconLabel->setPixmap(icon.pixmap(iconLabel->size())); QString title = (appName == summary) ? appName : (appName + ": " + summary); textLabel->setText(QString("
%1

%2") .arg(title) .arg(body)); timeLabel->setText(QTime::currentTime().toString(Qt::SystemLocaleShortDate)); } //-------------------------------------------------------------------------------- void NotifyItem::destroySysResources() { destroy(); } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- NotificationList::NotificationList(QWidget *parent) : QWidget(parent) { setWindowFlags(windowFlags() | Qt::Tool); setWindowTitle(i18n("Notifications")); scrollArea = new QScrollArea; scrollArea->setWidgetResizable(true); QWidget *listWidget = new QWidget; listVbox = new QVBoxLayout(listWidget); listVbox->setContentsMargins(QMargins()); listVbox->addStretch(); scrollArea->setWidget(listWidget); QVBoxLayout *vbox = new QVBoxLayout(this); vbox->setContentsMargins(QMargins()); vbox->addWidget(scrollArea); QPushButton *clearButton = new QPushButton; clearButton->setIcon(QIcon::fromTheme("edit-clear-list")); connect(clearButton, &QPushButton::clicked, this, [this]() { for (NotifyItem *item : items) item->deleteLater(); }); vbox->addWidget(clearButton); resize(500, 300); } //-------------------------------------------------------------------------------- NotificationList::~NotificationList() { for (NotifyItem *item : items) item->disconnect(); // make sure destroyed() is no longer triggered } //-------------------------------------------------------------------------------- void NotificationList::addItem(uint id, const QString &appName, const QString &summary, const QString &body, const QIcon &icon, const QStringList &actions, const QVariantMap &hints, int timeout) { QPointer item = new NotifyItem(nullptr, id, appName, summary, body, icon, actions); item->resize(500, item->sizeHint().height()); KWindowSystem::setState(item->winId(), NET::SkipTaskbar | NET::SkipPager); items.append(item.data()); placeItems(); int wordCount = body.splitRef(' ', QString::SkipEmptyParts).count(); bool neverExpires = timeout == 0; // according to spec if ( timeout <= 0 ) { timeout = 4000 + 250 * wordCount; if ( actions.count() ) timeout += 15000; // give user more time to think ... } // 0=low, 1=normal, 2=critical if ( hints.contains("urgency") && (hints["urgency"].toInt() == 2) ) { neverExpires = true; timeout = 20000; // just show it longer since its urgent } bool transient = hints.contains("transient") ? hints["transient"].toBool() : false; // if there are actions, show it longer if ( actions.count() || neverExpires ) transient = false; // exceptions ... if ( transient && hints.contains("x-kde-eventId") ) { // I'd like to keep this much longer if ( hints["x-kde-eventId"].toString() == "new-email" ) transient = false; } numItems++; emit itemsCountChanged(); connect(item.data(), &NotifyItem::destroyed, this, [this](QObject *obj) { items.removeOne(static_cast(obj)); placeItems(); if ( --numItems == 0 ) { hide(); emit listNowEmpty(); } else emit itemsCountChanged(); } ); QTimer::singleShot(timeout, [=]() { if ( item ) { // sometimes there were some leftover/half-functioning windows. This seems to avoid that item->destroySysResources(); listVbox->insertWidget(listVbox->count() - 1, item); // insert before stretch item->show(); placeItems(); // reorder the remaining ones scrollArea->setWidgetResizable(true); // to update scrollbars, else next line does not work scrollArea->ensureWidgetVisible(item); if ( !neverExpires ) { if ( !appTimeouts.contains(appName) ) appTimeouts.insert(appName, 10); // default: 10 minutes lifetime int expireTimeout; if ( transient ) expireTimeout = 2 * 60 * 1000; // still keep it a short time else expireTimeout = appTimeouts[appName] * 60 * 1000; QTimer::singleShot(expireTimeout, item.data(), &NotifyItem::deleteLater); } } } ); } //-------------------------------------------------------------------------------- void NotificationList::closeItem(uint id) { for (NotifyItem *item : items) { if ( item->getId() == id ) { item->deleteLater(); break; } } } //-------------------------------------------------------------------------------- void NotificationList::placeItems() { QRect screen = QApplication::desktop()->availableGeometry(this); QPoint point = parentWidget()->mapToGlobal(parentWidget()->pos()); int x = point.x(); int y = screen.bottom(); for (NotifyItem *item : items) { if ( !item->parentWidget() ) // temporary item not in the list yet { y -= item->sizeHint().height(); y -= 5; // a small space point.setX(std::min(x, screen.x() + screen.width() - item->sizeHint().width())); point.setY(y); item->move(point); item->show(); } } } //-------------------------------------------------------------------------------- diff --git a/NotificationList.hxx b/NotificationList.hxx index 2ea278d..de505f4 100644 --- a/NotificationList.hxx +++ b/NotificationList.hxx @@ -1,81 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _NotificationList_H_ #define _NotificationList_H_ #include #include #include #include #include class QVBoxLayout; class NotifyItem : public QFrame { Q_OBJECT public: NotifyItem(QWidget *parent, uint theid, const QString &app, const QString &summary, const QString &body, const QIcon &icon, const QStringList &actions); uint getId() const { return id; } void destroySysResources(); private: QLabel *timeLabel, *iconLabel, *textLabel; uint id; QString appName; }; //-------------------------------------------------------------------------------- class NotificationList : public QWidget { Q_OBJECT public: NotificationList(QWidget *parent); ~NotificationList() override; void addItem(uint id, const QString &appName, const QString &summary, const QString &body, const QIcon &icon, const QStringList &actions, const QVariantMap &hints, int timeout); void closeItem(uint id); int itemCount() const { return numItems; } Q_SIGNALS: void itemsCountChanged(); void listNowEmpty(); private: void placeItems(); private: QScrollArea *scrollArea; QVBoxLayout *listVbox; QMap appTimeouts; // appName, timeout (minutes) int numItems = 0; QVector items; }; #endif diff --git a/NotificationServer.cxx b/NotificationServer.cxx index 63b3fbb..133fbff 100644 --- a/NotificationServer.cxx +++ b/NotificationServer.cxx @@ -1,132 +1,133 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- NotificationServer::NotificationServer(QWidget *parent) : SysTrayItem(parent, "preferences-desktop-notification") { new NotificationsAdaptor(this); QDBusConnection dbus = QDBusConnection::sessionBus(); if ( dbus.registerService("org.freedesktop.Notifications") ) { if ( !dbus.registerObject("/org/freedesktop/Notifications", this) ) dbus.unregisterService("org.freedesktop.Notifications"); } notificationList = new NotificationList(this); connect(notificationList, &NotificationList::listNowEmpty, this, &NotificationServer::hide); connect(notificationList, &NotificationList::itemsCountChanged, [this]() { show(); setToolTip(i18np("%1 notification", "%1 notifications", notificationList->itemCount())); } ); hide(); } //-------------------------------------------------------------------------------- QStringList NotificationServer::GetCapabilities() { return QStringList() << "body" << "body-hyperlinks" << "body-images" << "body-markup" << "icon-static" << "persistence" << "actions" ; } //-------------------------------------------------------------------------------- void NotificationServer::CloseNotification(uint id) { notificationList->closeItem(id); } //-------------------------------------------------------------------------------- QString NotificationServer::GetServerInformation(QString &vendor, QString &version, QString &spec_version) { vendor = "kollix"; version = "1.0"; spec_version = "1.2"; return vendor; } //-------------------------------------------------------------------------------- uint NotificationServer::Notify(const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &theBody, const QStringList &actions, const QVariantMap &hints, int timeout) { //qDebug() << "app" << app_name << "summary" << summary << "body" << theBody << "timeout" << timeout << "replaceId" << replaces_id // << "hints" << hints << "actions" << actions; QString body(theBody); body.replace("\n", "
"); QIcon icon; if ( !app_icon.isEmpty() ) icon = QIcon::fromTheme(app_icon); else if ( hints.contains("image-path") ) icon = QIcon(hints["image-path"].toString()); QString appName = app_name; if ( appName.isEmpty() && hints.contains("desktop-entry") ) { KService::Ptr service = KService::serviceByDesktopName(hints["desktop-entry"].toString().toLower()); if ( service ) appName = service->name(); } notificationList->addItem(notifyId, appName, summary, body, icon, actions, hints, timeout); if ( replaces_id != 0 ) notificationList->closeItem(replaces_id); return notifyId++; } //-------------------------------------------------------------------------------- QWidget *NotificationServer::getDetailsList() { return notificationList; } //-------------------------------------------------------------------------------- diff --git a/NotificationServer.hxx b/NotificationServer.hxx index d683710..49f9802 100644 --- a/NotificationServer.hxx +++ b/NotificationServer.hxx @@ -1,57 +1,58 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _NotificationServer_H_ #define _NotificationServer_H_ // https://developer.gnome.org/notification-spec/ #include class NotificationList; class NotificationServer : public SysTrayItem { Q_OBJECT public: NotificationServer(QWidget *parent); void CloseNotification(uint id); QStringList GetCapabilities(); QString GetServerInformation(QString &vendor, QString &version, QString &spec_version); uint Notify(const QString &app_name, uint replaces_id, const QString &app_icon, const QString &summary, const QString &body, const QStringList &actions, const QVariantMap &hints, int timeout); protected: QWidget *getDetailsList() override; Q_SIGNALS: void ActionInvoked(uint id, const QString &action_key); void NotificationClosed(uint id, uint reason); private: uint notifyId = 1; NotificationList *notificationList; }; #endif diff --git a/OnScreenVolume.cxx b/OnScreenVolume.cxx index 2675ab4..f0f78e4 100644 --- a/OnScreenVolume.cxx +++ b/OnScreenVolume.cxx @@ -1,174 +1,175 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- OnScreenVolume::OnScreenVolume(QWidget *parent) : QProgressBar(parent) { setWindowFlags(Qt::Window | Qt::FramelessWindowHint | Qt::WindowDoesNotAcceptFocus); setFixedSize(400, 40); hide(); KWindowSystem::setState(winId(), NET::KeepAbove); KWindowSystem::setType(winId(), NET::Dock); KWindowSystem::setOnAllDesktops(winId(), true); hideTimer.setInterval(1000); hideTimer.setSingleShot(true); connect(&hideTimer, &QTimer::timeout, this, &QWidget::hide); QDBusConnection::sessionBus() .connect("org.kde.kmix", "/Mixers", "org.kde.KMix.MixSet", "masterChanged", this, SLOT(getMasterMixer())); if ( !QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kmix") ) { QDBusServiceWatcher *w = new QDBusServiceWatcher("org.kde.kmix", QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration, this); connect(w, &QDBusServiceWatcher::serviceRegistered, this, &OnScreenVolume::getMasterMixer); } else getMasterMixer(); } //-------------------------------------------------------------------------------- void OnScreenVolume::getMasterMixer() { QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kmix", "/Mixers", "org.freedesktop.DBus.Properties", "GetAll"); msg << "org.kde.KMix.MixSet"; QDBusConnection::sessionBus() .callWithCallback(msg, this, SLOT(gotMasterMixer(QDBusMessage)), SLOT(gotMasterMixerError(QDBusError, QDBusMessage))); } //-------------------------------------------------------------------------------- void OnScreenVolume::gotMasterMixerError(QDBusError error, QDBusMessage msg) { Q_UNUSED(error) Q_UNUSED(msg) if ( retryTimer.interval() == 0 ) { // the service could already be registered but no object yet retryTimer.setInterval(1000); retryTimer.setSingleShot(true); connect(&retryTimer, &QTimer::timeout, this, &OnScreenVolume::getMasterMixer); } retryTimer.start(); } //-------------------------------------------------------------------------------- void OnScreenVolume::gotMasterMixer(QDBusMessage msg) { QDBusReply reply = msg; if ( !reply.isValid() ) return; if ( !masterMixer.isEmpty() ) { // disconnect previous master mixer QDBusConnection::sessionBus() .disconnect("org.kde.kmix", "/Mixers/" + masterMixer, "org.kde.KMix.Mixer", "controlChanged", this, SLOT(controlChanged())); } masterMixer = reply.value()["currentMasterMixer"].toString(); masterMixer.replace(':', '_'); masterControl = reply.value()["currentMasterControl"].toString(); masterControl.replace('.', '_'); masterControl.replace('-', '_'); //qDebug() << masterMixer << masterControl; QDBusConnection::sessionBus() .connect("org.kde.kmix", "/Mixers/" + masterMixer, "org.kde.KMix.Mixer", "controlChanged", this, SLOT(controlChanged())); } //-------------------------------------------------------------------------------- void OnScreenVolume::controlChanged() { QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.kmix", "/Mixers/" + masterMixer + '/' + masterControl, "org.freedesktop.DBus.Properties", "Get"); msg << "org.kde.KMix.Control" << "volume"; QDBusConnection::sessionBus().callWithCallback(msg, this, SLOT(volumeChanged(QDBusMessage))); } //-------------------------------------------------------------------------------- void OnScreenVolume::volumeChanged(QDBusMessage reply) { if ( reply.type() == QDBusMessage::ErrorMessage ) return; if ( reply.arguments().count() && (reply.arguments()[0].value().variant().toInt() != value()) ) { setValue(reply.arguments()[0].value().variant().toInt()); if ( !isVisible() ) // just always once before showing { KConfig config("kmixrc"); KConfigGroup group = config.group("Global"); if ( !group.readEntry("showOSD", true) ) return; } move((QApplication::desktop()->width() - width()) / 2, QApplication::desktop()->height() * 0.8); show(); hideTimer.start(); } } //-------------------------------------------------------------------------------- diff --git a/OnScreenVolume.hxx b/OnScreenVolume.hxx index 1e3cdda..4c991e2 100644 --- a/OnScreenVolume.hxx +++ b/OnScreenVolume.hxx @@ -1,50 +1,51 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _OnScreenVolume_H_ #define _OnScreenVolume_H_ #include #include #include class QDBusMessage; class QDBusError; class OnScreenVolume : public QProgressBar { Q_OBJECT public: OnScreenVolume(QWidget *parent); private Q_SLOTS: void gotMasterMixerError(QDBusError error, QDBusMessage msg); void getMasterMixer(); void gotMasterMixer(QDBusMessage msg); void controlChanged(); void volumeChanged(QDBusMessage reply); private: QTimer hideTimer, retryTimer; QString masterMixer; QString masterControl; bool requestPending = false; }; #endif diff --git a/Pager.cxx b/Pager.cxx index 26cb769..cfeaf6a 100644 --- a/Pager.cxx +++ b/Pager.cxx @@ -1,135 +1,136 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017, 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- Pager::Pager(DesktopPanel *parent) : QWidget(parent) { group = new QButtonGroup(this); QGridLayout *grid = new QGridLayout(this); grid->setSpacing(2); grid->setContentsMargins(QMargins()); connect(KWindowSystem::self(), &KWindowSystem::numberOfDesktopsChanged, this, &Pager::fill); connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, [this]() { if ( KWindowSystem::currentDesktop() <= buttons.count() ) buttons[KWindowSystem::currentDesktop() - 1]->setChecked(true); } ); connect(parent, &DesktopPanel::rowsChanged, this, &Pager::fill); KConfig config; KConfigGroup group = config.group("Pager"); if ( !group.hasKey("showIcons") ) // create config entry so that one knows it exists group.writeEntry("showIcons", true); showIcons = group.readEntry("showIcons", true); fill(); QAction *action = new QAction(this); action->setIcon(QIcon::fromTheme("configure")); action->setText(i18n("Configure Virtual Desktops...")); addAction(action); connect(action, &QAction::triggered, [this]() { auto dialog = new KCMultiDialog(parentWidget()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18n("Configure Virtual Desktops")); dialog->addModule("desktop"); dialog->adjustSize(); dialog->show(); } ); setContextMenuPolicy(Qt::ActionsContextMenu); } //-------------------------------------------------------------------------------- void Pager::fill() { qDeleteAll(buttons); buttons.clear(); NETRootInfo ri(QX11Info::connection(), 0, NET::WM2DesktopLayout); int row = 0, col = 0; const int MAX_COLUMNS = std::max(1, ri.desktopLayoutColumnsRows().width()); for (int i = 1; i <= KWindowSystem::numberOfDesktops(); i++) { PagerButton *b = new PagerButton(i, qobject_cast(parentWidget()), showIcons); b->setCheckable(true); b->setFocusPolicy(Qt::NoFocus); group->addButton(b); buttons.append(b); if ( i == KWindowSystem::currentDesktop() ) b->setChecked(true); connect(b, &PagerButton::clicked, this, &Pager::changeDesktop); static_cast(layout())->addWidget(b, row, col); col = (col + 1) % MAX_COLUMNS; if ( col == 0 ) row++; } } //-------------------------------------------------------------------------------- void Pager::changeDesktop(bool checked) { if ( !checked ) return; int desktopNum = qobject_cast(sender())->getDesktop(); if ( KWindowSystem::currentDesktop() == desktopNum ) KWindowSystem::setShowingDesktop(!KWindowSystem::showingDesktop()); else KWindowSystem::setCurrentDesktop(desktopNum); } //-------------------------------------------------------------------------------- diff --git a/Pager.hxx b/Pager.hxx index 759e6bc..1b249ae 100644 --- a/Pager.hxx +++ b/Pager.hxx @@ -1,45 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017, 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _Pager_H_ #define _Pager_H_ #include class QButtonGroup; class PagerButton; class DesktopPanel; class Pager : public QWidget { Q_OBJECT public: Pager(DesktopPanel *parent); private Q_SLOTS: void fill(); void changeDesktop(bool checked); private: QButtonGroup *group; QVector buttons; bool showIcons = true; }; #endif diff --git a/PagerButton.cxx b/PagerButton.cxx index 0ec8bb4..d867cef 100644 --- a/PagerButton.cxx +++ b/PagerButton.cxx @@ -1,186 +1,187 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017, 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- PagerButton::PagerButton(int num, DesktopPanel *p, bool doShowIcon) : desktop(num), panel(p), showIcon(doShowIcon) { setText(KWindowSystem::desktopName(desktop).isEmpty() ? QString::number(desktop) : KWindowSystem::desktopName(desktop)); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); setAcceptDrops(true); dragDropTimer.setSingleShot(true); dragDropTimer.setInterval(1000); connect(&dragDropTimer, &QTimer::timeout, this, [this]() { emit clicked(true); }); if ( showIcon ) { createPixmap(); connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &PagerButton::createPixmap); connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &PagerButton::createPixmap); connect(KWindowSystem::self(), &KWindowSystem::stackingOrderChanged, this, &PagerButton::createPixmap); connect(KWindowSystem::self(), SIGNAL(windowChanged(WId, NET::Properties, NET::Properties2)), this, SLOT(windowChanged(WId, NET::Properties, NET::Properties2))); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, [this]() { updateGeometry(); }); } connect(KWindowSystem::self(), &KWindowSystem::desktopNamesChanged, this, [this]() { setText(KWindowSystem::desktopName(desktop).isEmpty() ? QString::number(desktop) : KWindowSystem::desktopName(desktop)); }); } //-------------------------------------------------------------------------------- QSize PagerButton::sizeHint() const { QSize s = fontMetrics().size(0, text()); s.setWidth(std::max(45, s.width() + 10)); s.setHeight(QPushButton::sizeHint().height()); if ( panel->getRows() == 1 ) s.setHeight(std::max(s.height(), KIconLoader::global()->currentSize(KIconLoader::Panel))); return s; } //-------------------------------------------------------------------------------- void PagerButton::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); QStyleOptionButton option; initStyleOption(&option); style()->drawControl(QStyle::CE_PushButtonBevel, &option, &painter, this); if ( !firstPixmap.isNull() ) painter.drawPixmap((width() - firstPixmap.width()) / 2, (height() - firstPixmap.height()) / 2, firstPixmap); style()->drawControl(QStyle::CE_PushButtonLabel, &option, &painter, this); } //-------------------------------------------------------------------------------- void PagerButton::createPixmap() { firstPixmap = QPixmap(); QList windows = KWindowSystem::stackingOrder(); // from top to bottom for (int i = windows.count() - 1; i >= 0; i--) { WId wid = windows[i]; KWindowInfo win(wid, NET::WMDesktop | NET::WMWindowType | NET::WMState | NET::WMIcon); if ( win.valid(true) && win.isOnDesktop(desktop) && (win.windowType(NET::DesktopMask) != NET::Desktop) && (win.windowType(NET::DockMask) != NET::Dock) && !(win.state() & NET::SkipTaskbar) ) { firstPixmap = KWindowSystem::icon(wid, 22, 22, true); KIconEffect effect; firstPixmap = effect.apply(firstPixmap, KIconEffect::DeSaturate, 0, QColor(), true); break; } } update(); } //-------------------------------------------------------------------------------- void PagerButton::windowChanged(WId id, NET::Properties props, NET::Properties2 props2) { Q_UNUSED(id) Q_UNUSED(props2) if ( props & (NET::WMIcon | NET::WMDesktop) ) createPixmap(); } //-------------------------------------------------------------------------------- void PagerButton::dragEnterEvent(QDragEnterEvent *event) { event->accept(); dragDropTimer.start(); } //-------------------------------------------------------------------------------- void PagerButton::dragLeaveEvent(QDragLeaveEvent *event) { event->accept(); dragDropTimer.stop(); } //-------------------------------------------------------------------------------- void PagerButton::dropEvent(QDropEvent *event) { dragDropTimer.stop(); if ( !event->mimeData()->hasFormat("application/x-winId") ) { event->ignore(); return; } event->acceptProposedAction(); WId wid = static_cast(event->mimeData()->data("application/x-winId").toInt()); KWindowSystem::setOnDesktop(wid, desktop); } //-------------------------------------------------------------------------------- diff --git a/PagerButton.hxx b/PagerButton.hxx index 49d1db8..6e9585d 100644 --- a/PagerButton.hxx +++ b/PagerButton.hxx @@ -1,58 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017, 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _PagerButton_H_ #define _PagerButton_H_ #include #include class DesktopPanel; #include class PagerButton : public QPushButton { Q_OBJECT public: PagerButton(int num, DesktopPanel *panel, bool showIcon); int getDesktop() const { return desktop; } QSize sizeHint() const override; protected: void paintEvent(QPaintEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; void dragLeaveEvent(QDragLeaveEvent *event) override; void dropEvent(QDropEvent *event) override; private Q_SLOTS: void createPixmap(); void windowChanged(WId id, NET::Properties props, NET::Properties2 props2); private: int desktop; DesktopPanel *panel; bool showIcon; QPixmap firstPixmap; QTimer dragDropTimer; }; #endif diff --git a/PictureFrameApplet.cxx b/PictureFrameApplet.cxx index c6d95d0..28478dd 100644 --- a/PictureFrameApplet.cxx +++ b/PictureFrameApplet.cxx @@ -1,122 +1,123 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include //-------------------------------------------------------------------------------- PictureFrameApplet::PictureFrameApplet(QWidget *parent, const QString &theId) : DesktopApplet(parent, theId) { setAutoFillBackground(true); setContentsMargins(2, 2, 2, 2); } //-------------------------------------------------------------------------------- QSize PictureFrameApplet::sizeHint() const { return QSize(400, 400); } //-------------------------------------------------------------------------------- void PictureFrameApplet::resizeEvent(QResizeEvent *) { loadImage(); } //-------------------------------------------------------------------------------- void PictureFrameApplet::paintEvent(QPaintEvent *) { QPainter painter(this); painter.drawPixmap(contentsRect().x() + (contentsRect().width() - pixmap.width()) / 2, contentsRect().y() + (contentsRect().height() - pixmap.height()) / 2, pixmap); const int frameWidth = contentsMargins().left(); const int fw2 = frameWidth / 2; QPen pen(palette().color(foregroundRole()), frameWidth); pen.setJoinStyle(Qt::MiterJoin); painter.setPen(pen); painter.drawRect(rect().adjusted(fw2, fw2, -fw2, -fw2)); } //-------------------------------------------------------------------------------- void PictureFrameApplet::loadConfig() { KConfig config; KConfigGroup group = config.group(id); imagePath = group.readEntry("imagePath", QString()); DesktopApplet::loadConfig(); loadImage(); } //-------------------------------------------------------------------------------- void PictureFrameApplet::loadImage() { pixmap.load(imagePath); if ( !pixmap.isNull() ) { pixmap = pixmap.scaled(contentsRect().size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); update(); } } //-------------------------------------------------------------------------------- void PictureFrameApplet::configure() { if ( dialog ) { dialog->raise(); dialog->activateWindow(); return; } dialog = new PictureFrameAppletConfigureDialog(this); dialog->setWindowTitle(i18n("Configure PictureFrame Applet")); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); connect(dialog.data(), &QDialog::accepted, this, [this]() { saveConfig(); KConfig config; KConfigGroup group = config.group(id); group.writeEntry("imagePath", imagePath); }); } //-------------------------------------------------------------------------------- diff --git a/PictureFrameApplet.hxx b/PictureFrameApplet.hxx index 3aa18a4..c84d8d2 100644 --- a/PictureFrameApplet.hxx +++ b/PictureFrameApplet.hxx @@ -1,56 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _PictureFrameApplet_H_ #define _PictureFrameApplet_H_ #include #include #include class PictureFrameApplet : public DesktopApplet { Q_OBJECT public: PictureFrameApplet(QWidget *parent, const QString &theId); void loadConfig() override; QSize sizeHint() const override; QString getImagePath() const { return imagePath; } void setImagePath(const QString &path) { imagePath = path; loadImage(); } public Q_SLOTS: void configure() override; protected: void resizeEvent(QResizeEvent *event) override; void paintEvent(QPaintEvent *event) override; private: void loadImage(); private: QPixmap pixmap; QString imagePath; QPointer dialog; }; #endif diff --git a/PictureFrameAppletConfigureDialog.cxx b/PictureFrameAppletConfigureDialog.cxx index a864353..cc45d13 100644 --- a/PictureFrameAppletConfigureDialog.cxx +++ b/PictureFrameAppletConfigureDialog.cxx @@ -1,47 +1,48 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include //-------------------------------------------------------------------------------- PictureFrameAppletConfigureDialog::PictureFrameAppletConfigureDialog(PictureFrameApplet *parent) : QDialog(parent), applet(parent) { ui.setupUi(this); ui.textColor->setColor(applet->palette().color(applet->foregroundRole())); ui.backgroundColor->setColor(applet->palette().color(applet->backgroundRole())); ui.imagePath->setUrl(QUrl::fromLocalFile(applet->getImagePath())); } //-------------------------------------------------------------------------------- void PictureFrameAppletConfigureDialog::accept() { QPalette pal = applet->palette(); pal.setColor(applet->foregroundRole(), ui.textColor->color()); pal.setColor(applet->backgroundRole(), ui.backgroundColor->color()); applet->setPalette(pal); applet->setImagePath(ui.imagePath->url().toLocalFile()); QDialog::accept(); } //-------------------------------------------------------------------------------- diff --git a/PictureFrameAppletConfigureDialog.hxx b/PictureFrameAppletConfigureDialog.hxx index ee61333..09a24aa 100644 --- a/PictureFrameAppletConfigureDialog.hxx +++ b/PictureFrameAppletConfigureDialog.hxx @@ -1,42 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _PictureFrameAppletConfigureDialog_H_ #define _PictureFrameAppletConfigureDialog_H_ #include #include class PictureFrameApplet; class PictureFrameAppletConfigureDialog : public QDialog { Q_OBJECT public: PictureFrameAppletConfigureDialog(PictureFrameApplet *parent); private Q_SLOTS: void accept() override; private: PictureFrameApplet *applet; Ui::PictureFrameAppletConfigureDialog ui; }; #endif diff --git a/PkUpdateList.cxx b/PkUpdateList.cxx index 011c5e3..8258a30 100644 --- a/PkUpdateList.cxx +++ b/PkUpdateList.cxx @@ -1,583 +1,584 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#define TEST_LOGOUT //#define TEST_REBOOT //-------------------------------------------------------------------------------- PkUpdateListItem::PkUpdateListItem(QWidget *parent, PackageKit::Transaction::Info info, const PkUpdates::PackageData &data) : QWidget(parent), package(data) { setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); QGridLayout *grid = new QGridLayout(this); grid->setContentsMargins(QMargins()); grid->setSpacing(0); checkBox = new QCheckBox; checkBox->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); grid->addWidget(checkBox, 0, 0); QString icon; switch ( info ) { case PackageKit::Transaction::InfoSecurity: icon = "update-high"; break; case PackageKit::Transaction::InfoImportant: icon = "update-medium"; break; case PackageKit::Transaction::InfoBugfix: icon = "update-low"; break; default: ; } QLabel *iconLabel = new QLabel; iconLabel->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); iconLabel->setPixmap(QIcon::fromTheme(icon).pixmap(16)); grid->addWidget(iconLabel, 0, 1); checkBox->setChecked(!icon.isEmpty()); // auto-check the important ones connect(checkBox, &QCheckBox::toggled, this, &PkUpdateListItem::toggled); label = new QToolButton; label->setAutoRaise(true); label->setText(package.summary + " [" + PackageKit::Daemon::packageName(package.id) + ", " + PackageKit::Daemon::packageVersion(package.id) + ']'); grid->addWidget(label, 0, 2, Qt::AlignLeft); detailsLabel = new QLabel; detailsLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); detailsLabel->setIndent(30); detailsLabel->setAlignment(Qt::AlignLeft); grid->addWidget(detailsLabel, 1, 2); detailsLabel->hide(); connect(label, &QToolButton::clicked, this, &PkUpdateListItem::getUpdateDetails); { QHBoxLayout *progressHbox = new QHBoxLayout; progressHbox->setSpacing(10); progress = new QProgressBar; progress->setFixedWidth(300); progressHbox->addWidget(progress); errorLabel = new QLabel; errorLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); errorLabel->hide(); progressHbox->addWidget(errorLabel); packageLabel = new QLabel; packageLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); packageLabel->hide(); progressHbox->addWidget(packageLabel); grid->addLayout(progressHbox, 2, 2, Qt::AlignLeft); showProgress(false); } } //-------------------------------------------------------------------------------- void PkUpdateListItem::showProgress(bool yes) { progress->setValue(0); progress->setVisible(yes); } //-------------------------------------------------------------------------------- void PkUpdateListItem::getUpdateDetails() { if ( !detailsLabel->isHidden() ) { detailsLabel->hide(); return; } //qDebug() << "getUpdateDetails" << package.id; PackageKit::Transaction *transaction = PackageKit::Daemon::getUpdateDetail(package.id); detailsLabel->setText(i18n("Getting details ...")); detailsLabel->show(); connect(transaction, &PackageKit::Transaction::updateDetail, this, &PkUpdateListItem::updateDetail); connect(transaction, &PackageKit::Transaction::errorCode, this, [this](PackageKit::Transaction::Error error, const QString &details) { Q_UNUSED(error) detailsLabel->setText(details); }); } //-------------------------------------------------------------------------------- void PkUpdateListItem::updateDetail(const QString &packageID, const QStringList &updates, const QStringList &obsoletes, const QStringList &vendorUrls, const QStringList &bugzillaUrls, const QStringList &cveUrls, PackageKit::Transaction::Restart restart, const QString &updateText, const QString &changelog, PackageKit::Transaction::UpdateState state, const QDateTime &issued, const QDateTime &updated) { Q_UNUSED(packageID) Q_UNUSED(updates) Q_UNUSED(obsoletes) Q_UNUSED(vendorUrls) Q_UNUSED(bugzillaUrls) Q_UNUSED(cveUrls) Q_UNUSED(changelog) Q_UNUSED(state) Q_UNUSED(issued) Q_UNUSED(updated) QString text; if ( restart == PackageKit::Transaction::RestartSession ) text = i18n("Session restart required
"); else if ( restart == PackageKit::Transaction::RestartSystem ) text = i18n("Reboot required
"); text += updateText.trimmed(); text.replace("\n", "
"); detailsLabel->setText(text); } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- PkUpdateList::PkUpdateList(QWidget *parent) : QWidget(parent), restart(PackageKit::Transaction::RestartNone) { setWindowFlags(windowFlags() | Qt::Tool); setWindowTitle(i18n("Software Updates")); QVBoxLayout *vbox = new QVBoxLayout(this); vbox->setContentsMargins(QMargins(0, -1, 0, 0)); // action buttons QHBoxLayout *hbox = new QHBoxLayout; QCheckBox *checkAll = new QCheckBox(i18n("All")); connect(checkAll, &QCheckBox::toggled, this, &PkUpdateList::checkAll); filterEdit = new QLineEdit; filterEdit->setPlaceholderText(i18n("Filter")); filterEdit->setClearButtonEnabled(true); connect(filterEdit, &QLineEdit::textEdited, this, &PkUpdateList::filterChanged); installButton = new QPushButton(i18n("Install")); installButton->setEnabled(false); connect(installButton, &QPushButton::clicked, this, &PkUpdateList::install); refreshButton = new QPushButton(i18n("Refresh")); connect(refreshButton, &QPushButton::clicked, this, [this]() { setPackages(PkUpdates::PackageList()); emit refreshRequested(); }); hbox->addWidget(checkAll); hbox->addWidget(filterEdit); hbox->addWidget(installButton); hbox->addWidget(refreshButton); vbox->addLayout(hbox); // list of items in the order: security, important, bugfix, others QScrollArea *scrollArea = new QScrollArea; scrollArea->setWidgetResizable(true); vbox->addWidget(scrollArea); QWidget *w = new QWidget; vbox = new QVBoxLayout(w); itemsLayout = new QVBoxLayout; itemsLayout->setSpacing(0); vbox->addLayout(itemsLayout); vbox->addStretch(); scrollArea->setWidget(w); } //-------------------------------------------------------------------------------- void PkUpdateList::setPackages(const PkUpdates::PackageList &packages) { QLayoutItem *child; while ( (child = itemsLayout->takeAt(0)) ) { delete child->widget(); delete child; } QList list = packages.values(PackageKit::Transaction::InfoSecurity); for (const PkUpdates::PackageData &data : list) { auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), PackageKit::Transaction::InfoSecurity, data); connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked); itemsLayout->addWidget(item); item->show(); } list = packages.values(PackageKit::Transaction::InfoImportant); for (const PkUpdates::PackageData &data : list) { auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), PackageKit::Transaction::InfoImportant, data); connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked); itemsLayout->addWidget(item); item->show(); } list = packages.values(PackageKit::Transaction::InfoBugfix); for (const PkUpdates::PackageData &data : list) { auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), PackageKit::Transaction::InfoBugfix, data); connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked); itemsLayout->addWidget(item); item->show(); } // all the others for (PkUpdates::PackageList::const_iterator it = packages.constBegin(); it != packages.constEnd(); ++it) { if ( (it.key() != PackageKit::Transaction::InfoSecurity) && (it.key() != PackageKit::Transaction::InfoImportant) && (it.key() != PackageKit::Transaction::InfoBugfix) ) { auto *item = new PkUpdateListItem(itemsLayout->parentWidget(), it.key(), it.value()); connect(item, &PkUpdateListItem::toggled, this, &PkUpdateList::countChecked); itemsLayout->addWidget(item); item->show(); } } filterChanged(filterEdit->text()); countChecked(); } //-------------------------------------------------------------------------------- void PkUpdateList::checkAll(bool on) { for (int i = 0; i < itemsLayout->count(); i++) { PkUpdateListItem *item = qobject_cast(itemsLayout->itemAt(i)->widget()); if ( item && !item->isHidden() ) { item->checkBox->blockSignals(true); item->checkBox->setChecked(on); item->checkBox->blockSignals(false); } } countChecked(); } //-------------------------------------------------------------------------------- void PkUpdateList::countChecked() { int count = 0; for (int i = 0; i < itemsLayout->count(); i++) { PkUpdateListItem *item = qobject_cast(itemsLayout->itemAt(i)->widget()); if ( item && !item->isHidden() && item->checkBox->isChecked() ) count++; } if ( count ) { installButton->setText(i18np("Install %1 package", "Install %1 packages", count)); installButton->setEnabled(true); } else { installButton->setText(i18n("Install")); installButton->setEnabled(false); } installButton->setIcon(QIcon()); refreshButton->setEnabled(true); } //-------------------------------------------------------------------------------- void PkUpdateList::filterChanged(const QString &text) { itemsLayout->parentWidget()->layout()->setEnabled(false); for (int i = 0; i < itemsLayout->count(); i++) { PkUpdateListItem *item = qobject_cast(itemsLayout->itemAt(i)->widget()); item->setVisible(text.isEmpty() || (item->label->text().indexOf(text, 0, Qt::CaseInsensitive) != -1)); } itemsLayout->parentWidget()->layout()->setEnabled(true); countChecked(); } //-------------------------------------------------------------------------------- void PkUpdateList::install() { if ( !installQ.isEmpty() ) // installation in progress; cancel it { QPointer currentItem = installQ.head(); if ( transaction ) { QDBusPendingReply<> reply = transaction->cancel(); reply.waitForFinished(); if ( reply.isError() && currentItem ) { currentItem->errorLabel->setText(reply.error().message()); currentItem->errorLabel->show(); } } for (int i = 0; i < itemsLayout->count(); i++) { PkUpdateListItem *item = qobject_cast(itemsLayout->itemAt(i)->widget()); if ( !transaction || (item != currentItem) ) { item->showProgress(false); item->errorLabel->hide(); } } installQ.clear(); countChecked(); return; } restart = PackageKit::Transaction::RestartNone; for (int i = 0; i < itemsLayout->count(); i++) { QPointer item = qobject_cast(itemsLayout->itemAt(i)->widget()); if ( item && !item->isHidden() && item->checkBox->isChecked() ) { item->showProgress(true); item->errorLabel->hide(); item->detailsLabel->hide(); installQ.enqueue(item); } } installOne(); } //-------------------------------------------------------------------------------- void PkUpdateList::installOne() { if ( installQ.isEmpty() ) // installation finished { if ( restart != PackageKit::Transaction::RestartNone ) { QString text; if ( (restart == PackageKit::Transaction::RestartSystem) || (restart == PackageKit::Transaction::RestartSecuritySystem) ) { KNotification *notif = new KNotification("restart needed", parentWidget(), KNotification::Persistent); notif->setTitle(i18n("System Reboot Required")); notif->setText(i18n("One of the installed packages requires a system reboot")); notif->setActions(QStringList() << i18n("Reboot System")); connect(notif, &KNotification::action1Activated, this, []() { QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface", "logout"); msg << 0/*no confirm*/ << 1/*reboot*/ << 0; // plasma-workspace/libkworkspace/kworkspace.h QDBusConnection::sessionBus().send(msg); }); notif->sendEvent(); } else if ( (restart == PackageKit::Transaction::RestartSession) || (restart == PackageKit::Transaction::RestartSecuritySession) ) { KNotification *notif = new KNotification("restart needed", parentWidget(), KNotification::Persistent); notif->setTitle(i18n("Session Restart Required")); notif->setText(i18n("One of the installed packages requires you to logout")); notif->setActions(QStringList() << i18n("Logout")); connect(notif, &KNotification::action1Activated, this, []() { QDBusMessage msg = QDBusMessage::createMethodCall("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface", "logout"); msg << 0/*no confirm*/ << 0/*logout*/ << 0; // plasma-workspace/libkworkspace/kworkspace.h QDBusConnection::sessionBus().send(msg); }); notif->sendEvent(); } } countChecked(); return; } QPointer item = installQ.head(); if ( !item ) return; installButton->setText(i18n("Cancel Installation")); installButton->setIcon(QIcon::fromTheme("process-stop")); refreshButton->setEnabled(false); PackageKit::Transaction::TransactionFlag flag = PackageKit::Transaction::TransactionFlagOnlyTrusted; #ifdef TEST_LOGOUT flag |= PackageKit::Transaction::TransactionFlagSimulate; restart = PackageKit::Transaction::RestartSession; #elif defined TEST_REBOOT flag |= PackageKit::Transaction::TransactionFlagSimulate; restart = PackageKit::Transaction::RestartSystem; #endif transaction = PackageKit::Daemon::updatePackage(item->package.id, flag); //qDebug() << "installing" << item->package.id; connect(transaction.data(), &PackageKit::Transaction::statusChanged, this, [item, this]() { if ( !item ) // already deleted return; //qDebug() << "status" << transaction->status(); QString text; switch ( transaction->status() ) { case PackageKit::Transaction::StatusWait: text = i18n("Waiting"); break; case PackageKit::Transaction::StatusWaitingForAuth: text = i18n("Waiting for authentication"); break; case PackageKit::Transaction::StatusDepResolve: text = i18n("Resolving dependencies"); break; case PackageKit::Transaction::StatusUpdate: text = i18n("Updating"); break; case PackageKit::Transaction::StatusInstall: text = i18n("Installing"); break; case PackageKit::Transaction::StatusDownload: text = i18n("Downloading"); break; case PackageKit::Transaction::StatusCancel: text = i18n("Canceling"); break; default: ; } if ( text.isEmpty() ) item->errorLabel->hide(); else { item->errorLabel->setText(text); item->errorLabel->show(); } }); connect(transaction.data(), &PackageKit::Transaction::itemProgress, this, [item](const QString &itemID, PackageKit::Transaction::Status status, uint percentage) { Q_UNUSED(status) if ( !item ) // already deleted return; item->packageLabel->setText(PackageKit::Daemon::packageName(itemID)); item->packageLabel->show(); if ( percentage <= 100 ) // 101 .. unknown item->progress->setValue(percentage); }); connect(transaction.data(), &PackageKit::Transaction::requireRestart, this, [this](PackageKit::Transaction::Restart type, const QString &/*packageID*/) { // keep most important restart type: System, Session if ( (type == PackageKit::Transaction::RestartSystem) || (type == PackageKit::Transaction::RestartSecuritySystem) || (((type == PackageKit::Transaction::RestartSession) || (type == PackageKit::Transaction::RestartSecuritySession)) && (restart != PackageKit::Transaction::RestartSystem) && (restart != PackageKit::Transaction::RestartSecuritySystem)) ) restart = type; }); connect(transaction.data(), &PackageKit::Transaction::errorCode, this, [item](PackageKit::Transaction::Error error, const QString &details) { Q_UNUSED(error) if ( !item ) return; item->showProgress(false); item->errorLabel->setText(details); item->errorLabel->show(); }); connect(transaction.data(), &PackageKit::Transaction::finished, this, [item, this](PackageKit::Transaction::Exit status, uint runtime) { Q_UNUSED(runtime) if ( status == PackageKit::Transaction::ExitSuccess ) { item->hide(); item->deleteLater(); emit packageInstalled(item->package.id); } else { item->showProgress(false); item->detailsLabel->setText(i18n("Update failed")); item->detailsLabel->show(); } if ( !installQ.isEmpty() ) // might have been cancelled, q cleared installQ.dequeue(); installOne(); }); } //-------------------------------------------------------------------------------- diff --git a/PkUpdateList.hxx b/PkUpdateList.hxx index 738894e..e4c9ebb 100644 --- a/PkUpdateList.hxx +++ b/PkUpdateList.hxx @@ -1,107 +1,108 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _PkUpdateList_H_ #define _PkUpdateList_H_ #include #include #include #include class QCheckBox; class QProgressBar; class QVBoxLayout; class QToolButton; class QPushButton; class QLabel; class QLineEdit; class PkUpdateList : public QWidget { Q_OBJECT public: PkUpdateList(QWidget *parent); void setPackages(const PkUpdates::PackageList &packages); Q_SIGNALS: void refreshRequested(); void packageInstalled(QString id); private Q_SLOTS: void checkAll(bool on); void install(); void installOne(); void countChecked(); void filterChanged(const QString &text); private: QVBoxLayout *itemsLayout; QLineEdit *filterEdit; QPushButton *installButton; QPushButton *refreshButton; QQueue> installQ; QPointer transaction; PackageKit::Transaction::Restart restart; }; //-------------------------------------------------------------------------------- class PkUpdateListItem : public QWidget { Q_OBJECT public: PkUpdateListItem(QWidget *parent, PackageKit::Transaction::Info info, const PkUpdates::PackageData &data); void showProgress(bool yes); PkUpdates::PackageData package; QToolButton *label; QCheckBox *checkBox; QProgressBar *progress; QToolButton *cancelButton; QLabel *detailsLabel; QLabel *errorLabel; QLabel *packageLabel; Q_SIGNALS: void toggled(); private Q_SLOTS: void getUpdateDetails(); void updateDetail(const QString &packageID, const QStringList &updates, const QStringList &obsoletes, const QStringList &vendorUrls, const QStringList &bugzillaUrls, const QStringList &cveUrls, PackageKit::Transaction::Restart restart, const QString &updateText, const QString &changelog, PackageKit::Transaction::UpdateState state, const QDateTime &issued, const QDateTime &updated); }; #endif diff --git a/PkUpdates.cxx b/PkUpdates.cxx index 2918ee8..f652d6c 100644 --- a/PkUpdates.cxx +++ b/PkUpdates.cxx @@ -1,285 +1,286 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017,2019 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- PkUpdates::PkUpdates(QWidget *parent) : SysTrayItem(parent) { KConfig config; KConfigGroup group = config.group("SoftwareUpdates"); if ( !group.hasKey("enabled") ) // create config entry so that one knows it exists group.writeEntry("enabled", true); bool isEnabled = group.readEntry("enabled", true); if ( !isEnabled ) { hide(); return; } setPixmap(QIcon::fromTheme("system-software-update").pixmap(size())); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, [this]() { createToolTip(); }); // check every hour if the next checkpoint was reached. This ensures that // we check for updates even when the computer was suspended for a while // and therefore a normal QTimer timeout for 1 day will not be reached in 1 day // TODO check again. start() was missing updateTimer.setInterval(3600 * 1000); updateTimer.start(); connect(&updateTimer, &QTimer::timeout, this, &PkUpdates::checkForUpdatesReached); //QTimer::singleShot(0, this, &PkUpdates::checkForUpdatesReached); } //-------------------------------------------------------------------------------- void PkUpdates::checkForUpdatesReached() { QDateTime current = QDateTime::currentDateTime(); if ( !nextCheck.isValid() || (current >= nextCheck) ) { checkForUpdates(); nextCheck = current.addDays(1); } } //-------------------------------------------------------------------------------- void PkUpdates::checkForUpdates() { setPixmap(QIcon::fromTheme("system-software-update").pixmap(size())); setToolTip(i18n("Checking for updates ...")); packages.clear(); PackageKit::Transaction *transaction = PackageKit::Daemon::refreshCache(true); connect(transaction, &PackageKit::Transaction::errorCode, this, &PkUpdates::transactionError); connect(transaction, &PackageKit::Transaction::finished, this, &PkUpdates::refreshFinished); } //-------------------------------------------------------------------------------- void PkUpdates::refreshFinished(PackageKit::Transaction::Exit status, uint runtime) { Q_UNUSED(runtime) if ( status != PackageKit::Transaction::ExitSuccess ) return; PackageKit::Transaction *transaction = PackageKit::Daemon::getUpdates(); connect(transaction, &PackageKit::Transaction::package, this, &PkUpdates::package); connect(transaction, &PackageKit::Transaction::errorCode, this, &PkUpdates::transactionError); connect(transaction, &PackageKit::Transaction::finished, this, [this]() { if ( updateList ) updateList->setPackages(packages); createToolTip(true); }); connect(transaction, &PackageKit::Transaction::percentageChanged, this, [this, transaction]() { if ( (transaction->percentage() <= 100) && (transaction->status() != PackageKit::Transaction::StatusFinished) ) setToolTip(i18n("Checking for updates ... %1%", transaction->percentage())); }); } //-------------------------------------------------------------------------------- void PkUpdates::package(PackageKit::Transaction::Info info, const QString &packageID, const QString &summary) { PackageData pkg; pkg.id = packageID; pkg.summary = summary; packages.insert(info, pkg); } //-------------------------------------------------------------------------------- void PkUpdates::transactionError(PackageKit::Transaction::Error error, const QString &details) { Q_UNUSED(error) setToolTip(i18n("Last check: %1\nError on checking for updates: %2", QDateTime::currentDateTime().toString(Qt::SystemLocaleShortDate), details)); KNotification::event("update error", i18n("Software Update Error"), details, QIcon::fromTheme("dialog-error").pixmap(32), this); } //-------------------------------------------------------------------------------- void PkUpdates::createToolTip(bool notify) { PackageKit::Transaction::Info info = PackageKit::Transaction::InfoUnknown; QString tooltip; int count = 0; QList list = packages.values(PackageKit::Transaction::InfoSecurity); if ( list.count() ) { info = PackageKit::Transaction::InfoSecurity; count += list.count(); tooltip += i18np("%1 security update available", "%1 security updates available", list.count()); addItems(tooltip, list); } list = packages.values(PackageKit::Transaction::InfoImportant); if ( list.count() ) { if ( info == PackageKit::Transaction::InfoUnknown ) info = PackageKit::Transaction::InfoImportant; count += list.count(); tooltip += i18np("%1 important update available", "%1 important updates available", list.count()); addItems(tooltip, list); } list = packages.values(PackageKit::Transaction::InfoBugfix); if ( list.count() ) { if ( info == PackageKit::Transaction::InfoUnknown ) info = PackageKit::Transaction::InfoBugfix; count += list.count(); tooltip += i18np("%1 bugfix update available", "%1 bugfix updates available", list.count()); addItems(tooltip, list); } int others = packages.count() - count; if ( tooltip.isEmpty() ) { if ( others ) { setToolTip(i18np("Last check: %1\nNo important updates available\n%2 other", "Last check: %1\nNo important updates available\n%2 others", QDateTime::currentDateTime().toString(Qt::SystemLocaleShortDate), others)); } else { setToolTip(i18n("Last check: %1\nNo important updates available", QDateTime::currentDateTime().toString(Qt::SystemLocaleShortDate))); } setPixmap(QIcon::fromTheme("update-none").pixmap(size())); } else { if ( others ) tooltip += i18np("
%1 other", "
%1 others", others); tooltip = i18n("Last check: %1
%2", QDateTime::currentDateTime().toString(Qt::SystemLocaleShortDate), tooltip); setToolTip(tooltip); QString icon; switch ( info ) { case PackageKit::Transaction::InfoSecurity: icon = "update-high"; break; case PackageKit::Transaction::InfoImportant: icon = "update-medium"; break; case PackageKit::Transaction::InfoBugfix: icon = "update-low"; break; default: ; } setPixmap(QIcon::fromTheme(icon).pixmap(size())); if ( notify ) { KNotification::event("updates available", i18n("Software Updates Available"), tooltip, QIcon::fromTheme(icon).pixmap(32), this, KNotification::Persistent); } } } //-------------------------------------------------------------------------------- void PkUpdates::addItems(QString &tooltip, const QList &list) const { tooltip += "
    "; int count = std::min(3, list.count()); if ( list.count() == 4 ) // if there's just one more, show it directly instead of "1 more" count++; for (int i = 0; i < count; i++) tooltip += "
  • " + list[i].summary + "
  • "; if ( list.count() > 4 ) tooltip += i18n("
  • %1 more ...
  • ", list.count() - count); tooltip += "
"; } //-------------------------------------------------------------------------------- QWidget *PkUpdates::getDetailsList() { if ( !updateList ) { updateList = new PkUpdateList(this); updateList->setPackages(packages); connect(updateList, &PkUpdateList::refreshRequested, this, &PkUpdates::checkForUpdates); connect(updateList, &PkUpdateList::packageInstalled, this, &PkUpdates::packageInstalled); } return updateList; } //-------------------------------------------------------------------------------- void PkUpdates::packageInstalled(const QString &id) { for (PackageList::iterator it = packages.begin(); it != packages.end(); ++it) { if ( (*it).id == id ) { packages.erase(it); break; } } createToolTip(); } //-------------------------------------------------------------------------------- diff --git a/PkUpdates.hxx b/PkUpdates.hxx index 02ff363..045abdb 100644 --- a/PkUpdates.hxx +++ b/PkUpdates.hxx @@ -1,70 +1,71 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _PkUpdates_H_ #define _PkUpdates_H_ // software updates via PackageKit #include #include #include #include #include class PkUpdateList; class PkUpdates : public SysTrayItem { Q_OBJECT public: PkUpdates(QWidget *parent); struct PackageData { QString id; QString summary; }; typedef QMultiMap PackageList; protected: QWidget *getDetailsList() override; private Q_SLOTS: void checkForUpdatesReached(); void checkForUpdates(); void refreshFinished(PackageKit::Transaction::Exit status, uint runtime); void package(PackageKit::Transaction::Info info, const QString &packageID, const QString &summary); void transactionError(PackageKit::Transaction::Error error, const QString &details); void packageInstalled(const QString &id); private: void addItems(QString &tooltip, const QList &list) const; void createToolTip(bool notify = false); private: PackageList packages; QTimer updateTimer; QDateTime nextCheck; PkUpdateList *updateList = nullptr; }; #endif diff --git a/PopupMenu.cxx b/PopupMenu.cxx index 9309a18..fe3fd6a 100644 --- a/PopupMenu.cxx +++ b/PopupMenu.cxx @@ -1,78 +1,79 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- void PopupMenu::mousePressEvent(QMouseEvent *event) { if ( event->button() == Qt::LeftButton ) dragStartPos = event->pos(); QMenu::mousePressEvent(event); } //-------------------------------------------------------------------------------- void PopupMenu::mouseMoveEvent(QMouseEvent *event) { if ( (event->buttons() == Qt::LeftButton) && (dragStartPos != QPoint(-1, -1)) && (event->pos() - dragStartPos).manhattanLength() > QGuiApplication::styleHints()->startDragDistance() ) { dragStartPos = QPoint(-1, -1); QAction *action = actionAt(event->pos()); if ( action && !action->menu() && action->data().isValid() ) { event->accept(); QDrag *drag = new QDrag(this); QMimeData *mimeData = new QMimeData; Qt::DropActions dropAction; if ( static_cast(action->data().type()) == QMetaType::QUrl ) { mimeData->setUrls(QList() << action->data().toUrl()); dropAction = Qt::CopyAction; } else if ( static_cast(action->data().type()) == QMetaType::Int ) { mimeData->setData("application/x-winId", QByteArray::number(action->data().toInt())); dropAction = Qt::MoveAction; } else qDebug() << "illegal data in PopupMenu action"; // should never come here drag->setMimeData(mimeData); drag->setPixmap(action->icon().pixmap(32, 32)); drag->exec(dropAction); } } else QMenu::mouseMoveEvent(event); } //-------------------------------------------------------------------------------- diff --git a/PopupMenu.hxx b/PopupMenu.hxx index 19f6cc9..3665d4f 100644 --- a/PopupMenu.hxx +++ b/PopupMenu.hxx @@ -1,42 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _PopupMenu_H_ #define _PopupMenu_H_ // A QMenu with drag functionality #include class PopupMenu : public QMenu { Q_OBJECT public: PopupMenu(QWidget *parent) : QMenu(parent), dragStartPos(-1, -1) { } protected: void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; private: QPoint dragStartPos; }; #endif diff --git a/QuickLaunch.cxx b/QuickLaunch.cxx index fc1fced..3c1078d 100644 --- a/QuickLaunch.cxx +++ b/QuickLaunch.cxx @@ -1,156 +1,157 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- QuickLaunch::QuickLaunch(DesktopPanel *parent) : Launcher(parent, "QuickLaunch") { QFrame *frame = new QFrame; frame->setFrameShape(QFrame::StyledPanel); grid = new QGridLayout(frame); grid->setContentsMargins(QMargins()); grid->setSpacing(2); layout()->addWidget(frame); loadConfig(); connect(parent, &DesktopPanel::rowsChanged, this, &QuickLaunch::fill); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &QuickLaunch::fill); } //-------------------------------------------------------------------------------- void QuickLaunch::fill() { const int MAX_ROWS = qobject_cast(parentWidget())->getRows(); QLayoutItem *child; while ( (child = grid->takeAt(0)) ) { delete child->widget(); delete child; } if ( !dirPath.isEmpty() ) { QDir dir(dirPath); QFileInfoList entries = dir.entryInfoList(QDir::Files); int row = 0, col = 0; for (const QFileInfo &info : entries) { QUrl url(QUrl::fromLocalFile(info.absoluteFilePath())); QIcon icon; QString name = info.fileName(); KFileItem item(url); if ( item.isDesktopFile() ) { KDesktopFile desktopFile(info.absoluteFilePath()); if ( desktopFile.noDisplay() ) continue; name = desktopFile.readName(); if ( name.isEmpty() ) name = desktopFile.readGenericName(); QString iconName = desktopFile.readIcon(); icon = QIcon::fromTheme(iconName.isEmpty() ? name : iconName); } else { QMimeDatabase db; icon = QIcon::fromTheme(db.mimeTypeForFile(info.absoluteFilePath()).iconName()); } QToolButton *button = new QToolButton(this); button->setAutoRaise(true); button->setIcon(icon); button->setToolTip(name); if ( MAX_ROWS > 1 ) button->setIconSize(QSize(22, 22)); else { int size = KIconLoader::global()->currentSize(KIconLoader::Panel); button->setIconSize(QSize(size, size)); } connect(button, &QToolButton::clicked, [url]() { new KRun(url, nullptr); }); grid->addWidget(button, row, col, Qt::AlignCenter); row = (row + 1) % MAX_ROWS; if ( row == 0 ) col++; // limit width in case one selects a directory with too many items const int MAX_COLS = 15; if ( col > MAX_COLS ) break; } } if ( grid->count() == 0 ) { // add default entry QToolButton *button = new QToolButton(this); button->setAutoRaise(true); button->setIcon(QIcon::fromTheme("user-home")); button->setIconSize(QSize(22, 22)); button->setToolTip(QStandardPaths::displayName(QStandardPaths::HomeLocation)); QUrl url = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); connect(button, &QToolButton::clicked, [url]() { new KRun(url, nullptr); }); grid->addWidget(button, 0, 0, Qt::AlignCenter); button = new QToolButton(this); button->setAutoRaise(true); button->setIcon(QIcon::fromTheme("internet-web-browser")); button->setIconSize(QSize(22, 22)); button->setToolTip(i18n("Web Browser")); connect(button, &QToolButton::clicked, []() { new KRun(QUrl("http://www.kde.org"), nullptr); }); if ( MAX_ROWS == 1 ) grid->addWidget(button, 0, 1, Qt::AlignCenter); else grid->addWidget(button, 1, 0, Qt::AlignCenter); } } //-------------------------------------------------------------------------------- diff --git a/QuickLaunch.hxx b/QuickLaunch.hxx index b0f6d34..bc20798 100644 --- a/QuickLaunch.hxx +++ b/QuickLaunch.hxx @@ -1,42 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _QuickLaunch_H_ #define _QuickLaunch_H_ #include #include #include class QuickLaunch : public Launcher { Q_OBJECT public: QuickLaunch(DesktopPanel *parent); private Q_SLOTS: void fill() override; private: QGridLayout *grid; }; #endif diff --git a/StartMenu.cxx b/StartMenu.cxx index b90b0f1..6cf634b 100644 --- a/StartMenu.cxx +++ b/StartMenu.cxx @@ -1,183 +1,184 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- StartMenu::StartMenu(DesktopPanel *parent) : QToolButton(parent) { setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); setThemeIcon("liquidshell"); popup = new PopupMenu(this); connect(this, &QToolButton::pressed, this, &StartMenu::showMenu); fill(); adjustIconSize(); connect(KSycoca::self(), SIGNAL(databaseChanged()), this, SLOT(fill())); connect(parent, &DesktopPanel::rowsChanged, this, &StartMenu::adjustIconSize); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, &StartMenu::adjustIconSize); } //-------------------------------------------------------------------------------- void StartMenu::adjustIconSize() { const int MAX_ROWS = qobject_cast(parentWidget())->getRows(); if ( MAX_ROWS > 1 ) setIconSize(QSize(48, 48)); else { int size = KIconLoader::global()->currentSize(KIconLoader::Panel); setIconSize(QSize(size, size)); } } //-------------------------------------------------------------------------------- void StartMenu::setThemeIcon(const QString &icon) { themeIcon = icon; setIcon(QIcon::fromTheme(themeIcon)); } //-------------------------------------------------------------------------------- void StartMenu::fill() { popup->clear(); fillFromGroup(popup, KServiceGroup::root()); popup->addSeparator(); // add important actions QAction *action = popup->addAction(QIcon::fromTheme("system-switch-user"), i18n("Switch User")); connect(action, &QAction::triggered, []() { QDBusConnection::sessionBus().send( QDBusMessage::createMethodCall("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface", "openSwitchUserDialog")); }); KService::Ptr sysSettings = KService::serviceByDesktopName("systemsettings"); if ( sysSettings ) { action = popup->addAction(QIcon::fromTheme(sysSettings->icon()), sysSettings->name()); connect(action, &QAction::triggered, [this, sysSettings]() { KRun::runApplication(*sysSettings, QList(), this); }); } action = popup->addAction(QIcon::fromTheme("system-run"), i18n("Run Command...")); connect(action, &QAction::triggered, []() { QDBusConnection::sessionBus().send( QDBusMessage::createMethodCall("org.kde.krunner", "/App", "org.kde.krunner.App", "display")); }); } //-------------------------------------------------------------------------------- void StartMenu::fillFromGroup(QMenu *menu, KServiceGroup::Ptr group) { if ( !group || !group->isValid() ) return; QList groupEntries = group->groupEntries(static_cast(KServiceGroup::SortEntries | KServiceGroup::ExcludeNoDisplay)); for (KServiceGroup::Ptr groupEntry : groupEntries) { if ( groupEntry->childCount() == 0 ) continue; QMenu *submenu = new PopupMenu(menu); menu->addMenu(submenu); submenu->setTitle(groupEntry->caption()); submenu->setIcon(QIcon::fromTheme(groupEntry->icon())); fillFromGroup(submenu, groupEntry); } KService::List serviceEntries = group->serviceEntries(static_cast(KServiceGroup::SortEntries | KServiceGroup::ExcludeNoDisplay)); for (KService::Ptr serviceEntry : serviceEntries) { if ( !serviceEntry->isType(KST_KService) ) continue; QIcon icon = QIcon::fromTheme(serviceEntry->icon()); QString text = serviceEntry->name(); if ( !serviceEntry->genericName().isEmpty() && (serviceEntry->genericName() != serviceEntry->name()) ) text += QString(" (%1)").arg(serviceEntry->genericName()); QAction *action = menu->addAction(icon, text); action->setData(QUrl::fromLocalFile(serviceEntry->entryPath())); action->setToolTip(static_cast(serviceEntry.data())->comment()); connect(action, &QAction::triggered, [serviceEntry]() { KRun::runApplication(*serviceEntry, QList(), nullptr); }); } } //-------------------------------------------------------------------------------- void StartMenu::contextMenuEvent(QContextMenuEvent *event) { QMenu menu; QAction *action = menu.addAction(QIcon::fromTheme("configure"), i18n("Configure Menu...")); connect(action, &QAction::triggered, []() { KRun::runCommand(QString("kmenuedit"), nullptr); }); menu.exec(event->globalPos()); } //-------------------------------------------------------------------------------- void StartMenu::showMenu() { popup->adjustSize(); QPoint p = mapToGlobal(QPoint(0, 0)); popup->move(p.x(), p.y() - popup->sizeHint().height()); popup->exec(); setDown(false); } //-------------------------------------------------------------------------------- diff --git a/StartMenu.hxx b/StartMenu.hxx index 71a175d..f0dfd6b 100644 --- a/StartMenu.hxx +++ b/StartMenu.hxx @@ -1,58 +1,59 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _StartMenu_H_ #define _StartMenu_H_ #include #include #include #include class StartMenu : public QToolButton { Q_OBJECT Q_PROPERTY(QString themeIcon READ getThemeIcon WRITE setThemeIcon) public: StartMenu(DesktopPanel *parent); QString getThemeIcon() const { return themeIcon; } void setThemeIcon(const QString &icon); protected: void contextMenuEvent(QContextMenuEvent *event) override; private Q_SLOTS: void adjustIconSize(); void fill(); void showMenu(); private: void fillFromGroup(QMenu *menu, KServiceGroup::Ptr group); private: QString themeIcon; QMenu *popup; }; #endif diff --git a/SysLoad.cxx b/SysLoad.cxx index fb3fb4e..3763b58 100644 --- a/SysLoad.cxx +++ b/SysLoad.cxx @@ -1,316 +1,317 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017,2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- const int INTERVAL_MS = 800; const int NET_INTERVAL_S = 60; // to avoid that a constant small network traffic always shows a full bar, // we set some arbitrary higher maximum value for the scale const size_t NET_INIT_SCALE = 50 * 1024; //-------------------------------------------------------------------------------- SysLoad::SysLoad(QWidget *parent) : QFrame(parent) { maxScale = NET_INIT_SCALE; setFrameShape(QFrame::StyledPanel); timeoutTimer.setInterval(INTERVAL_MS); timeoutTimer.start(); connect(&timeoutTimer, &QTimer::timeout, this, &SysLoad::fetch); connect(NetworkManager::notifier(), &NetworkManager::Notifier::primaryConnectionChanged, [this]() { maxScale = NET_INIT_SCALE; maxBytes = 0; }); // reset since we're changing network (which might be slower) // gradually decrease max network throughput to really be able to see when network traffic occurs netLoadTimer.setInterval(NET_INTERVAL_S * 1000); netLoadTimer.start(); connect(&netLoadTimer, &QTimer::timeout, this, [this]() { maxBytes = 0; if ( maxScale > NET_INIT_SCALE ) maxScale /= 2; }); setFixedWidth(60); } //-------------------------------------------------------------------------------- void SysLoad::fetch() { QFile f("/proc/stat"); if ( !f.open(QIODevice::ReadOnly) ) return; int num = 0; bool first = cpus.isEmpty(); while ( true ) { QByteArray line = f.readLine(); if ( line.isEmpty() ) break; if ( line.startsWith("cpu") && !line.startsWith("cpu ") ) { // time is in 1/100s units https://www.kernel.org/doc/Documentation/filesystems/proc.txt CpuData data; sscanf(line.constData(), "%*s %d %d %d", &data.userCPU, &data.niceCPU, &data.systemCPU); if ( first ) cpus.append(data); else { cpus[num].userPercent = (data.userCPU - cpus[num].userCPU) * 1000.0 / INTERVAL_MS; cpus[num].nicePercent = (data.niceCPU - cpus[num].niceCPU) * 1000.0 / INTERVAL_MS; cpus[num].systemPercent = (data.systemCPU - cpus[num].systemCPU) * 1000.0 / INTERVAL_MS; cpus[num].userCPU = data.userCPU; cpus[num].niceCPU = data.niceCPU; cpus[num].systemCPU = data.systemCPU; num++; } } } f.close(); // get memory information size_t memTotal = 0, memFree = 0, swapTotal = 0, swapFree = 0, cached = 0, buffers = 0; f.setFileName("/proc/meminfo"); if ( f.open(QIODevice::ReadOnly) ) { while ( true ) { QByteArray line = f.readLine(); if ( line.isEmpty() ) break; if ( line.startsWith("MemTotal:") ) sscanf(line, "%*s %zd kB", &memTotal); else if ( line.startsWith("MemFree:") ) sscanf(line, "%*s %zd kB", &memFree); else if ( line.startsWith("SwapTotal:") ) sscanf(line, "%*s %zd kB", &swapTotal); else if ( line.startsWith("SwapFree:") ) sscanf(line, "%*s %zd kB", &swapFree); else if ( line.startsWith("Cached:") ) sscanf(line, "%*s %zd kB", &cached); else if ( line.startsWith("Buffers:") ) sscanf(line, "%*s %zd kB", &buffers); } cached += buffers; memData.memPercent = memTotal ? (double(memTotal - memFree) / double(memTotal)) * 100.0 : 0.0; memData.memCachedPercent = memTotal ? (double(cached) / double(memTotal)) * 100.0 : 0.0; memData.swapPercent = swapTotal ? (double(swapTotal - swapFree) / double(swapTotal)) * 100.0 : 0.0; f.close(); } f.setFileName("/proc/cpuinfo"); // get speed of cores if ( f.open(QIODevice::ReadOnly) ) { int num = 0; while ( true ) { QByteArray line = f.readLine(); if ( line.isEmpty() ) break; if ( line.startsWith("cpu MHz") ) { if ( num < cpus.count() ) sscanf(line, "cpu MHz %*s %lf", &cpus[num++].MHz); } } f.close(); } f.setFileName("/proc/net/dev"); sumSent = sumReceived = 0; if ( f.open(QIODevice::ReadOnly) ) { while ( true ) { QByteArray line = f.readLine(); if ( line.isEmpty() ) break; int colon = line.indexOf(':'); if ( colon > 1 ) { QByteArray device = line.left(colon).trimmed(); size_t received = 0, sent = 0; sscanf(line.data() + colon + 1, "%zd %*d %*d %*d %*d %*d %*d %*d %zd", &received, &sent); if ( !netDevs.contains(device) ) { NetworkData data; data.prevReceived = received; data.prevSent = sent; data.valid = false; // first scan not valid since we count differences netDevs.insert(device, data); } else { NetworkData &data = netDevs[device]; data.received = received - data.prevReceived; data.sent = sent - data.prevSent; data.prevReceived = received; data.prevSent = sent; data.valid = true; if ( (device != "lo") && // don't count loopback adapter !device.startsWith("tun") ) // TODO: correct ? { sumReceived += data.received; sumSent += data.sent; } } } } f.close(); } maxBytes = std::max(maxBytes, (sumReceived + sumSent)); maxScale = std::max(maxBytes, maxScale); update(); QString tip; for (int i = 0; i < cpus.count(); i++) { if ( i ) tip += "
"; tip += i18n("CPU %1: %2% (%3 MHz)", i, locale().toString(std::min(100.0, cpus[i].userPercent + cpus[i].nicePercent + cpus[i].systemPercent), 'f', 1), static_cast(cpus[i].MHz)); } tip += "
"; memFree += cached; // show also the cached memory as free (for user applications) size_t memUsed = memTotal - memFree; size_t swapUsed = swapTotal - swapFree; int memUsedPercent = memTotal ? memUsed * 100 / memTotal : 0; int swapUsedPercent = swapTotal ? swapUsed * 100 / swapTotal : 0; tip += i18n("Memory Total: %1 MB (%2 GB)" , memTotal / 1024, locale().toString(memTotal / 1024.0 / 1024.0, 'f', 2)); tip += "
"; tip += i18n("Memory Used: %1 MB (%2 GB) %3%", memUsed / 1024, locale().toString(memUsed / 1024.0 / 1024.0, 'f', 2), memUsedPercent); tip += "
"; tip += i18n("Memory Free: %1 MB (%2 GB)" , memFree / 1024, locale().toString(memFree / 1024.0 / 1024.0, 'f', 2)); tip += "
"; tip += i18n("Swap Total: %1 MB (%2 GB)" , swapTotal / 1024, locale().toString(swapTotal / 1024.0 / 1024.0, 'f', 2)); tip += "
"; tip += i18n("Swap Used: %1 MB (%2 GB) %3%" , swapUsed / 1024, locale().toString(swapUsed / 1024.0 / 1024.0, 'f', 2), swapUsedPercent); tip += "
"; tip += i18n("Swap Free: %1 MB (%2 GB)" , swapFree / 1024, locale().toString(swapFree / 1024.0 / 1024.0, 'f', 2)); tip += "
"; tip += i18n("Net send/receive: %1/%2 KB/sec", locale().toString((sumSent / 1024.0) / (INTERVAL_MS / 1000.0), 'f', 2), locale().toString((sumReceived / 1024.0) / (INTERVAL_MS / 1000.0), 'f', 2)); tip += "
"; tip += i18n("Net max (last %2 secs): %1 KB/sec", locale().toString((maxBytes / 1024.0) / (INTERVAL_MS / 1000.0), 'f', 2), NET_INTERVAL_S); if ( underMouse() ) QToolTip::showText(QCursor::pos(), QLatin1String("") + tip + QLatin1String(""), this, rect()); } //-------------------------------------------------------------------------------- void SysLoad::paintEvent(QPaintEvent *event) { QFrame::paintEvent(event); const int cpuBars = cpuSummaryBar ? 1 : cpus.count(); int const barWidth = contentsRect().width() / (cpuBars + 2 + 1); // mem usage 2 bars + netSum int x = contentsRect().x(), y = contentsRect().y() + contentsRect().height(); QPainter painter(this); // cpu QVector drawCpus; if ( !cpuSummaryBar ) drawCpus = cpus; else { CpuData sum; for (const CpuData &data : cpus) { sum.userPercent += data.userPercent; sum.systemPercent += data.systemPercent; sum.nicePercent += data.nicePercent; } sum.userPercent /= cpus.count(); sum.systemPercent /= cpus.count(); sum.nicePercent /= cpus.count(); drawCpus.append(sum); } for (const CpuData &data : drawCpus) { int h = contentsRect().height() * (data.userPercent / 100.0); painter.fillRect(x, y - h, barWidth, h, cpuUserColor); y -= h; h = contentsRect().height() * (data.systemPercent / 100.0); painter.fillRect(x, y - h, barWidth, h, cpuSystemColor); y -= h; h = contentsRect().height() * (data.nicePercent / 100.0); painter.fillRect(x, y - h, barWidth, h, cpuNiceColor); x += barWidth; y = contentsRect().y() + contentsRect().height(); } // memory int h = contentsRect().height() * (memData.memPercent / 100.0); painter.fillRect(x, y - h, barWidth, h, memUsedColor); y -= h; h = contentsRect().height() * (memData.memCachedPercent / 100.0); painter.fillRect(x, y, barWidth, h, memCachedColor); x += barWidth; y = contentsRect().y() + contentsRect().height(); h = contentsRect().height() * (memData.swapPercent / 100.0); painter.fillRect(x, y - h, barWidth, h, memSwapColor); // net x += barWidth; y = contentsRect().y() + contentsRect().height(); h = contentsRect().height() * (double(sumReceived) / maxScale); painter.fillRect(x, y - h, barWidth, h, netReceivedColor); y -= h; h = contentsRect().height() * (double(sumSent) / maxScale); painter.fillRect(x, y - h, barWidth, h, netSentColor); } //-------------------------------------------------------------------------------- void SysLoad::mousePressEvent(QMouseEvent *event) { if ( event->button() == Qt::LeftButton ) { KRun::runCommand("ksysguard", this); } } //-------------------------------------------------------------------------------- diff --git a/SysLoad.hxx b/SysLoad.hxx index 45db894..6b25e78 100644 --- a/SysLoad.hxx +++ b/SysLoad.hxx @@ -1,100 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _SysLoad_H_ #define _SysLoad_H_ #include #include #include #include #include class SysLoad : public QFrame { Q_OBJECT Q_PROPERTY(QColor cpuUserColor MEMBER cpuUserColor) Q_PROPERTY(QColor cpuSystemColor MEMBER cpuSystemColor) Q_PROPERTY(QColor cpuNiceColor MEMBER cpuNiceColor) Q_PROPERTY(bool cpuSummaryBar MEMBER cpuSummaryBar) Q_PROPERTY(QColor memUsedColor MEMBER memUsedColor) Q_PROPERTY(QColor memCachedColor MEMBER memCachedColor) Q_PROPERTY(QColor memSwapColor MEMBER memSwapColor) Q_PROPERTY(QColor netReceivedColor MEMBER netReceivedColor) Q_PROPERTY(QColor netSentColor MEMBER netSentColor) public: SysLoad(QWidget *parent); protected: void paintEvent(QPaintEvent *event) override; void mousePressEvent(QMouseEvent *event) override; private Q_SLOTS: void fetch(); private: struct CpuData { int userCPU = 0, niceCPU = 0, systemCPU = 0; double userPercent = 0, nicePercent = 0, systemPercent = 0; double MHz = 0; }; struct MemoryData { double memPercent = 0; double memCachedPercent = 0; double swapPercent = 0; } memData; struct NetworkData { size_t prevReceived = 0, prevSent = 0; size_t received = 0, sent = 0; bool valid = false; }; QVector cpus; QMap netDevs; size_t maxScale = 0; size_t maxBytes = 0; size_t sumSent = 0, sumReceived = 0; QTimer timeoutTimer; QTimer netLoadTimer; QColor cpuUserColor = "#881f1f"; QColor cpuSystemColor = Qt::darkGreen; QColor cpuNiceColor = Qt::yellow; bool cpuSummaryBar = false; QColor memUsedColor = Qt::blue; QColor memCachedColor = Qt::darkGreen; QColor memSwapColor = Qt::cyan; QColor netReceivedColor = Qt::green; QColor netSentColor = Qt::red; }; #endif diff --git a/SysTray.cxx b/SysTray.cxx index 63dd9b2..bb5c9d1 100644 --- a/SysTray.cxx +++ b/SysTray.cxx @@ -1,264 +1,265 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #ifdef WITH_PACKAGEKIT #include #endif #include #include #include #include #include #include //-------------------------------------------------------------------------------- static const QLatin1String WATCHER_SERVICE("org.kde.StatusNotifierWatcher"); //-------------------------------------------------------------------------------- SysTray::SysTray(DesktopPanel *parent) : QFrame(parent) { qRegisterMetaType("KDbusImageStruct"); qRegisterMetaType("KDbusImageVector"); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); setFrameShape(QFrame::StyledPanel); connect(parent, &DesktopPanel::rowsChanged, this, &SysTray::fill); QHBoxLayout *hbox = new QHBoxLayout(this); hbox->setContentsMargins(QMargins(4, 0, 4, 0)); vbox = new QVBoxLayout; vbox->setContentsMargins(QMargins()); vbox->setSpacing(4); QFrame *separator = new QFrame; separator->setFrameStyle(QFrame::Plain); separator->setFrameShape(QFrame::VLine); appsVbox = new QVBoxLayout; appsVbox->setContentsMargins(QMargins()); appsVbox->setSpacing(4); hbox->addLayout(vbox); hbox->addWidget(separator); hbox->addLayout(appsVbox); if ( QDBusConnection::sessionBus().isConnected() ) { serviceName = QString("org.kde.StatusNotifierHost-%1").arg(QApplication::applicationPid()); QDBusConnection::sessionBus().registerService(serviceName); registerWatcher(); } fill(); } //-------------------------------------------------------------------------------- void SysTray::fill() { // delete all internal widgets QLayoutItem *child; while ( (child = vbox->takeAt(0)) ) { if ( child->layout() ) { while ( QLayoutItem *widgetItem = child->layout()->takeAt(0) ) delete widgetItem->widget(); } delete child; } const int MAX_ROWS = qobject_cast(parentWidget())->getRows(); QVector rowsLayout(MAX_ROWS); for (int i = 0; i < MAX_ROWS; i++) { rowsLayout[i] = new QHBoxLayout; rowsLayout[i]->setContentsMargins(QMargins()); rowsLayout[i]->setSpacing(4); vbox->addLayout(rowsLayout[i]); } if ( MAX_ROWS == 1 ) { rowsLayout[0]->addWidget(new NotificationServer(this), 0, Qt::AlignLeft); rowsLayout[0]->addWidget(new Network(this), 0, Qt::AlignLeft); rowsLayout[0]->addWidget(new DeviceNotifier(this), 0, Qt::AlignLeft); rowsLayout[0]->addWidget(new Battery(this), 0, Qt::AlignLeft); rowsLayout[0]->addWidget(new Bluetooth(this), 0, Qt::AlignLeft); #ifdef WITH_PACKAGEKIT rowsLayout[0]->addWidget(new PkUpdates(this), 0, Qt::AlignLeft); #endif } else if ( MAX_ROWS >= 2 ) { rowsLayout[0]->addWidget(new NotificationServer(this), 0, Qt::AlignLeft); rowsLayout[0]->addWidget(new DeviceNotifier(this), 0, Qt::AlignLeft); rowsLayout[0]->addWidget(new Bluetooth(this), 0, Qt::AlignLeft); rowsLayout[1]->addWidget(new Network(this), 0, Qt::AlignLeft); rowsLayout[1]->addWidget(new Battery(this), 0, Qt::AlignLeft); #ifdef WITH_PACKAGEKIT rowsLayout[1]->addWidget(new PkUpdates(this), 0, Qt::AlignLeft); #endif } // notifier items qDeleteAll(appsRows); appsRows.clear(); appsRows.resize(MAX_ROWS); for (int i = 0; i < MAX_ROWS; i++) { appsRows[i] = new QHBoxLayout; appsRows[i]->setContentsMargins(QMargins()); appsRows[i]->setSpacing(4); appsVbox->addLayout(appsRows[i]); } for (SysTrayNotifyItem *item : items) itemInitialized(item); } //-------------------------------------------------------------------------------- void SysTray::registerWatcher() { QDBusMessage msg = QDBusMessage::createMethodCall(WATCHER_SERVICE, "/StatusNotifierWatcher", "org.kde.StatusNotifierWatcher", "RegisterStatusNotifierHost"); msg << serviceName; QDBusConnection::sessionBus().send(msg); // get list of currently existing items msg = QDBusMessage::createMethodCall(WATCHER_SERVICE, "/StatusNotifierWatcher", "org.freedesktop.DBus.Properties", "Get"); msg << "org.kde.StatusNotifierWatcher" << "RegisteredStatusNotifierItems"; QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(msg); QDBusPendingCallWatcher *pendingCallWatcher = new QDBusPendingCallWatcher(call, this); connect(pendingCallWatcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *w) { w->deleteLater(); QDBusPendingReply reply = *w; QStringList items = reply.value().variant().toStringList(); for (const QString &item : items) itemRegistered(item); } ); // connect for new items QDBusConnection::sessionBus(). connect(WATCHER_SERVICE, "/StatusNotifierWatcher", "org.kde.StatusNotifierWatcher", "StatusNotifierItemRegistered", this, SLOT(itemRegistered(QString))); // connect for removed items QDBusConnection::sessionBus(). connect(WATCHER_SERVICE, "/StatusNotifierWatcher", "org.kde.StatusNotifierWatcher", "StatusNotifierItemUnregistered", this, SLOT(itemUnregistered(QString))); } //-------------------------------------------------------------------------------- void SysTray::itemRegistered(QString item) { //qDebug() << "itemRegistered" << item; int slash = item.indexOf('/'); if ( slash < 1 ) return; QString service = item.left(slash); QString path = item.mid(slash); // create item but insert it into the layout just when it was initialized // since it might be an invalid item which has no StatusNotifierItem path // and will delete itself on error SysTrayNotifyItem *sysItem = new SysTrayNotifyItem(this, service, path); sysItem->setObjectName(item); sysItem->setFixedSize(QSize(22, 22)); connect(sysItem, &SysTrayNotifyItem::initialized, this, &SysTray::itemInitialized); } //-------------------------------------------------------------------------------- void SysTray::itemInitialized(SysTrayNotifyItem *item) { // TODO count only visible items int lowestCount = 0; int lowestCountAt = 0; for (int i = 0; i < appsRows.count(); i++) { if ( i == 0 ) lowestCount = appsRows[i]->count(); else if ( appsRows[i]->count() < lowestCount ) { lowestCount = appsRows[i]->count(); lowestCountAt = i; } } appsRows[lowestCountAt]->addWidget(item, 0, Qt::AlignLeft); if ( !items.contains(item->objectName()) ) { items.insert(item->objectName(), item); disconnect(item, &SysTrayNotifyItem::initialized, this, &SysTray::itemInitialized); } } //-------------------------------------------------------------------------------- void SysTray::itemUnregistered(QString item) { //qDebug() << "itemUnregistered" << item; if ( items.contains(item) ) delete items.take(item); } //-------------------------------------------------------------------------------- diff --git a/SysTray.hxx b/SysTray.hxx index 9340d1c..3106321 100644 --- a/SysTray.hxx +++ b/SysTray.hxx @@ -1,56 +1,57 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _SysTray_H_ #define _SysTray_H_ #include #include #include #include #include #include #include #include class SysTray : public QFrame { Q_OBJECT public: SysTray(DesktopPanel *parent); private Q_SLOTS: void fill(); void itemRegistered(QString service); void itemUnregistered(QString service); void itemInitialized(SysTrayNotifyItem *item); private: void registerWatcher(); private: QVBoxLayout *vbox, *appsVbox; QVector appsRows; QString serviceName; QMap> items; }; #endif diff --git a/SysTrayItem.cxx b/SysTrayItem.cxx index 6dc4070..41674ec 100644 --- a/SysTrayItem.cxx +++ b/SysTrayItem.cxx @@ -1,89 +1,90 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- SysTrayItem::SysTrayItem(QWidget *parent, const QString &icon) : QLabel(parent), iconName(icon) { setFixedSize(QSize(22, 22)); if ( !iconName.isEmpty() ) { setPixmap(QIcon::fromTheme(iconName).pixmap(size())); connect(KIconLoader::global(), &KIconLoader::iconLoaderSettingsChanged, this, [this]() { setPixmap(QIcon::fromTheme(iconName).pixmap(size())); }); } } //-------------------------------------------------------------------------------- void SysTrayItem::mousePressEvent(QMouseEvent *event) { if ( event->button() != Qt::LeftButton ) return; toggleDetailsList(); } //-------------------------------------------------------------------------------- void SysTrayItem::showDetailsList() { QWidget *detailsList = getDetailsList(); if ( !detailsList ) return; QPoint point = mapToGlobal(pos()); QRect screen = QApplication::desktop()->availableGeometry(this); point.setX(std::min(point.x(), screen.x() + screen.width() - detailsList->size().width())); point.setY(screen.bottom() - detailsList->size().height()); detailsList->move(point); detailsList->show(); KWindowSystem::raiseWindow(detailsList->winId()); } //-------------------------------------------------------------------------------- void SysTrayItem::toggleDetailsList() { QWidget *detailsList = getDetailsList(); if ( !detailsList ) return; if ( detailsList->isVisible() ) detailsList->close(); else showDetailsList(); } //-------------------------------------------------------------------------------- diff --git a/SysTrayItem.hxx b/SysTrayItem.hxx index 5e2a275..f83806a 100644 --- a/SysTrayItem.hxx +++ b/SysTrayItem.hxx @@ -1,44 +1,45 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _SysTrayItem_H_ #define _SysTrayItem_H_ #include class SysTrayItem : public QLabel { Q_OBJECT public: SysTrayItem(QWidget *parent, const QString &icon = QString()); protected Q_SLOTS: void toggleDetailsList(); void showDetailsList(); protected: virtual void mousePressEvent(QMouseEvent *event) override; virtual QWidget *getDetailsList() { return nullptr; } private: QString iconName; }; #endif diff --git a/SysTrayNotifyItem.cxx b/SysTrayNotifyItem.cxx index fda4a77..4d228b6 100644 --- a/SysTrayNotifyItem.cxx +++ b/SysTrayNotifyItem.cxx @@ -1,221 +1,222 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- SysTrayNotifyItem::SysTrayNotifyItem(QWidget *parent, const QString &service, const QString &path) : QLabel("?", parent) { // make sure several "New*" signals from dbus just trigger one fetchData() call fetchTimer.setSingleShot(true); fetchTimer.setInterval(100); connect(&fetchTimer, &QTimer::timeout, this, &SysTrayNotifyItem::fetchData); dbus = new OrgKdeStatusNotifierItem(service, path, QDBusConnection::sessionBus(), this); connect(dbus, &OrgKdeStatusNotifierItem::NewAttentionIcon, this, &SysTrayNotifyItem::startTimer); connect(dbus, &OrgKdeStatusNotifierItem::NewIcon, this, &SysTrayNotifyItem::startTimer); connect(dbus, &OrgKdeStatusNotifierItem::NewOverlayIcon, this, &SysTrayNotifyItem::startTimer); connect(dbus, &OrgKdeStatusNotifierItem::NewStatus, this, &SysTrayNotifyItem::startTimer); connect(dbus, &OrgKdeStatusNotifierItem::NewTitle, this, &SysTrayNotifyItem::startTimer); connect(dbus, &OrgKdeStatusNotifierItem::NewToolTip, this, &SysTrayNotifyItem::startTimer); fetchData(); } //-------------------------------------------------------------------------------- void SysTrayNotifyItem::startTimer() { fetchTimer.start(); } //-------------------------------------------------------------------------------- void SysTrayNotifyItem::fetchData() { QDBusMessage msg = QDBusMessage::createMethodCall(dbus->service(), dbus->path(), "org.freedesktop.DBus.Properties", "GetAll"); msg << dbus->interface(); QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(msg); QDBusPendingCallWatcher *pendingCallWatcher = new QDBusPendingCallWatcher(call, this); connect(pendingCallWatcher, &QDBusPendingCallWatcher::finished, this, &SysTrayNotifyItem::fetchDataReply); } //-------------------------------------------------------------------------------- void SysTrayNotifyItem::fetchDataReply(QDBusPendingCallWatcher *w) { w->deleteLater(); QDBusPendingReply reply = *w; if ( reply.isError() ) { //qDebug() << dbus->service() << reply.error(); deleteLater(); return; } /* qDebug() << dbus->service(); qDebug() << "att pixmap file:" << dbus->attentionIconName(); qDebug() << "pixmap file:" << dbus->iconName(); qDebug() << "overlay pixmap file:" << dbus->overlayIconName(); */ QPixmap attentionPixmap = dbus->attentionIconPixmap().pixmap(size()); if ( attentionPixmap.isNull() ) attentionPixmap = QIcon::fromTheme(dbus->attentionIconName(), QIcon()).pixmap(size()); QPixmap pixmap = dbus->iconPixmap().pixmap(size()); if ( pixmap.isNull() ) pixmap = QIcon::fromTheme(dbus->iconName(), QIcon()).pixmap(size()); QPixmap overlay = dbus->overlayIconPixmap().pixmap(size()); if ( overlay.isNull() ) overlay = QIcon::fromTheme(dbus->overlayIconName(), QIcon()).pixmap(size()); QPixmap finalPixmap; if ( !attentionPixmap.isNull() && (dbus->status() == "NeedsAttention") ) finalPixmap = applyOverlay(attentionPixmap, overlay); else if ( !pixmap.isNull() ) finalPixmap = applyOverlay(pixmap, overlay); if ( (dbus->id() == "KMail") || (dbus->id() == "Akregator") ) { // hack for the unwillingness of the kmail maintainer to show unread message count on icon QString text = dbus->toolTip().subTitle.left(dbus->toolTip().subTitle.indexOf(QChar(' '))); bool ok = false; int num = text.toInt(&ok); if ( ok && (num < 100) ) { QPainter painter(&finalPixmap); painter.setPen(Qt::blue); painter.drawText(finalPixmap.rect(), Qt::AlignCenter, QString::number(num)); } } if ( !finalPixmap.isNull() ) setPixmap(finalPixmap); QString tip(dbus->title()); if ( !dbus->toolTip().icon.isEmpty() || !dbus->toolTip().image.isNull() || !dbus->toolTip().title.isEmpty() || !dbus->toolTip().subTitle.isEmpty() ) { tip = QString("
%1
%2
") .arg(dbus->toolTip().title) .arg(dbus->toolTip().subTitle); } setToolTip(tip); show(); if ( dbus->status() == "Passive" ) { // TODO make it configurable if ( dbus->id() == "KMail" ) hide(); } emit initialized(this); } //-------------------------------------------------------------------------------- QPixmap SysTrayNotifyItem::applyOverlay(const QPixmap &pixmap, const QPixmap &overlay) { QPixmap result(pixmap); if ( !overlay.isNull() ) { QPainter painter(&result); painter.drawPixmap(0, 0, overlay); painter.end(); } return result; } //-------------------------------------------------------------------------------- void SysTrayNotifyItem::wheelEvent(QWheelEvent *event) { int delta; QString orientation; if ( event->angleDelta().x() != 0 ) { orientation = "horizontal"; delta = event->angleDelta().x(); } else { orientation = "vertical"; delta = event->angleDelta().y(); } dbus->Scroll(delta, orientation); event->accept(); } //-------------------------------------------------------------------------------- void SysTrayNotifyItem::mouseReleaseEvent(QMouseEvent *event) { // need to do this in the release event since otherwise the popup opened // by the other application would immediately close again (I assume since this widget // still grabs the mouse) if ( event->button() == Qt::LeftButton ) { dbus->Activate(event->globalPos().x(), event->globalPos().y()); WId wid = dbus->windowId(); KWindowSystem::raiseWindow(wid); KWindowSystem::forceActiveWindow(wid); } else if ( event->button() == Qt::RightButton ) { dbus->ContextMenu(event->globalPos().x(), event->globalPos().y()); } } //-------------------------------------------------------------------------------- diff --git a/SysTrayNotifyItem.hxx b/SysTrayNotifyItem.hxx index 9cff434..ac9e7d6 100644 --- a/SysTrayNotifyItem.hxx +++ b/SysTrayNotifyItem.hxx @@ -1,55 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _SysTrayNotifyItem_H_ #define _SysTrayNotifyItem_H_ #include #include class OrgKdeStatusNotifierItem; class QDBusPendingCallWatcher; class SysTrayNotifyItem : public QLabel { Q_OBJECT public: SysTrayNotifyItem(QWidget *parent, const QString &service, const QString &path); Q_SIGNALS: void initialized(SysTrayNotifyItem *); protected: void wheelEvent(QWheelEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; private Q_SLOTS: void startTimer(); void fetchData(); void fetchDataReply(QDBusPendingCallWatcher *w); private: QPixmap applyOverlay(const QPixmap &pixmap, const QPixmap &overlay); private: QTimer fetchTimer; OrgKdeStatusNotifierItem *dbus; }; #endif diff --git a/TaskBar.cxx b/TaskBar.cxx index 205cb38..ebb67c4 100644 --- a/TaskBar.cxx +++ b/TaskBar.cxx @@ -1,111 +1,112 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- TaskBar::TaskBar(DesktopPanel *parent) : QWidget(parent) { grid = new QGridLayout(this); grid->setSpacing(2); grid->setContentsMargins(QMargins()); fill(); connect(parent, &DesktopPanel::rowsChanged, this, &TaskBar::fill); connect(KWindowSystem::self(), &KWindowSystem::currentDesktopChanged, this, &TaskBar::fill); connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &TaskBar::fill); connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &TaskBar::fill); connect(KWindowSystem::self(), SIGNAL(windowChanged(WId, NET::Properties, NET::Properties2)), this, SLOT(windowChanged(WId, NET::Properties, NET::Properties2))); } //-------------------------------------------------------------------------------- void TaskBar::fill() { QLayoutItem *child; while ( (child = grid->takeAt(0)) ) { delete child->widget(); delete child; } QList windowsToShow; const int MAX_ROWS = qobject_cast(parentWidget())->getRows(); for (WId wid : KWindowSystem::windows()) { KWindowInfo win(wid, NET::WMDesktop | NET::WMWindowType | NET::WMState); if ( win.valid(true) && win.isOnCurrentDesktop() && (win.windowType(NET::DesktopMask) != NET::Desktop) && (win.windowType(NET::DockMask) != NET::Dock) && (win.windowType(NET::PopupMenuMask) != NET::PopupMenu) && (win.windowType(NET::UtilityMask) != NET::Utility) && !(win.state() & NET::SkipTaskbar) ) windowsToShow.append(wid); } if ( windowsToShow.count() == 0 ) { grid->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding), 0, 0); return; } int row = 0, col = 0, actualRows; const int MAX_COLUMNS = std::max(2, static_cast(std::ceil(windowsToShow.count() / float(MAX_ROWS)))); actualRows = static_cast(std::ceil(windowsToShow.count() / float(MAX_COLUMNS))); const int ICON_SIZE = height() / actualRows >= 36 ? 32 : 22; for (WId wid : windowsToShow) { TaskBarButton *b = new TaskBarButton(wid); b->setIconSize(ICON_SIZE); grid->addWidget(b, row, col); col = (col + 1) % MAX_COLUMNS; if ( col == 0 ) row++; } } //-------------------------------------------------------------------------------- void TaskBar::windowChanged(WId wid, NET::Properties props, NET::Properties2 props2) { Q_UNUSED(wid) Q_UNUSED(props2) if ( props & NET::WMDesktop ) fill(); } //-------------------------------------------------------------------------------- diff --git a/TaskBar.hxx b/TaskBar.hxx index 0528a9f..9a2f20d 100644 --- a/TaskBar.hxx +++ b/TaskBar.hxx @@ -1,43 +1,44 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _TaskBar_H_ #define _TaskBar_H_ #include #include #include class TaskBar : public QWidget { Q_OBJECT public: TaskBar(DesktopPanel *parent); private Q_SLOTS: void fill(); void windowChanged(WId wid, NET::Properties props, NET::Properties2 props2); private: QGridLayout *grid; }; #endif diff --git a/TaskBarButton.cxx b/TaskBarButton.cxx index d6ca8ec..2f29bcd 100644 --- a/TaskBarButton.cxx +++ b/TaskBarButton.cxx @@ -1,261 +1,262 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- TaskBarButton::TaskBarButton(WId theWid) : wid(theWid) { setAutoFillBackground(true); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setAcceptDrops(true); dragDropTimer.setSingleShot(true); dragDropTimer.setInterval(1000); connect(&dragDropTimer, &QTimer::timeout, [this]() { KWindowSystem::raiseWindow(wid); KWindowSystem::forceActiveWindow(wid); }); QHBoxLayout *hbox = new QHBoxLayout(this); hbox->setContentsMargins(QMargins(4, 2, 4, 2)); iconLabel = new QLabel; iconLabel->setScaledContents(true); iconLabel->setFixedSize(32, 32); iconLabel->setContextMenuPolicy(Qt::PreventContextMenu); hbox->addWidget(iconLabel); textLabel = new KSqueezedTextLabel; textLabel->setTextElideMode(Qt::ElideRight); textLabel->setContextMenuPolicy(Qt::PreventContextMenu); hbox->addWidget(textLabel); fill(); setBackground(); connect(KWindowSystem::self(), SIGNAL(windowChanged(WId, NET::Properties, NET::Properties2)), this, SLOT(windowChanged(WId, NET::Properties, NET::Properties2))); connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &TaskBarButton::setBackground); } //-------------------------------------------------------------------------------- void TaskBarButton::setIconSize(int size) { iconLabel->setFixedSize(size, size); } //-------------------------------------------------------------------------------- void TaskBarButton::fill() { KWindowInfo win(wid, NET::WMName | NET::WMIcon); iconLabel->setPixmap(KWindowSystem::icon(wid, 32, 32, true)); textLabel->setText(win.name()); setToolTip(win.name()); } //-------------------------------------------------------------------------------- void TaskBarButton::mousePressEvent(QMouseEvent *event) { if ( event->button() == Qt::LeftButton ) { KWindowSystem::setShowingDesktop(false); if ( wid == KWindowSystem::activeWindow() ) KWindowSystem::minimizeWindow(wid); else KWindowSystem::forceActiveWindow(wid); dragStartPos = event->pos(); event->accept(); } else if ( event->button() == Qt::RightButton ) { // context menu to close window etc. QPointer menu(new QMenu(this)); if ( KWindowSystem::numberOfDesktops() > 1 ) { QMenu *desktops = menu->addMenu(i18n("Move To Desktop")); desktops->addAction(i18n("All Desktops"), [this]() { KWindowSystem::setOnAllDesktops(wid, true); }); desktops->addSeparator(); for (int i = 1; i <= KWindowSystem::numberOfDesktops(); i++) desktops->addAction(KWindowSystem::desktopName(i), [this, i]() { KWindowSystem::setOnDesktop(wid, i); }); } menu->addAction(QIcon::fromTheme("window-close"), i18n("Close"), [this]() { NETRootInfo ri(QX11Info::connection(), NET::CloseWindow); ri.closeWindowRequest(wid); } ); menu->exec(event->globalPos()); delete menu; } } //-------------------------------------------------------------------------------- void TaskBarButton::mouseMoveEvent(QMouseEvent *event) { event->accept(); if ( (event->buttons() == Qt::LeftButton) && (event->pos() - dragStartPos).manhattanLength() > QGuiApplication::styleHints()->startDragDistance() ) { QDrag *drag = new QDrag(parentWidget()); QMimeData *mimeData = new QMimeData; mimeData->setData("application/x-winId", QByteArray::number(static_cast(wid))); drag->setMimeData(mimeData); drag->setPixmap(*(iconLabel->pixmap())); drag->exec(); } } //-------------------------------------------------------------------------------- void TaskBarButton::paintEvent(QPaintEvent *event) { Q_UNUSED(event); QPainter painter(this); QStyleOptionButton option; initStyleOption(&option); style()->drawControl(QStyle::CE_PushButtonBevel, &option, &painter, this); } //-------------------------------------------------------------------------------- void TaskBarButton::windowChanged(WId id, NET::Properties props, NET::Properties2 props2) { Q_UNUSED(id) Q_UNUSED(props2) //qDebug() << textLabel->text() << props << (props & (NET::WMVisibleName | NET::WMState | NET::WMName)); //qDebug() << this << id << props << "me" << (wid == id); //if ( (id != wid) || (props == 0) ) //return; if ( props & (NET::WMState | NET::ActiveWindow) ) setBackground(); // WMVisibleName alone is not enough. WMName needed if ( (wid == id) && (props & (NET::WMIcon | NET::WMName)) ) fill(); } //-------------------------------------------------------------------------------- void TaskBarButton::setBackground() { KColorScheme scheme(QPalette::Active, KColorScheme::Window); QPalette pal = palette(); KWindowInfo win(wid, NET::WMState); if ( win.state() & NET::Hidden ) pal.setBrush(foregroundRole(), scheme.foreground(KColorScheme::InactiveText)); else pal.setBrush(foregroundRole(), scheme.foreground(KColorScheme::NormalText)); QBrush brush; if ( win.state() & NET::DemandsAttention ) brush = scheme.background(KColorScheme::ActiveBackground); else if ( wid == KWindowSystem::activeWindow() ) brush = scheme.shade(KColorScheme::MidShade); else brush = scheme.background(); pal.setBrush(backgroundRole(), brush); setPalette(pal); } //-------------------------------------------------------------------------------- void TaskBarButton::dragEnterEvent(QDragEnterEvent *event) { event->accept(); dragDropTimer.start(); } //-------------------------------------------------------------------------------- void TaskBarButton::dragLeaveEvent(QDragLeaveEvent *event) { event->accept(); dragDropTimer.stop(); } //-------------------------------------------------------------------------------- void TaskBarButton::dropEvent(QDropEvent *event) { event->accept(); dragDropTimer.stop(); } //-------------------------------------------------------------------------------- void TaskBarButton::updateWMGeometry() { NETWinInfo info(QX11Info::connection(), wid, QX11Info::appRootWindow(), 0, 0); NETRect rect; QPoint globalPos = mapToGlobal(QPoint(0, 0)); rect.pos.x = globalPos.x(); rect.pos.y = globalPos.y(); rect.size.width = width(); rect.size.height = height(); info.setIconGeometry(rect); } //-------------------------------------------------------------------------------- diff --git a/TaskBarButton.hxx b/TaskBarButton.hxx index 3956b97..1079bf0 100644 --- a/TaskBarButton.hxx +++ b/TaskBarButton.hxx @@ -1,68 +1,69 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _TaskBarButton_H_ #define _TaskBarButton_H_ #include #include class QLabel; class KSqueezedTextLabel; #include class TaskBarButton : public QPushButton { Q_OBJECT public: TaskBarButton(WId wid); void setIconSize(int size); Q_SIGNALS: void clicked(); protected: void mousePressEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override; void paintEvent(QPaintEvent *event) override; void dragEnterEvent(QDragEnterEvent *event) override; void dragLeaveEvent(QDragLeaveEvent *event) override; void dropEvent(QDropEvent *event) override; void moveEvent(QMoveEvent *) override { updateWMGeometry(); } void resizeEvent(QResizeEvent *) override { updateWMGeometry(); } private Q_SLOTS: void fill(); void setBackground(); void windowChanged(WId id, NET::Properties props, NET::Properties2 props2); private: void updateWMGeometry(); private: WId wid; QLabel *iconLabel; KSqueezedTextLabel *textLabel; QTimer dragDropTimer; QPoint dragStartPos; }; #endif diff --git a/WeatherApplet.cxx b/WeatherApplet.cxx index cfe03f4..f59f100 100644 --- a/WeatherApplet.cxx +++ b/WeatherApplet.cxx @@ -1,400 +1,401 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- QString WeatherApplet::apiKey; //-------------------------------------------------------------------------------- WeatherApplet::WeatherApplet(QWidget *parent, const QString &theId) : DesktopApplet(parent, theId) { setAutoFillBackground(true); timer.setInterval(600000); // 10min smallest update interval for free data connect(&timer, &QTimer::timeout, this, &WeatherApplet::fetchData); QVBoxLayout *vbox = new QVBoxLayout(this); cityLabel = new QLabel(this); cityLabel->setObjectName("city"); cityLabel->setWordWrap(true); QFont f = font(); f.setPointSizeF(fontInfo().pointSizeF() * 2); f.setBold(true); cityLabel->setFont(f); vbox->addWidget(cityLabel); QGridLayout *grid = new QGridLayout; vbox->addLayout(grid); grid->addWidget(new QLabel(i18n("Temperature:"), this), 0, 0); grid->addWidget(tempLabel = new QLabel, 0, 1); grid->addWidget(new QLabel(i18n("Pressure:"), this), 1, 0); grid->addWidget(pressureLabel = new QLabel, 1, 1); grid->addWidget(new QLabel(i18n("Humidity:"), this), 2, 0); grid->addWidget(humidityLabel = new QLabel, 2, 1); grid->addWidget(new QLabel(i18n("Wind Speed:"), this), 3, 0); grid->addWidget(windSpeedLabel = new QLabel, 3, 1); grid->addWidget(new QLabel(i18n("Wind Direction:"), this), 4, 0); grid->addWidget(windDirectionLabel = new QLabel, 4, 1); for (int i = 0; i < 4; i++) { shortForecast[i] = new ForecastWidget(this, false); grid->addWidget(shortForecast[i], 0, 2 + i, 5, 1, Qt::AlignCenter); } QHBoxLayout *hbox = new QHBoxLayout; vbox->addLayout(hbox); for (int i = 0; i < 5; i++) { forecast[i] = new ForecastWidget(this); hbox->addWidget(forecast[i]); if ( i < 4 ) hbox->addStretch(); } connect(NetworkManager::notifier(), &NetworkManager::Notifier::connectivityChanged, this, [this](NetworkManager::Connectivity connectivity) { if ( connectivity == NetworkManager::Full ) fetchData(); }); } //-------------------------------------------------------------------------------- QSize WeatherApplet::sizeHint() const { return QSize(700, 300); } //-------------------------------------------------------------------------------- void WeatherApplet::loadConfig() { KConfig config; KConfigGroup group = config.group("Weather"); apiKey = group.readEntry("apiKey", QString()); group = config.group(id); cityId = group.readEntry("cityId", QString()); units = group.readEntry("units", QString("metric")); DesktopApplet::loadConfig(); if ( apiKey.isEmpty() || cityId.isEmpty() ) cityLabel->setText(i18n("Not configured")); } //-------------------------------------------------------------------------------- void WeatherApplet::showEvent(QShowEvent *) { // only query every 10 minutes, which is the limit for free data if ( !timer.isActive() ) { timer.start(); fetchData(); } } //-------------------------------------------------------------------------------- void WeatherApplet::fetchData() { if ( !isVisible() || apiKey.isEmpty() || cityId.isEmpty() ) return; QString url = QString("http://api.openweathermap.org/data/2.5/weather?APPID=%1&units=%2&id=%3") .arg(apiKey, units, cityId); KIO::StoredTransferJob *job = KIO::storedGet(QUrl(url), KIO::Reload, KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, &WeatherApplet::gotData); url = QString("http://api.openweathermap.org/data/2.5/forecast?APPID=%1&units=%2&id=%3") .arg(apiKey, units, cityId); job = KIO::storedGet(QUrl(url), KIO::Reload, KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, &WeatherApplet::gotData); } //-------------------------------------------------------------------------------- void WeatherApplet::gotData(KJob *job) { if ( job->error() ) { cityLabel->setText(job->errorString()); return; } QJsonDocument doc = QJsonDocument::fromJson(static_cast(job)->data()); if ( doc.isNull() || !doc.isObject() ) return; QString tempUnit = i18n("°K"); if ( units == "metric" ) tempUnit = i18n("°C"); else if ( units == "imperial" ) tempUnit = i18n("°F"); QJsonObject data = doc.object(); if ( data.contains("city") && data["city"].isObject() ) cityLabel->setText(data["city"].toObject()["name"].toString()); // current if ( data.contains("main") && data["main"].isObject() ) { QJsonObject mainData = data["main"].toObject(); double temp = mainData["temp"].toDouble(); tempLabel->setText(i18n("%1 %2", locale().toString(temp, 'f', 1), tempUnit)); double pressure = mainData["pressure"].toDouble(); pressureLabel->setText(i18n("%1 hPa", locale().toString(pressure, 'f', 1))); double humidity = mainData["humidity"].toDouble(); humidityLabel->setText(i18n("%1 %", locale().toString(humidity, 'f', 1))); } if ( data.contains("wind") && data["wind"].isObject() ) { QJsonObject windData = data["wind"].toObject(); QString speedUnit = "m/s"; if ( units == "imperial" ) speedUnit = "mi/h"; double speed = windData["speed"].toDouble(); windSpeedLabel->setText(i18n("%1 %2", locale().toString(speed, 'f', 0), speedUnit)); double deg = windData["deg"].toDouble(); windDirectionLabel->setText(i18n("%1 °", locale().toString(deg, 'f', 0))); } if ( data.contains("weather") && data["weather"].isArray() ) { QDateTime dt = QDateTime::fromMSecsSinceEpoch(qint64(data["dt"].toInt()) * 1000); shortForecast[0]->day->setText(dt.time().toString(Qt::SystemLocaleShortDate)); setIcon(shortForecast[0]->icon, data["weather"].toArray()[0].toObject()["icon"].toString()); } // forecast if ( data.contains("list") && data["list"].isArray() ) { for (int i = 0; i < 5; i++) forecast[i]->hide(); QJsonArray array = data["list"].toArray(); // 3 hours short forecast for (int i = 0; i < 3; i++) { setIcon(shortForecast[1 + i]->icon, array[i].toObject()["weather"].toArray()[0].toObject()["icon"].toString()); QDateTime dt = QDateTime::fromMSecsSinceEpoch(qint64(array[i].toObject()["dt"].toInt()) * 1000); shortForecast[1 + i]->day->setText(dt.time().toString(Qt::SystemLocaleShortDate)); shortForecast[1 + i]->show(); } QHash minTemp, maxTemp; // key = day for (QJsonValue value : array) { QJsonObject obj = value.toObject(); int day = QDateTime::fromMSecsSinceEpoch(qint64(obj["dt"].toInt()) * 1000).date().dayOfWeek(); double temp = obj["main"].toObject()["temp"].toDouble(); if ( !minTemp.contains(day) ) { minTemp.insert(day, temp); maxTemp.insert(day, temp); } else { if ( temp < minTemp[day] ) minTemp[day] = temp; if ( temp > maxTemp[day] ) maxTemp[day] = temp; } } int idx = 0; for (QJsonValue value : array) { QJsonObject obj = value.toObject(); if ( obj["dt_txt"].toString().contains("12:00") ) { QString icon = obj["weather"].toArray()[0].toObject()["icon"].toString(); setIcon(forecast[idx]->icon, icon); int day = QDateTime::fromMSecsSinceEpoch(qint64(obj["dt"].toInt()) * 1000).date().dayOfWeek(); forecast[idx]->day->setText(QDate::shortDayName(day)); forecast[idx]->min->setText(i18n("%1 %2", locale().toString(minTemp[day], 'f', 1), tempUnit)); forecast[idx]->max->setText(i18n("%1 %2", locale().toString(maxTemp[day], 'f', 1), tempUnit)); forecast[idx]->show(); idx++; if ( idx == 5 ) break; } } } timer.start(); // after showEvent make sure to wait another full timeout phase } //-------------------------------------------------------------------------------- void WeatherApplet::setIcon(QLabel *label, const QString &icon) { QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/api.openweathermap.org/"; QDir dir; dir.mkpath(cacheDir); QString filePath = cacheDir + icon + ".png"; if ( QFile::exists(filePath) ) { QPixmap pixmap(filePath); if ( !pixmap.isNull() ) label->setPixmap(pixmap); } else { KIO::StoredTransferJob *job = KIO::storedGet(QUrl("http://api.openweathermap.org/img/w/" + icon), KIO::Reload, KIO::HideProgressInfo); connect(job, &KIO::Job::result, this, [label, filePath](KJob *job) { if ( job->error() ) return; QPixmap pixmap; pixmap.loadFromData(static_cast(job)->data()); if ( !pixmap.isNull() ) { label->setPixmap(pixmap); pixmap.save(filePath, "PNG"); } }); } } //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- //-------------------------------------------------------------------------------- ForecastWidget::ForecastWidget(QWidget *parent, bool showMinMax) : QWidget(parent) { QGridLayout *grid = new QGridLayout(this); if ( showMinMax ) { min = new QLabel(this); max = new QLabel(this); min->setAlignment(Qt::AlignRight); max->setAlignment(Qt::AlignRight); grid->addWidget(max, 0, 1); grid->addWidget(min, 1, 1); } day = new QLabel(this); icon = new QLabel(this); day->setAlignment(Qt::AlignCenter); icon->setFixedSize(64, 64); icon->setScaledContents(true); grid->addWidget(day, 2, 0, 1, 2); grid->addWidget(icon, 0, 0, 2, 1); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); } //-------------------------------------------------------------------------------- void WeatherApplet::configure() { if ( dialog ) { dialog->raise(); dialog->activateWindow(); return; } dialog = new WeatherAppletConfigureDialog(this); dialog->setWindowTitle(i18n("Configure Weather Applet")); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); connect(dialog.data(), &QDialog::accepted, this, [this]() { saveConfig(); KConfig config; KConfigGroup group = config.group("Weather"); group.writeEntry("apiKey", apiKey); group = config.group(id); group.writeEntry("cityId", cityId); group.writeEntry("units", units); if ( !apiKey.isEmpty() && !cityId.isEmpty() ) { fetchData(); timer.start(); } else { cityLabel->setText(i18n("Not configured")); } }); } //-------------------------------------------------------------------------------- diff --git a/WeatherApplet.hxx b/WeatherApplet.hxx index 1cdd5cd..e1000e6 100644 --- a/WeatherApplet.hxx +++ b/WeatherApplet.hxx @@ -1,91 +1,92 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _WeatherApplet_H_ #define _WeatherApplet_H_ #include #include #include #include #include #include // applet using data from http://api.openweathermap.org class WeatherApplet : public DesktopApplet { Q_OBJECT public: WeatherApplet(QWidget *parent, const QString &theId); void loadConfig() override; QSize sizeHint() const override; public Q_SLOTS: void configure() override; protected: void showEvent(QShowEvent *event) override; private Q_SLOTS: void fetchData(); void gotData(KJob *job); private: // methods void setIcon(QLabel *label, const QString &icon); private: // members static QString apiKey; // see http://openweathermap.org/api QString cityId, units; QTimer timer; QLabel *cityLabel = nullptr; QLabel *tempLabel = nullptr; QLabel *pressureLabel = nullptr; QLabel *humidityLabel = nullptr; QLabel *windSpeedLabel = nullptr; QLabel *windDirectionLabel = nullptr; class ForecastWidget *shortForecast[4] = { nullptr }; class ForecastWidget *forecast[5] = { nullptr }; QPointer dialog; friend WeatherAppletConfigureDialog; }; //-------------------------------------------------------------------------------- class ForecastWidget : public QWidget { Q_OBJECT public: ForecastWidget(QWidget *parent, bool showMinMax = true); QLabel *min = nullptr; QLabel *max = nullptr; QLabel *day = nullptr; QLabel *icon = nullptr; }; #endif diff --git a/WeatherAppletConfigureDialog.cxx b/WeatherAppletConfigureDialog.cxx index 95cac22..d4071e8 100644 --- a/WeatherAppletConfigureDialog.cxx +++ b/WeatherAppletConfigureDialog.cxx @@ -1,175 +1,176 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //-------------------------------------------------------------------------------- WeatherAppletConfigureDialog::WeatherAppletConfigureDialog(WeatherApplet *parent) : QDialog(parent), applet(parent) { ui.setupUi(this); connect(ui.city, &QLineEdit::textEdited, [this](const QString &txt) { auto found = ui.cityList->findItems(txt, Qt::MatchStartsWith); if ( found.count() ) ui.cityList->setCurrentItem(found[0]); }); connect(ui.cityList, &QTreeWidget::itemClicked, [this](QTreeWidgetItem *current) { ui.city->setText(current->text(0)); }); ui.apiKey->setText(applet->apiKey); if ( applet->units == "metric" ) ui.metric->setChecked(true); else ui.imperial->setChecked(true); connect(ui.getApiKey, &QPushButton::clicked, []() { new KRun(QUrl("http://openweathermap.org/appid"), nullptr); }); ui.textColor->setColor(applet->palette().color(applet->foregroundRole())); ui.backgroundColor->setColor(applet->palette().color(applet->backgroundRole())); // for city selection, we need the json file QString cacheDir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/api.openweathermap.org/"; QDir dir; dir.mkpath(cacheDir); QString filePath = cacheDir + "city.list.json.gz"; if ( QFile::exists(filePath) ) QTimer::singleShot(0, [this, filePath]() { readJsonFile(filePath); }); else { KIO::CopyJob *job = KIO::copy(QUrl("http://bulk.openweathermap.org/sample/city.list.json.gz"), QUrl::fromLocalFile(filePath)); connect(job, &KIO::Job::result, this, &WeatherAppletConfigureDialog::gotJsonFile); ui.city->setText(i18n("Downloading city list...")); ui.city->setReadOnly(true); } } //-------------------------------------------------------------------------------- void WeatherAppletConfigureDialog::gotJsonFile(KJob *job) { ui.city->setText(QString()); ui.city->setReadOnly(false); if ( job->error() ) { QMessageBox::warning(this, i18n("Download Error"), i18n("Error on downloading city list: %1").arg(job->errorString())); return; } readJsonFile(static_cast(job)->destUrl().toLocalFile()); } //-------------------------------------------------------------------------------- void WeatherAppletConfigureDialog::readJsonFile(const QString &filePath) { ui.city->setFocus(); KFilterDev jsonFile(filePath); if ( !jsonFile.open(QIODevice::ReadOnly) ) return; QJsonArray array = QJsonDocument::fromJson(jsonFile.readAll()).array(); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); int i = 0; for (const QJsonValue &value : array) { if ( value.isObject() ) { QJsonObject obj = value.toObject(); QString city = obj["name"].toString(); QString country = obj["country"].toString(); if ( !city.isEmpty() && (city != "-") && !country.isEmpty() ) { QTreeWidgetItem *item = new QTreeWidgetItem(ui.cityList); item->setText(0, city); item->setText(1, country); item->setData(0, Qt::UserRole, obj["id"].toInt()); } } if ( (i++ % 40000) == 0 ) QApplication::processEvents(); } ui.cityList->setSortingEnabled(true); ui.cityList->sortByColumn(0, Qt::AscendingOrder); ui.cityList->header()->resizeSections(QHeaderView::ResizeToContents); for (int i = 0; i < ui.cityList->topLevelItemCount(); i++) { if ( ui.cityList->topLevelItem(i)->data(0, Qt::UserRole).toString() == applet->cityId ) { ui.cityList->setCurrentItem(ui.cityList->topLevelItem(i)); ui.city->setText(ui.cityList->topLevelItem(i)->text(0)); break; } } QApplication::restoreOverrideCursor(); } //-------------------------------------------------------------------------------- void WeatherAppletConfigureDialog::accept() { applet->apiKey = ui.apiKey->text(); auto selected = ui.cityList->selectedItems(); if ( selected.count() ) applet->cityId = selected[0]->data(0, Qt::UserRole).toString(); if ( ui.metric->isChecked() ) applet->units = "metric"; else applet->units = "imperial"; QPalette pal = applet->palette(); pal.setColor(applet->foregroundRole(), ui.textColor->color()); pal.setColor(applet->backgroundRole(), ui.backgroundColor->color()); applet->setPalette(pal); QDialog::accept(); } //-------------------------------------------------------------------------------- diff --git a/WeatherAppletConfigureDialog.hxx b/WeatherAppletConfigureDialog.hxx index 30cd1e4..3109780 100644 --- a/WeatherAppletConfigureDialog.hxx +++ b/WeatherAppletConfigureDialog.hxx @@ -1,48 +1,49 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _WeatherAppletConfigureDialog_H_ #define _WeatherAppletConfigureDialog_H_ #include #include #include class WeatherApplet; class WeatherAppletConfigureDialog : public QDialog { Q_OBJECT public: WeatherAppletConfigureDialog(WeatherApplet *parent); public Q_SLOTS: void accept() override; private Q_SLOTS: void gotJsonFile(KJob *job); void readJsonFile(const QString &filePath); private: WeatherApplet *applet; Ui::WeatherAppletConfigureDialog ui; QString cityId; }; #endif diff --git a/WindowList.cxx b/WindowList.cxx index 0adbc7d..596a51b 100644 --- a/WindowList.cxx +++ b/WindowList.cxx @@ -1,71 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include //-------------------------------------------------------------------------------- WindowList::WindowList(QWidget *parent) : QPushButton(parent) { setIcon(QIcon::fromTheme("arrow-up")); setMaximumWidth(22); setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); setMenu(new PopupMenu(this)); connect(menu(), &PopupMenu::aboutToShow, this, &WindowList::fillMenu); } //-------------------------------------------------------------------------------- void WindowList::fillMenu() { menu()->clear(); QList windows = KWindowSystem::windows(); for (int i = 1; i <= KWindowSystem::numberOfDesktops(); i++) { menu()->addSection(KWindowSystem::desktopName(i).isEmpty() ? QString::number(i) : KWindowSystem::desktopName(i)); for (WId wid : windows) { KWindowInfo win(wid, NET::WMDesktop | NET::WMWindowType | NET::WMState | NET::WMName | NET::WMIcon); if ( win.valid(true) && win.isOnDesktop(i) && (win.windowType(NET::DesktopMask) != NET::Desktop) && (win.windowType(NET::DockMask) != NET::Dock) && !(win.state() & NET::SkipTaskbar) ) { QAction *action = menu()->addAction(KWindowSystem::icon(wid, 22, 22, true), win.name(), [wid]() { KWindowSystem::forceActiveWindow(wid); }); action->setData(static_cast(wid)); } } } } //-------------------------------------------------------------------------------- diff --git a/WindowList.hxx b/WindowList.hxx index 97e0312..d6eef70 100644 --- a/WindowList.hxx +++ b/WindowList.hxx @@ -1,36 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #ifndef _WindowList_H_ #define _WindowList_H_ #include class WindowList : public QPushButton { Q_OBJECT public: WindowList(QWidget *parent); private Q_SLOTS: void fillMenu(); }; #endif diff --git a/desktop.cxx b/desktop.cxx index 2097145..9cbebb8 100644 --- a/desktop.cxx +++ b/desktop.cxx @@ -1,75 +1,76 @@ +// SPDX-License-Identifier: GPL-3.0-or-later /* Copyright 2017, 2018 Martin Koller, kollix@aon.at This file is part of liquidshell. liquidshell 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 3 of the License, or (at your option) any later version. liquidshell 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 liquidshell. If not, see . */ #include #include #include #include #include #include #include #include #include #include int main(int argc, char **argv) { #if QT_VERSION >= QT_VERSION_CHECK(5,7,0) QCoreApplication::setAttribute(Qt::AA_UseStyleSheetPropagationInWidgetStyles); #endif QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication app(argc, argv); KLocalizedString::setApplicationDomain("liquidshell"); KAboutData aboutData("liquidshell", i18n("Liquid Desktop Workspace"), "1.5", i18n("A QtWidgets based alternative to plasmashell"), KAboutLicense::GPL_V3, i18n("Copyright 2017,2018 Martin Koller"), QString(), "https://www.linux-apps.com/p/1205621/"); // homepage aboutData.addAuthor("Martin Koller", "", "kollix@aon.at"); KAboutData::setApplicationData(aboutData); QCommandLineParser parser; aboutData.setupCommandLine(&parser); parser.process(app); aboutData.processCommandLine(&parser); KCrash::setFlags(KCrash::AutoRestart); KDBusService programDBusService(KDBusService::Unique | KDBusService::NoExitOnFailure); DesktopWidget desktop; desktop.show(); QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QStringLiteral("org.kde.KSplash"), QStringLiteral("setStage")); ksplashProgressMessage.setArguments(QList() << QStringLiteral("desktop")); QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); return app.exec(); }