Add high DPI support to internal KWin windows

Summary:
So far we didn't try to do high DPI on kwin internal windows, such as
the user context menu and tab bars and whatever.

Due to wayland scaling they were the correct phyiscal size but upscaled.
This patch fixes our QPA to enable Qt's high-dpi support.

BUG: 402853

Note icons are still low res. This is because the global
QGuiApplication::devicePixelRatio which is the max of all connected
screens is static for the duration of the app. QIcon uses this when
determining the DPR to use. This will require a Qt change.

Test Plan:
Ran at 2x on my normal DPI screen (as that's easier to see anything)
* User action menu is high DPI
* Window deco tooltips are still fine
* Tab switcher is high DPI
* Overlay in present windows Desktop grid are still ok

Reviewers: #kwin

Subscribers: zzag, kwin

Tags: #kwin

Differential Revision: https://phabricator.kde.org/D18042
This commit is contained in:
David Edmundson 2019-01-11 22:48:04 +00:00
parent b7f6e57eff
commit 2df9d22a08
8 changed files with 69 additions and 13 deletions

View file

@ -33,6 +33,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <KWayland/Client/seat.h> #include <KWayland/Client/seat.h>
#include <KWayland/Client/shell.h> #include <KWayland/Client/shell.h>
#include <KWayland/Server/surface_interface.h>
#include <linux/input.h> #include <linux/input.h>
using namespace KWayland::Client; using namespace KWayland::Client;
@ -64,6 +66,7 @@ private Q_SLOTS:
void testModifierClickUnrestrictedMove(); void testModifierClickUnrestrictedMove();
void testModifierScroll(); void testModifierScroll();
void testPopup(); void testPopup();
void testScale();
}; };
class HelperWindow : public QRasterWindow class HelperWindow : public QRasterWindow
@ -687,6 +690,29 @@ void InternalWindowTest::testPopup()
QCOMPARE(internalClient->isPopupWindow(), true); QCOMPARE(internalClient->isPopupWindow(), true);
} }
void InternalWindowTest::testScale()
{
QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection,
Q_ARG(int, 2),
Q_ARG(QVector<QRect>, QVector<QRect>({QRect(0,0,1280, 1024), QRect(1280/2, 0, 1280, 1024)})),
Q_ARG(QVector<int>, QVector<int>({2,2})));
QSignalSpy clientAddedSpy(waylandServer(), &WaylandServer::shellClientAdded);
QVERIFY(clientAddedSpy.isValid());
HelperWindow win;
win.setGeometry(0, 0, 100, 100);
win.setFlags(win.flags() | Qt::Popup);
win.show();
QCOMPARE(win.devicePixelRatio(), 2.0);
QVERIFY(clientAddedSpy.wait());
QCOMPARE(clientAddedSpy.count(), 1);
auto internalClient = clientAddedSpy.first().first().value<ShellClient*>();
QCOMPARE(internalClient->surface()->scale(), 2);
QMetaObject::invokeMethod(kwinApp()->platform(), "setVirtualOutputs", Qt::DirectConnection, Q_ARG(int, 2));
}
} }
WAYLANDTEST_MAIN(KWin::InternalWindowTest) WAYLANDTEST_MAIN(KWin::InternalWindowTest)

View file

@ -207,9 +207,9 @@ int main(int argc, char *argv[]) \
} }
#ifdef NO_XWAYLAND #ifdef NO_XWAYLAND
#define WAYLANDTEST_MAIN(TestObject) WAYLANDTEST_MAIN_HELPER(TestObject, QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling), KWin::Application::OperationModeWaylandOnly) #define WAYLANDTEST_MAIN(TestObject) WAYLANDTEST_MAIN_HELPER(TestObject, QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps), KWin::Application::OperationModeWaylandOnly)
#else #else
#define WAYLANDTEST_MAIN(TestObject) WAYLANDTEST_MAIN_HELPER(TestObject, QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling), KWin::Application::OperationModeXwayland) #define WAYLANDTEST_MAIN(TestObject) WAYLANDTEST_MAIN_HELPER(TestObject, QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps), KWin::Application::OperationModeXwayland)
#endif #endif
#endif #endif

View file

