* Copyright (C) 2007 by Daniel Laidig <d.laidig@gmx.de> *
* Copyright (C) 2012 by Luís Gabriel Lima <lampih@gmail.com> *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
***************************************************************************/
#include "pager.h"
#include <math.h>
#include <QAction>
#include <QApplication>
#include <QFont>
#include <QTimer>
#if HAVE_X11
#include <QX11Info>
#endif
#include <QDBusInterface>
#include <QTextDocument>
#include <QDesktopWidget>
#include <KColorScheme>
#include <KGlobalSettings>
#include <KIconLoader>
#include <KLocalizedString>
#include <KWindowSystem>
#include <KProcess>
#if HAVE_X11
#include <netwm.h>
#endif
#include <Plasma/FrameSvg>
#include <Plasma/Package>
#include <kactivities/consumer.h>
#include <task.h>
const int FAST_UPDATE_DELAY = 100;
const int UPDATE_DELAY = 500;
const int MAXDESKTOPS = 20;
const qreal MAX_TEXT_WIDTH = 800;
Pager::Pager(QObject *parent)
: QObject(parent),
m_displayedText(None),
m_currentDesktopSelected(DoNothing),
m_columns(0),
m_currentDesktop(0),
m_orientation(Qt::Horizontal),
m_showWindowIcons(false),
m_desktopDown(false),
m_validSizes(false),
m_desktopWidget(QApplication::desktop())
#if HAVE_X11
, m_isX11(QX11Info::isPlatformX11())
#else
, m_isX11(false)
#endif
{
m_rows = 1;
#if HAVE_X11
if (m_isX11) {
NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout);
m_rows = info.desktopLayoutColumnsRows().height();
}
#endif
m_desktopCount = qMax(1, KWindowSystem::numberOfDesktops());
m_pagerModel = new PagerModel(this);
m_timer = new QTimer(this);
m_timer->setSingleShot(true);
connect(m_timer, &QTimer::timeout, this, &Pager::recalculateWindowRects);
connect(KWindowSystem::self(), SIGNAL(currentDesktopChanged(int)), this, SLOT(currentDesktopChanged(int)));
connect(KWindowSystem::self(), &KWindowSystem::windowAdded, this, &Pager::startTimerFast);
connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, this, &Pager::startTimerFast);
connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, this, &Pager::startTimerFast);
connect(KWindowSystem::self(), &KWindowSystem::numberOfDesktopsChanged, this, &Pager::numberOfDesktopsChanged);
connect(KWindowSystem::self(), &KWindowSystem::desktopNamesChanged, this, &Pager::desktopNamesChanged);
connect(KWindowSystem::self(), &KWindowSystem::stackingOrderChanged, this, &Pager::startTimerFast);
connect(KWindowSystem::self(), SIGNAL(windowChanged(WId,const ulong*)), this, SLOT(windowChanged(WId,const ulong*)));
connect(KWindowSystem::self(), SIGNAL(showingDesktopChanged(bool)), this, SLOT(startTimer()));
connect(m_desktopWidget, &QDesktopWidget::screenCountChanged, this, &Pager::desktopsSizeChanged);
connect(m_desktopWidget, &QDesktopWidget::resized, this, &Pager::desktopsSizeChanged);
QDBusConnection dbus = QDBusConnection::sessionBus();
dbus.connect(QString(), QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig"),
this, SLOT(desktopsSizeChanged()));
recalculateGridSizes(m_rows);
setCurrentDesktop(KWindowSystem::currentDesktop());
KActivities::Consumer *act = new KActivities::Consumer(this);
connect(act, &KActivities::Consumer::currentActivityChanged, this, &Pager::currentActivityChanged);
m_currentActivity = act->currentActivity();
}
Pager::~Pager()
{
}
int Pager::desktopCount() const
{
return m_desktopCount;
}
void Pager::setCurrentDesktop(int desktop)
{
if (m_currentDesktop != desktop) {
m_currentDesktop = desktop;
emit currentDesktopChanged();
}
}
void Pager::setShowWindowIcons(bool show)
{
if (m_showWindowIcons != show) {
m_showWindowIcons = show;
emit showWindowIconsChanged();
}
}
Qt::Orientation Pager::orientation() const
{
return m_orientation;
}
void Pager::setOrientation(Qt::Orientation orientation)
{
if (m_orientation == orientation) {
return;
}
m_orientation = orientation;
emit orientationChanged();
if (m_columns != m_rows) {
recalculateGridSizes(m_columns);
recalculateWindowRects();
}
}
QSizeF Pager::size() const
{
return m_size;
}
QSize Pager::preferredSize() const
{
return m_preferredSize;
}
void Pager::setSize(const QSizeF &size)
{
if (m_size == size) {
return;
}
m_size = size;
emit sizeChanged();
m_validSizes = false;
startTimer();
}
Pager::CurrentDesktopSelected Pager::currentDesktopSelected() const
{
return m_currentDesktopSelected;
}
void Pager::setCurrentDesktopSelected(CurrentDesktopSelected cur)
{
if (m_currentDesktopSelected == cur) {
return;
}
m_currentDesktopSelected = cur;
emit currentDesktopSelectedChanged();
}
Pager::DisplayedText Pager::displayedText() const
{
return m_displayedText;
}
void Pager::setDisplayedText(Pager::DisplayedText disp)
{
if (m_displayedText == disp) {
return;
}
m_displayedText = disp;
emit displayedTextChanged();
}
#if HAVE_X11
void Pager::slotAddDesktop()
{
if (!m_isX11) {
return;
}
NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops);
info.setNumberOfDesktops(info.numberOfDesktops() + 1);
}
void Pager::slotRemoveDesktop()
{
if (!m_isX11) {
return;
}
NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops);
int desktops = info.numberOfDesktops();
if (desktops > 1) {
info.setNumberOfDesktops(info.numberOfDesktops() - 1);
}
}
#endif
void Pager::recalculateGridSizes(int rows)
{
rows = qBound(1, rows, m_desktopCount);
int columns = m_desktopCount / rows;
if (m_desktopCount % rows > 0) {
columns++;
}
rows = m_desktopCount / columns;
if (m_desktopCount % columns > 0) {
rows++;
}
m_rows = rows;
m_columns = columns;
updateSizes();
}
void Pager::updateSizes()
{
const int padding = 2;
const int textMargin = 3;
const qreal leftMargin = 0;
const qreal topMargin = 0;
const qreal rightMargin = 0;
const qreal bottomMargin = 0;
QRect totalRect;
for (int x = 0; x < m_desktopWidget->screenCount(); x++) {
totalRect |= m_desktopWidget->screenGeometry(x);
}
const qreal ratio = (qreal) totalRect.width() /
(qreal) totalRect.height();
qreal itemHeight;
qreal itemWidth;
qreal preferredItemHeight;
qreal preferredItemWidth;
if (orientation() == Qt::Vertical) {
itemWidth = (m_size.width() - leftMargin - rightMargin -
padding * (m_columns - 1)) / m_columns;
itemHeight = itemWidth / ratio;
} else {
preferredItemHeight = (m_size.height() - topMargin - bottomMargin -
padding * (m_rows - 1)) / m_rows;
preferredItemWidth = preferredItemHeight * ratio;
if (m_displayedText == Name) {
for (int i = 0; i < m_desktopCount; i++) {
QFontMetricsF metrics(KGlobalSettings::taskbarFont());
QSizeF textSize = metrics.size(Qt::TextSingleLine, KWindowSystem::desktopName(i+1));
if (textSize.width() + textMargin * 2 > preferredItemWidth) {
preferredItemWidth = textSize.width() + textMargin * 2;
}
}
}
itemWidth = (m_size.width() - leftMargin - rightMargin -
padding * (m_columns - 1)) / m_columns;
if (itemWidth > preferredItemWidth) {
itemWidth = preferredItemWidth;
}
itemHeight = preferredItemHeight;
if (itemWidth < itemHeight * ratio) {
itemWidth = itemHeight * ratio;
}
}
m_widthScaleFactor = itemWidth / totalRect.width();
m_heightScaleFactor = itemHeight / totalRect.height();
m_pagerModel->clearDesktopRects();
int pw = (int)(leftMargin + (itemWidth + padding) * m_columns + rightMargin);
int ph = (int)(topMargin + (itemHeight + padding) * m_rows + bottomMargin);
if (orientation() == Qt::Vertical) {
pw = m_size.width();
} else {
ph = m_size.height();
}
m_preferredSize = QSize(pw, ph);
emit preferredSizeChanged();
QRectF itemRect(QPointF(leftMargin, topMargin) , QSizeF(itemWidth, itemHeight));
for (int i = 0; i < m_desktopCount; i++) {
itemRect.moveLeft(leftMargin + (i % m_columns) * (itemWidth + padding));
itemRect.moveTop(topMargin + (i / m_columns) * (itemHeight + padding));
QString name = KWindowSystem::desktopName(i + 1);
m_pagerModel->appendDesktopRect(itemRect, name);
}
m_validSizes = true;
}
void Pager::recalculateWindowRects()
{
if (!m_isX11) {
return;
}
#if HAVE_X11
NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout);
m_rows = info.desktopLayoutColumnsRows().height();
if (!m_validSizes) {
recalculateGridSizes(m_rows);
updateSizes();
}
QList<WId> windows = KWindowSystem::stackingOrder();
m_pagerModel->clearWindowRects();
foreach (WId window, windows) {
KWindowInfo info = KWindowSystem::windowInfo(window, NET::WMGeometry | NET::WMFrameExtents |
NET::WMWindowType | NET::WMDesktop |
NET::WMState | NET::XAWMState | NET::WMVisibleName);
NET::WindowType type = info.windowType(NET::NormalMask | NET::DialogMask | NET::OverrideMask |
NET::UtilityMask | NET::DesktopMask | NET::DockMask |
NET::TopMenuMask | NET::SplashMask | NET::ToolbarMask |
NET::MenuMask);
if (type == NET::Desktop || type == NET::Dock || type == NET::TopMenu ||
type == NET::Splash || type == NET::Menu || type == NET::Toolbar ||
info.hasState(NET::SkipPager) || info.isMinimized()) {
continue;
}
NETWinInfo netInfo(QX11Info::connection(), window, QX11Info::appRootWindow(), 0, NET::WM2Activities);
QString result(netInfo.activities());
if (!result.isEmpty() && result != QLatin1String("00000000-0000-0000-0000-000000000000")) {
QStringList activities = result.split(',');
if (!activities.contains(m_currentActivity)) {
continue;
}
}
for (int i = 0; i < m_desktopCount; i++) {
if (!info.isOnDesktop(i+1)) {
continue;
}
QRectF windowRect = info.frameGeometry();
if (KWindowSystem::mapViewport()) {
windowRect = fixViewportPosition(windowRect.toRect());
}
windowRect = QRectF(windowRect.x() * m_widthScaleFactor,
windowRect.y() * m_heightScaleFactor,
windowRect.width() * m_widthScaleFactor,
windowRect.height() * m_heightScaleFactor).toRect();
bool active = (window == KWindowSystem::activeWindow());
int windowIconSize = KIconLoader::global()->currentSize(KIconLoader::Small);
int windowRectSize = qMin(windowRect.width(), windowRect.height());
windowIconSize = qMax(windowIconSize, windowRectSize / 2);
QPixmap icon = KWindowSystem::icon(info.win(), windowIconSize, windowIconSize, true);
m_pagerModel->appendWindowRect(i, window, windowRect, active, icon, info.visibleName());
}
}
#endif
}
void Pager::currentDesktopChanged(int desktop)
{
if (desktop < 1) {
return;
}
setCurrentDesktop(desktop);
m_desktopDown = false;
startTimerFast();
}
void Pager::currentActivityChanged(const QString &activity)
{
m_currentActivity = activity;
startTimerFast();
}
void Pager::numberOfDesktopsChanged(int num)
{
if (!m_isX11) {
return;
}
#if HAVE_X11
if (num < 1) {
return;
}
NETRootInfo info(QX11Info::connection(), NET::NumberOfDesktops | NET::DesktopNames, NET::WM2DesktopLayout);
m_rows = info.desktopLayoutColumnsRows().height();
if (num != m_desktopCount) {
m_desktopCount = num;
emit desktopCountChanged();
}
m_pagerModel->clearDesktopRects();
recalculateGridSizes(m_rows);
recalculateWindowRects();
#endif
}
void Pager::desktopNamesChanged()
{
m_pagerModel->clearDesktopRects();
m_validSizes = false;
startTimer();
}
void Pager::windowChanged(WId id, const unsigned long* dirty)
{
if (!m_isX11) {
return;
}
Q_UNUSED(id)
#if HAVE_X11
if (dirty[NETWinInfo::PROTOCOLS] & (NET::WMGeometry | NET::WMDesktop) ||
dirty[NETWinInfo::PROTOCOLS2] & NET::WM2Activities) {
startTimer();
}
#endif
}
void Pager::desktopsSizeChanged()
{
m_pagerModel->clearDesktopRects();
m_validSizes = false;
startTimer();
}
void Pager::startTimer()
{
if (!m_timer->isActive()) {
m_timer->start(UPDATE_DELAY);
}
}
void Pager::startTimerFast()
{
if (!m_timer->isActive()) {
m_timer->start(FAST_UPDATE_DELAY);
}
}
void Pager::moveWindow(int window, double x, double y, int targetDesktop, int sourceDesktop)
{
#if HAVE_X11
if (!m_isX11) {
return;
}
WId windowId = (WId) window;
QPointF dest = QPointF(x, y) - m_pagerModel->desktopRectAt(targetDesktop).topLeft();
dest = QPointF(dest.x()/m_widthScaleFactor, dest.y()/m_heightScaleFactor);
dest = QPointF(qMax(dest.x(), qreal(0.0)), qMax(dest.y(), qreal(0.0)));
NETRootInfo info(QX11Info::connection(), 0);
int flags = (0x20 << 12) | (0x03 << 8) | 1;
if (!KWindowSystem::mapViewport()) {
KWindowInfo windowInfo = KWindowSystem::windowInfo(windowId, NET::WMDesktop | NET::WMState);
if (!windowInfo.onAllDesktops()) {
KWindowSystem::setOnDesktop(windowId, targetDesktop+1);
}
if (!(windowInfo.state() & NET::FullScreen) &&
(targetDesktop == sourceDesktop || windowInfo.onAllDesktops())) {
QPoint d = dest.toPoint();
info.moveResizeWindowRequest(windowId, flags, d.x(), d.y(), 0, 0);
}
} else {
dest += KWindowSystem::desktopToViewport(targetDesktop+1, false);
QPoint d = KWindowSystem::constrainViewportRelativePosition(dest.toPoint());
info.moveResizeWindowRequest(windowId, flags, d.x(), d.y(), 0, 0);
}
m_timer->start();
#else
Q_UNUSED(window)
Q_UNUSED(x)
Q_UNUSED(y)
Q_UNUSED(targetDesktop)
Q_UNUSED(sourceDesktop)
#endif
}
void Pager::changeDesktop(int newDesktop)
{
#if HAVE_X11
if (!m_isX11) {
return;
}
if (m_currentDesktop == newDesktop+1) {
if (m_currentDesktopSelected == ShowDesktop) {
NETRootInfo info(QX11Info::connection(), 0);
m_desktopDown = !m_desktopDown;
info.setShowingDesktop(m_desktopDown);
}
} else {
KWindowSystem::setCurrentDesktop(newDesktop + 1);
setCurrentDesktop(newDesktop + 1);
}
#else
Q_UNUSED(newDesktop)
#endif
}
void Pager::dropMimeData(QMimeData *mimeData, int desktopId)
{
if (!mimeData) {
return;
}
bool ok;
const QList<WId> &ids = TaskManager::Task::idsFromMimeData(mimeData, &ok);
if (ok) {
foreach (const WId &id, ids) {
KWindowSystem::setOnDesktop(id, desktopId + 1);
}
}
}
QRect Pager::fixViewportPosition( const QRect& r )
{
QRect desktopGeom = m_desktopWidget->geometry();
int x = r.center().x() % desktopGeom.width();
int y = r.center().y() % desktopGeom.height();
if( x < 0 ) {
x = x + desktopGeom.width();
}
if( y < 0 ) {
y = y + desktopGeom.height();
}
return QRect( x - r.width() / 2, y - r.height() / 2, r.width(), r.height());
}
void Pager::openVirtualDesktopsKCM()
{
QProcess::startDetached(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("desktop"));
}
#include "moc_pager.cpp"