@ -553,7 +553,7 @@ int main(int argc, char * argv[])
qunsetenv("QT_DEVICE_PIXEL_RATIO"); qunsetenv("QT_DEVICE_PIXEL_RATIO");
qputenv("QT_IM_MODULE", "qtvirtualkeyboard"); qputenv("QT_IM_MODULE", "qtvirtualkeyboard");
qputenv("QSG_RENDER_LOOP", "basic"); qputenv("QSG_RENDER_LOOP", "basic");
QCoreApplication::setAttribute(Qt::AA_DisableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
KWin::ApplicationWayland a(argc, argv); KWin::ApplicationWayland a(argc, argv);
a.setupTranslator(); a.setupTranslator();
// reset QT_QPA_PLATFORM to a sane value for any processes started from KWin // reset QT_QPA_PLATFORM to a sane value for any processes started from KWin

View file

@ -47,6 +47,7 @@ BackingStore::BackingStore(QWindow *w, KWayland::Client::ShmPool *shm)
} }
const QSize size = m_backBuffer.size(); const QSize size = m_backBuffer.size();
m_backBuffer = QImage(b->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied); m_backBuffer = QImage(b->address(), size.width(), size.height(), QImage::Format_ARGB32_Premultiplied);
m_backBuffer.setDevicePixelRatio(scale());
} }
); );
} }
@ -61,7 +62,7 @@ QPaintDevice *BackingStore::paintDevice()
void BackingStore::resize(const QSize &size, const QRegion &staticContents) void BackingStore::resize(const QSize &size, const QRegion &staticContents)
{ {
Q_UNUSED(staticContents) Q_UNUSED(staticContents)
m_size = size; m_size = size * scale();
if (!m_buffer) { if (!m_buffer) {
return; return;
} }
@ -73,13 +74,15 @@ void BackingStore::flush(QWindow *window, const QRegion &region, const QPoint &o
{ {
Q_UNUSED(region) Q_UNUSED(region)
Q_UNUSED(offset) Q_UNUSED(offset)
auto s = static_cast<Window *>(window->handle())->surface();
auto w = static_cast<Window *>(window->handle());
auto s = w->surface();
if (!s) { if (!s) {
return; return;
} }
s->attachBuffer(m_buffer); s->attachBuffer(m_buffer);
// TODO: proper damage region // TODO: proper damage region
s->damage(QRect(QPoint(0, 0), m_backBuffer.size())); s->damage(QRect(QPoint(0, 0), m_backBuffer.size() / scale()));
s->commit(KWayland::Client::Surface::CommitFlag::None); s->commit(KWayland::Client::Surface::CommitFlag::None);
waylandServer()->internalClientConection()->flush(); waylandServer()->internalClientConection()->flush();
waylandServer()->dispatch(); waylandServer()->dispatch();
@ -108,6 +111,7 @@ void BackingStore::beginPaint(const QRegion&)
auto b = m_buffer.toStrongRef(); auto b = m_buffer.toStrongRef();
b->setUsed(true); b->setUsed(true);
m_backBuffer = QImage(b->address(), m_size.width(), m_size.height(), QImage::Format_ARGB32_Premultiplied); m_backBuffer = QImage(b->address(), m_size.width(), m_size.height(), QImage::Format_ARGB32_Premultiplied);
m_backBuffer.setDevicePixelRatio(scale());
if (oldBuffer) { if (oldBuffer) {
b->copy(oldBuffer->address()); b->copy(oldBuffer->address());
} else { } else {
@ -115,5 +119,10 @@ void BackingStore::beginPaint(const QRegion&)
} }
} }
int BackingStore::scale() const
{
return static_cast<Window *>(window()->handle())->scale();
}
} }
} }

View file

@ -48,6 +48,7 @@ public:
void beginPaint(const QRegion &) override; void beginPaint(const QRegion &) override;
private: private:
int scale() const;
KWayland::Client::ShmPool *m_shm; KWayland::Client::ShmPool *m_shm;
QWeakPointer<KWayland::Client::Buffer> m_buffer; QWeakPointer<KWayland::Client::Buffer> m_buffer;
QImage m_backBuffer; QImage m_backBuffer;

View file

@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#define WL_EGL_PLATFORM 1 #define WL_EGL_PLATFORM 1
#include "integration.h" #include "integration.h"
#include "window.h" #include "window.h"
#include "screens.h"
#include "../../shell_client.h" #include "../../shell_client.h"
#include "../../wayland_server.h" #include "../../wayland_server.h"
#include <logging.h> #include <logging.h>
@ -44,7 +45,10 @@ Window::Window(QWindow *window, KWayland::Client::Surface *surface, KWayland::Cl
, m_shellSurface(shellSurface) , m_shellSurface(shellSurface)
, m_windowId(++s_windowId) , m_windowId(++s_windowId)
, m_integration(integration) , m_integration(integration)
, m_scale(screens()->maxScale())
{ {
m_surface->setScale(m_scale);
QObject::connect(m_surface, &QObject::destroyed, window, [this] { m_surface = nullptr;}); QObject::connect(m_surface, &QObject::destroyed, window, [this] { m_surface = nullptr;});
QObject::connect(m_shellSurface, &QObject::destroyed, window, [this] { m_shellSurface = nullptr;}); QObject::connect(m_shellSurface, &QObject::destroyed, window, [this] { m_shellSurface = nullptr;});
waylandServer()->internalClientConection()->flush(); waylandServer()->internalClientConection()->flush();
@ -94,14 +98,17 @@ void Window::setGeometry(const QRect &rect)
if (rect.height() != oldRect.height()) { if (rect.height() != oldRect.height()) {
emit window()->heightChanged(rect.height()); emit window()->heightChanged(rect.height());
} }
const QSize nativeSize = rect.size() * m_scale;
if (m_contentFBO) { if (m_contentFBO) {
if (m_contentFBO->width() != geometry().width() || m_contentFBO->height() != geometry().height()) { if (m_contentFBO->size() != nativeSize) {
m_resized = true; m_resized = true;
} }
} }
#if HAVE_WAYLAND_EGL #if HAVE_WAYLAND_EGL
if (m_eglWaylandWindow) { if (m_eglWaylandWindow) {
wl_egl_window_resize(m_eglWaylandWindow, geometry().width(), geometry().height(), 0, 0); wl_egl_window_resize(m_eglWaylandWindow, nativeSize.width(), nativeSize.height(), 0, 0);
} }
#endif #endif
QWindowSystemInterface::handleGeometryChange(window(), geometry()); QWindowSystemInterface::handleGeometryChange(window(), geometry());
@ -124,7 +131,7 @@ void Window::unmap()
void Window::createEglSurface(EGLDisplay dpy, EGLConfig config) void Window::createEglSurface(EGLDisplay dpy, EGLConfig config)
{ {
#if HAVE_WAYLAND_EGL #if HAVE_WAYLAND_EGL
const QSize size = window()->size(); const QSize size = window()->size() * m_scale;
m_eglWaylandWindow = wl_egl_window_create(*m_surface, size.width(), size.height()); m_eglWaylandWindow = wl_egl_window_create(*m_surface, size.width(), size.height());
if (!m_eglWaylandWindow) { if (!m_eglWaylandWindow) {
return; return;
@ -158,7 +165,8 @@ void Window::createFBO()
if (m_contentFBO && r.size().isEmpty()) { if (m_contentFBO && r.size().isEmpty()) {
return; return;
} }
m_contentFBO.reset(new QOpenGLFramebufferObject(r.width(), r.height(), QOpenGLFramebufferObject::CombinedDepthStencil)); const QSize nativeSize = r.size() * m_scale;
m_contentFBO.reset(new QOpenGLFramebufferObject(nativeSize.width(), nativeSize.height(), QOpenGLFramebufferObject::CombinedDepthStencil));
if (!m_contentFBO->isValid()) { if (!m_contentFBO->isValid()) {
qCWarning(KWIN_QPA) << "Content FBO is not valid"; qCWarning(KWIN_QPA) << "Content FBO is not valid";
} }
@ -174,5 +182,15 @@ ShellClient *Window::shellClient()
return m_shellClient; return m_shellClient;
} }
int Window::scale() const
{
return m_scale;
}
qreal Window::devicePixelRatio() const
{
return m_scale;
}
} }
} }

View file

@ -74,6 +74,9 @@ public:
} }
void createEglSurface(EGLDisplay dpy, EGLConfig config); void createEglSurface(EGLDisplay dpy, EGLConfig config);
int scale() const;
qreal devicePixelRatio() const override;
void bindContentFBO(); void bindContentFBO();
const QSharedPointer<QOpenGLFramebufferObject> &contentFBO() const { const QSharedPointer<QOpenGLFramebufferObject> &contentFBO() const {
return m_contentFBO; return m_contentFBO;
@ -96,6 +99,7 @@ private:
#endif #endif
quint32 m_windowId; quint32 m_windowId;
const Integration *m_integration; const Integration *m_integration;
int m_scale = 1;
}; };
} }

View file

@ -510,9 +510,7 @@ void ShellClient::setInternalFramebufferObject(const QSharedPointer<QOpenGLFrame
return; return;
} }
//Kwin currently scales internal windows to 1, so this is currently always correct m_clientSize = fbo->size() / surface()->scale();
//when that changes, this needs adjusting
m_clientSize = fbo->size();
markAsMapped(); markAsMapped();
doSetGeometry(QRect(geom.topLeft(), m_clientSize)); doSetGeometry(QRect(geom.topLeft(), m_clientSize));
Toplevel::setInternalFramebufferObject(fbo); Toplevel::setInternalFramebufferObject(fbo);