~ci-train-bot/qtubuntu/qtubuntu-ubuntu-xenial-landing-005

« back to all changes in this revision

Viewing changes to src/ubuntumirclient/window.cpp

  • Committer: CI Train Bot
  • Author(s): Alberto Aguirre
  • Date: 2015-11-17 14:49:21 UTC
  • mfrom: (258.6.46 use-mir-surface-apis)
  • Revision ID: ci-train-bot@canonical.com-20151117144921-nrpn5zal5ncrjxvt
Add support for Qt popups and dialog windows.

I have done some refactoring, cleanup and some bug fixing.

- An UbuntuWindow internally creates UbuntuSurface
- UbuntuSurface is responsible for creating the mir surfaces and calling mir apis
- Fix mutex locking (now really protecting access to mSurface) was not locked properly (QMutexLocker temporaries were created : QMutexLocker(&d->mutex) instead of QMutexLocker lock(&d->mutex).
- Handling resize events from the server has been improved.
-- Old resize events are dropped - meaning no redraw requests are issued if we know there are newer resize events in the queue
-- Redraw requests are never issued through the rendering thread only through the Qt event dispatch thread.
-- No flushing of event queue which leads to fewer interruptions in other surfaces (specially noticeable on surfaces that do animations).
- Workaround QtCreator not assigning a parent to its menu bar menus
-- The last focused window is tracked and used if a Qt popup is created without a parent
- Client requested resizes (through setGeometry) is now supported
- Resizing constraints are supported (propagateSizeHints)
- Visibility and window state are tracked separately
- Better focusing event handling
-- When an app has multiple windows, mir will send focus lost/gain pairs,
   in which case we need to peek into the queue to avoid telling Qt to unfocus all windows prematurely.
Approved by: PS Jenkins bot, Daniel d'Andrada

Show diffs side-by-side

added added

removed removed

Lines of Context:
15
15
 */
16
16
 
17
17
// Local
 
18
#include "window.h"
18
19
#include "clipboard.h"
19
20
#include "input.h"
20
 
#include "window.h"
21
21
#include "screen.h"
22
22
#include "logging.h"
23
23
 
 
24
#include <mir_toolkit/mir_client_library.h>
 
25
 
24
26
// Qt
25
27
#include <qpa/qwindowsysteminterface.h>
26
 
#include <QMutex>
27
28
#include <QMutexLocker>
28
29
#include <QSize>
29
30
#include <QtMath>
33
34
 
34
35
#include <EGL/egl.h>
35
36
 
36
 
#define IS_OPAQUE_FLAG 1
37
 
 
38
37
namespace
39
38
{
 
39
 
 
40
// FIXME: this used to be defined by platform-api, but it's been removed in v3. Change ubuntu-keyboard to use
 
41
// a different enum for window roles.
 
42
enum UAUiWindowRole {
 
43
    U_MAIN_ROLE = 1,
 
44
    U_DASH_ROLE,
 
45
    U_INDICATOR_ROLE,
 
46
    U_NOTIFICATIONS_ROLE,
 
47
    U_GREETER_ROLE,
 
48
    U_LAUNCHER_ROLE,
 
49
    U_ON_SCREEN_KEYBOARD_ROLE,
 
50
    U_SHUTDOWN_DIALOG_ROLE,
 
51
};
 
52
 
 
53
struct MirSpecDeleter
 
54
{
 
55
    void operator()(MirSurfaceSpec *spec) { mir_surface_spec_release(spec); }
 
56
};
 
57
 
 
58
using Spec = std::unique_ptr<MirSurfaceSpec, MirSpecDeleter>;
 
59
 
 
60
EGLNativeWindowType nativeWindowFor(MirSurface *surf)
 
61
{
 
62
    auto stream = mir_surface_get_buffer_stream(surf);
 
63
    return reinterpret_cast<EGLNativeWindowType>(mir_buffer_stream_get_egl_native_window(stream));
 
64
}
 
65
 
40
66
MirSurfaceState qtWindowStateToMirSurfaceState(Qt::WindowState state)
41
67
{
42
68
    switch (state) {
43
69
    case Qt::WindowNoState:
44
70
        return mir_surface_state_restored;
45
 
 
46
71
    case Qt::WindowFullScreen:
47
72
        return mir_surface_state_fullscreen;
48
 
 
49
73
    case Qt::WindowMaximized:
50
74
        return mir_surface_state_maximized;
51
 
 
52
75
    case Qt::WindowMinimized:
53
76
        return mir_surface_state_minimized;
54
 
 
55
77
    default:
56
78
        LOG("Unexpected Qt::WindowState: %d", state);
57
79
        return mir_surface_state_restored;
64
86
    switch (state) {
65
87
    case Qt::WindowNoState:
66
88
        return "NoState";
67
 
 
68
89
    case Qt::WindowFullScreen:
69
90
        return "FullScreen";
70
 
 
71
91
    case Qt::WindowMaximized:
72
92
        return "Maximized";
73
 
 
74
93
    case Qt::WindowMinimized:
75
94
        return "Minimized";
76
 
 
77
95
    default:
78
96
        return "!?";
79
97
    }
80
98
}
81
99
#endif
82
100
 
83
 
} // anonymous namespace
84
 
 
85
 
class UbuntuWindowPrivate
86
 
{
87
 
public:
88
 
    void createEGLSurface(EGLNativeWindowType nativeWindow);
89
 
    void destroyEGLSurface();
90
 
    int panelHeight();
91
 
 
92
 
    UbuntuScreen* screen;
93
 
    EGLSurface eglSurface;
94
 
    WId id;
95
 
    UbuntuInput* input;
96
 
    Qt::WindowState state;
97
 
    MirConnection *connection;
98
 
    MirSurface* surface;
99
 
    QSize bufferSize;
100
 
    QMutex mutex;
101
 
    QSharedPointer<UbuntuClipboard> clipboard;
102
 
    int resizeCatchUpAttempts;
103
 
#if !defined(QT_NO_DEBUG)
104
 
    int frameNumber;
105
 
#endif
106
 
};
107
 
 
108
 
static void eventCallback(MirSurface* surface, const MirEvent *event, void* context)
109
 
{
110
 
    (void) surface;
111
 
    DASSERT(context != NULL);
112
 
    UbuntuWindow* platformWindow = static_cast<UbuntuWindow*>(context);
113
 
    platformWindow->priv()->input->postEvent(platformWindow, event);
114
 
}
115
 
 
116
 
static void surfaceCreateCallback(MirSurface* surface, void* context)
117
 
{
118
 
    DASSERT(context != NULL);
119
 
    UbuntuWindow* platformWindow = static_cast<UbuntuWindow*>(context);
120
 
    platformWindow->priv()->surface = surface;
121
 
 
122
 
    mir_surface_set_event_handler(surface, eventCallback, context);
123
 
}
124
 
 
125
 
UbuntuWindow::UbuntuWindow(QWindow* w, QSharedPointer<UbuntuClipboard> clipboard, UbuntuScreen* screen,
126
 
                           UbuntuInput* input, MirConnection* connection)
127
 
    : QObject(nullptr), QPlatformWindow(w)
128
 
{
129
 
    DASSERT(screen != NULL);
130
 
 
131
 
    d = new UbuntuWindowPrivate;
132
 
    d->screen = screen;
133
 
    d->eglSurface = EGL_NO_SURFACE;
134
 
    d->input = input;
135
 
    d->state = window()->windowState();
136
 
    d->connection = connection;
137
 
    d->clipboard = clipboard;
138
 
    d->resizeCatchUpAttempts = 0;
139
 
 
 
101
WId makeId()
 
102
{
140
103
    static int id = 1;
141
 
    d->id = id++;
142
 
 
143
 
#if !defined(QT_NO_DEBUG)
144
 
    d->frameNumber = 0;
145
 
#endif
146
 
 
147
 
    // Use client geometry if set explicitly, use available screen geometry otherwise.
148
 
    QPlatformWindow::setGeometry(window()->geometry() != screen->geometry() ?
149
 
        window()->geometry() : screen->availableGeometry());
150
 
    createWindow();
151
 
    DLOG("UbuntuWindow::UbuntuWindow (this=%p, w=%p, screen=%p, input=%p)", this, w, screen, input);
152
 
}
153
 
 
154
 
UbuntuWindow::~UbuntuWindow()
155
 
{
156
 
    DLOG("UbuntuWindow::~UbuntuWindow");
157
 
    d->destroyEGLSurface();
158
 
 
159
 
    mir_surface_release_sync(d->surface);
160
 
 
161
 
    delete d;
162
 
}
163
 
 
164
 
void UbuntuWindowPrivate::createEGLSurface(EGLNativeWindowType nativeWindow)
165
 
{
166
 
  DLOG("UbuntuWindowPrivate::createEGLSurface (this=%p, nativeWindow=%p)",
167
 
          this, reinterpret_cast<void*>(nativeWindow));
168
 
 
169
 
  eglSurface = eglCreateWindowSurface(screen->eglDisplay(), screen->eglConfig(),
170
 
          nativeWindow, nullptr);
171
 
 
172
 
  DASSERT(eglSurface != EGL_NO_SURFACE);
173
 
}
174
 
 
175
 
void UbuntuWindowPrivate::destroyEGLSurface()
176
 
{
177
 
    DLOG("UbuntuWindowPrivate::destroyEGLSurface (this=%p)", this);
178
 
    if (eglSurface != EGL_NO_SURFACE) {
179
 
        eglDestroySurface(screen->eglDisplay(), eglSurface);
180
 
        eglSurface = EGL_NO_SURFACE;
181
 
    }
 
104
    return id++;
 
105
}
 
106
 
 
107
MirPixelFormat defaultPixelFormatFor(MirConnection *connection)
 
108
{
 
109
    MirPixelFormat format;
 
110
    unsigned int nformats;
 
111
    mir_connection_get_available_surface_formats(connection, &format, 1, &nformats);
 
112
    return format;
 
113
}
 
114
 
 
115
UAUiWindowRole roleFor(QWindow *window)
 
116
{
 
117
    QVariant roleVariant = window->property("role");
 
118
    if (!roleVariant.isValid())
 
119
        return U_MAIN_ROLE;
 
120
 
 
121
    uint role = roleVariant.toUInt();
 
122
    if (role < U_MAIN_ROLE || role > U_SHUTDOWN_DIALOG_ROLE)
 
123
        return U_MAIN_ROLE;
 
124
 
 
125
    return static_cast<UAUiWindowRole>(role);
 
126
}
 
127
 
 
128
UbuntuWindow *transientParentFor(QWindow *window)
 
129
{
 
130
    QWindow *parent = window->transientParent();
 
131
    return parent ? static_cast<UbuntuWindow *>(parent->handle()) : nullptr;
 
132
}
 
133
 
 
134
Spec makeSurfaceSpec(QWindow *window, UbuntuInput *input, MirConnection *connection)
 
135
{
 
136
   const auto geom = window->geometry();
 
137
   const int width = geom.width() > 0 ? geom.width() : 1;
 
138
   const int height = geom.height() > 0 ? geom.height() : 1;
 
139
   const auto pixelFormat = defaultPixelFormatFor(connection);
 
140
 
 
141
   if (U_ON_SCREEN_KEYBOARD_ROLE == roleFor(window)) {
 
142
       DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - creating input method surface (width=%d, height=%d", window, width, height);
 
143
       return Spec{mir_connection_create_spec_for_input_method(connection, width, height, pixelFormat)};
 
144
   }
 
145
 
 
146
   const Qt::WindowType type = window->type();
 
147
   if (type == Qt::Popup) {
 
148
       auto parent = transientParentFor(window);
 
149
       if (parent == nullptr) {
 
150
           //NOTE: We cannot have a parentless popup -
 
151
           //try using the last surface to receive input as that will most likely be
 
152
           //the one that caused this popup to be created
 
153
           parent = input->lastFocusedWindow();
 
154
       }
 
155
       if (parent) {
 
156
           auto pos = geom.topLeft();
 
157
           pos -= parent->geometry().topLeft();
 
158
           MirRectangle location{pos.x(), pos.y(), 0, 0};
 
159
           DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - creating menu surface(width:%d, height:%d)", window, width, height);
 
160
           return Spec{mir_connection_create_spec_for_menu(
 
161
                       connection, width, height, pixelFormat, parent->mirSurface(),
 
162
                       &location, mir_edge_attachment_any)};
 
163
       } else {
 
164
           DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - cannot create a menu without a parent!", window);
 
165
       }
 
166
   } else if (type == Qt::Dialog) {
 
167
       auto parent = transientParentFor(window);
 
168
       if (parent) {
 
169
           // Modal dialog
 
170
           DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - creating modal dialog (width=%d, height=%d", window, width, height);
 
171
           return Spec{mir_connection_create_spec_for_modal_dialog(connection, width, height, pixelFormat, parent->mirSurface())};
 
172
       } else {
 
173
           // TODO: do Qt parentless dialogs have the same semantics as mir?
 
174
           DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - creating parentless dialog (width=%d, height=%d)", window, width, height);
 
175
           return Spec{mir_connection_create_spec_for_dialog(connection, width, height, pixelFormat)};
 
176
       }
 
177
   }
 
178
   DLOG("[ubuntumirclient QPA] makeSurfaceSpec(window=%p) - creating normal surface(type=0x%x, width=%d, height=%d)", window, type, width, height);
 
179
   return Spec{mir_connection_create_spec_for_normal_surface(connection, width, height, pixelFormat)};
 
180
}
 
181
 
 
182
void setSizingConstraints(MirSurfaceSpec *spec, const QSize& minSize, const QSize& maxSize, const QSize& increment)
 
183
{
 
184
    mir_surface_spec_set_min_width(spec, minSize.width());
 
185
    mir_surface_spec_set_min_height(spec, minSize.height());
 
186
    if (maxSize.width() >= minSize.width()) {
 
187
        mir_surface_spec_set_max_width(spec, maxSize.width());
 
188
    }
 
189
    if (maxSize.height() >= minSize.height()) {
 
190
        mir_surface_spec_set_max_height(spec, maxSize.height());
 
191
    }
 
192
    if (increment.width() > 0) {
 
193
        mir_surface_spec_set_width_increment(spec, increment.width());
 
194
    }
 
195
    if (increment.height() > 0) {
 
196
        mir_surface_spec_set_height_increment(spec, increment.height());
 
197
    }
 
198
}
 
199
 
 
200
MirSurface *createMirSurface(QWindow *window, UbuntuScreen *screen, UbuntuInput *input, MirConnection *connection)
 
201
{
 
202
    auto spec = makeSurfaceSpec(window, input, connection);
 
203
    const auto title = window->title().toUtf8();
 
204
    mir_surface_spec_set_name(spec.get(), title.constData());
 
205
 
 
206
    setSizingConstraints(spec.get(), window->minimumSize(), window->maximumSize(), window->sizeIncrement());
 
207
 
 
208
    if (window->windowState() == Qt::WindowFullScreen) {
 
209
        mir_surface_spec_set_fullscreen_on_output(spec.get(), screen->mirOutputId());
 
210
    }
 
211
 
 
212
    auto surface = mir_surface_create_sync(spec.get());
 
213
    Q_ASSERT(mir_surface_is_valid(surface));
 
214
    return surface;
182
215
}
183
216
 
184
217
// FIXME - in order to work around https://bugs.launchpad.net/mir/+bug/1346633
185
218
// we need to guess the panel height (3GU + 2DP)
186
 
int UbuntuWindowPrivate::panelHeight()
 
219
int panelHeight()
187
220
{
188
221
    const int defaultGridUnit = 8;
189
222
    int gridUnit = defaultGridUnit;
199
232
    return gridUnit * 3 + qFloor(densityPixelRatio) * 2;
200
233
}
201
234
 
202
 
namespace
203
 
{
204
 
static MirPixelFormat
205
 
mir_choose_default_pixel_format(MirConnection *connection)
206
 
{
207
 
    MirPixelFormat format[mir_pixel_formats];
208
 
    unsigned int nformats;
209
 
 
210
 
    mir_connection_get_available_surface_formats(connection,
211
 
        format, mir_pixel_formats, &nformats);
212
 
 
213
 
    return format[0];
214
 
}
215
 
}
216
 
 
217
 
void UbuntuWindow::createWindow()
218
 
{
219
 
    DLOG("UbuntuWindow::createWindow (this=%p)", this);
220
 
 
221
 
    // FIXME: remove this remnant of an old platform-api enum - needs ubuntu-keyboard update
222
 
    const int SCREEN_KEYBOARD_ROLE = 7;
223
 
    // Get surface role and flags.
224
 
    QVariant roleVariant = window()->property("role");
225
 
    int role = roleVariant.isValid() ? roleVariant.toUInt() : 1;  // 1 is the default role for apps.
226
 
    QVariant opaqueVariant = window()->property("opaque");
227
 
    uint flags = opaqueVariant.isValid() ?
228
 
        opaqueVariant.toUInt() ? static_cast<uint>(IS_OPAQUE_FLAG) : 0 : 0;
229
 
 
230
 
    // FIXME(loicm) Opaque flag is forced for now for non-system sessions (applications) for
231
 
    //     performance reasons.
232
 
    flags |= static_cast<uint>(IS_OPAQUE_FLAG);
233
 
 
234
 
    const QByteArray title = (!window()->title().isNull()) ? window()->title().toUtf8() : "Window 1"; // legacy title
235
 
    const int panelHeight = d->panelHeight();
236
 
 
237
 
#if !defined(QT_NO_DEBUG)
238
 
    LOG("panelHeight: '%d'", panelHeight);
239
 
    LOG("role: '%d'", role);
240
 
    LOG("flags: '%s'", (flags & static_cast<uint>(1)) ? "Opaque" : "NotOpaque");
241
 
    LOG("title: '%s'", title.constData());
242
 
#endif
243
 
 
244
 
    // Get surface geometry.
245
 
    QRect geometry;
246
 
    if (d->state == Qt::WindowFullScreen) {
247
 
        printf("UbuntuWindow - fullscreen geometry\n");
248
 
        geometry = screen()->geometry();
249
 
    } else if (d->state == Qt::WindowMaximized) {
250
 
        printf("UbuntuWindow - maximized geometry\n");
251
 
        geometry = screen()->availableGeometry();
252
 
        /*
253
 
         * FIXME: Autopilot relies on being able to convert coordinates relative of the window
254
 
         * into absolute screen coordinates. Mir does not allow this, see bug lp:1346633
255
 
         * Until there's a correct way to perform this transformation agreed, this horrible hack
256
 
         * guesses the transformation heuristically.
257
 
         *
258
 
         * Assumption: this method only used on phone devices!
259
 
         */
260
 
        geometry.setY(panelHeight);
261
 
    } else {
262
 
        printf("UbuntuWindow - regular geometry\n");
263
 
        geometry = this->geometry();
264
 
        geometry.setY(panelHeight);
265
 
    }
266
 
 
267
 
    DLOG("[ubuntumirclient QPA] creating surface at (%d, %d) with size (%d, %d) with title '%s'\n",
268
 
            geometry.x(), geometry.y(), geometry.width(), geometry.height(), title.data());
269
 
 
270
 
    MirSurfaceSpec *spec;
271
 
    if (role == SCREEN_KEYBOARD_ROLE)
272
 
    {
273
 
        spec = mir_connection_create_spec_for_input_method(d->connection, geometry.width(),
274
 
            geometry.height(), mir_choose_default_pixel_format(d->connection));
275
 
    }
276
 
    else
277
 
    {
278
 
        spec = mir_connection_create_spec_for_normal_surface(d->connection, geometry.width(),
279
 
            geometry.height(), mir_choose_default_pixel_format(d->connection));
280
 
    }
281
 
    mir_surface_spec_set_name(spec, title.data());
282
 
 
283
 
    // Create platform window
284
 
    mir_wait_for(mir_surface_create(spec, surfaceCreateCallback, this));
285
 
    mir_surface_spec_release(spec);
286
 
 
287
 
    DASSERT(d->surface != NULL);
288
 
    d->createEGLSurface((EGLNativeWindowType)mir_buffer_stream_get_egl_native_window(mir_surface_get_buffer_stream(d->surface)));
289
 
 
290
 
    if (d->state == Qt::WindowFullScreen) {
291
 
    // TODO: We could set this on creation once surface spec supports it (mps already up)
292
 
        mir_wait_for(mir_surface_set_state(d->surface, mir_surface_state_fullscreen));
293
 
    }
294
 
 
295
 
    // Window manager can give us a final size different from what we asked for
296
 
    // so let's check what we ended up getting
297
 
    {
 
235
} //namespace
 
236
 
 
237
class UbuntuSurface
 
238
{
 
239
public:
 
240
    UbuntuSurface(UbuntuWindow *platformWindow, UbuntuScreen *screen, UbuntuInput *input, MirConnection *connection)
 
241
        : mWindow(platformWindow->window())
 
242
        , mPlatformWindow(platformWindow)
 
243
        , mScreen(screen)
 
244
        , mInput(input)
 
245
        , mConnection(connection)
 
246
        , mMirSurface(createMirSurface(mWindow, screen, input, connection))
 
247
        , mEglDisplay(screen->eglDisplay())
 
248
        , mEglSurface(eglCreateWindowSurface(mEglDisplay, screen->eglConfig(), nativeWindowFor(mMirSurface), nullptr))
 
249
        , mVisible(false)
 
250
        , mNeedsRepaint(false)
 
251
        , mParented(mWindow->transientParent() || mWindow->parent())
 
252
        , mWindowState(mWindow->windowState())
 
253
 
 
254
    {
 
255
        mir_surface_set_event_handler(mMirSurface, surfaceEventCallback, this);
 
256
 
 
257
        // Window manager can give us a final size different from what we asked for
 
258
        // so let's check what we ended up getting
298
259
        MirSurfaceParameters parameters;
299
 
        mir_surface_get_parameters(d->surface, &parameters);
300
 
 
301
 
        geometry.setWidth(parameters.width);
302
 
        geometry.setHeight(parameters.height);
303
 
    }
304
 
 
305
 
    DLOG("[ubuntumirclient QPA] created surface has size (%d, %d)",
306
 
            geometry.width(), geometry.height());
307
 
 
308
 
    // Assume that the buffer size matches the surface size at creation time
309
 
    d->bufferSize = geometry.size();
310
 
 
311
 
    // Tell Qt about the geometry.
312
 
    QWindowSystemInterface::handleGeometryChange(window(), geometry);
313
 
    QPlatformWindow::setGeometry(geometry);
314
 
}
315
 
 
316
 
void UbuntuWindow::moveResize(const QRect& rect)
317
 
{
318
 
    (void) rect;
319
 
    // TODO: Not yet supported by mir.
320
 
}
321
 
 
322
 
void UbuntuWindow::handleSurfaceResize(int width, int height)
323
 
{
324
 
    QMutexLocker(&d->mutex);
325
 
    DLOG("UbuntuWindow::handleSurfaceResize(width=%d, height=%d) [%d]", width, height,
326
 
        d->frameNumber);
327
 
 
328
 
    // The current buffer size hasn't actually changed. so just render on it and swap
329
 
    // buffers in the hope that the next buffer will match the surface size advertised
330
 
    // in this event.
331
 
    // But since this event is processed by a thread different from the one that swaps
332
 
    // buffers, you can never know if this information is already outdated as there's
333
 
    // no synchronicity whatsoever between the processing of resize events and the
334
 
    // consumption of buffers.
335
 
    if (d->bufferSize.width() != width || d->bufferSize.height() != height) {
336
 
        // if the next buffer doesn't have a different size, try some
337
 
        // more
338
 
        // FIXME: This is working around a mir bug! We really shound't have to
339
 
        // swap more than once to get a buffer with the new size!
340
 
        d->resizeCatchUpAttempts = 2;
341
 
 
342
 
        QWindowSystemInterface::handleExposeEvent(window(), geometry());
343
 
        QWindowSystemInterface::flushWindowSystemEvents();
344
 
    }
345
 
}
346
 
 
347
 
void UbuntuWindow::handleSurfaceFocusChange(bool focused)
348
 
{
349
 
    LOG("UbuntuWindow::handleSurfaceFocusChange(focused=%s)", focused ? "true" : "false");
350
 
    QWindow *activatedWindow = focused ? window() : nullptr;
351
 
 
352
 
    // System clipboard contents might have changed while this window was unfocused and wihtout
 
260
        mir_surface_get_parameters(mMirSurface, &parameters);
 
261
 
 
262
        auto geom = mWindow->geometry();
 
263
        geom.setWidth(parameters.width);
 
264
        geom.setHeight(parameters.height);
 
265
        geom.setY(panelHeight());
 
266
 
 
267
        // Assume that the buffer size matches the surface size at creation time
 
268
        mBufferSize = geom.size();
 
269
        platformWindow->QPlatformWindow::setGeometry(geom);
 
270
        QWindowSystemInterface::handleGeometryChange(mWindow, geom);
 
271
 
 
272
        DLOG("[ubuntumirclient QPA] created surface at (%d, %d) with size (%d, %d), title '%s', role: '%d'\n",
 
273
             geom.x(), geom.y(), geom.width(), geom.height(), mWindow->title().toUtf8().constData(), roleFor(mWindow));
 
274
    }
 
275
 
 
276
    ~UbuntuSurface()
 
277
    {
 
278
        if (mEglSurface != EGL_NO_SURFACE)
 
279
            eglDestroySurface(mEglDisplay, mEglSurface);
 
280
        if (mMirSurface)
 
281
            mir_surface_release_sync(mMirSurface);
 
282
    }
 
283
 
 
284
    void resize(const QSize& newSize);
 
285
    void setState(Qt::WindowState newState);
 
286
    void setVisible(bool state);
 
287
    void updateTitle(const QString& title);
 
288
    void setSizingConstraints(const QSize& minSize, const QSize& maxSize, const QSize& increment);
 
289
 
 
290
    void onSwapBuffersDone();
 
291
    void handleSurfaceResized(int width, int height);
 
292
    int needsRepaint() const;
 
293
 
 
294
    EGLSurface eglSurface() const { return mEglSurface; }
 
295
    MirSurface *mirSurface() const { return mMirSurface; }
 
296
 
 
297
private:
 
298
    static void surfaceEventCallback(MirSurface* surface, const MirEvent *event, void* context);
 
299
    void postEvent(const MirEvent *event);
 
300
    void updateSurface();
 
301
 
 
302
    QWindow * const mWindow;
 
303
    UbuntuWindow * const mPlatformWindow;
 
304
    UbuntuScreen * const mScreen;
 
305
    UbuntuInput * const mInput;
 
306
    MirConnection * const mConnection;
 
307
 
 
308
    MirSurface * const mMirSurface;
 
309
    const EGLDisplay mEglDisplay;
 
310
    const EGLSurface mEglSurface;
 
311
 
 
312
    bool mVisible;
 
313
    bool mNeedsRepaint;
 
314
    bool mParented;
 
315
    Qt::WindowState mWindowState;
 
316
    QSize mBufferSize;
 
317
 
 
318
    QMutex mTargetSizeMutex;
 
319
    QSize mTargetSize;
 
320
};
 
321
 
 
322
void UbuntuSurface::resize(const QSize& size)
 
323
{
 
324
    DLOG("[ubuntumirclient QPA] resize(window=%p, width=%d, height=%d)", mWindow, size.width(), size.height());
 
325
 
 
326
    if (mWindowState == Qt::WindowFullScreen || mWindowState == Qt::WindowMaximized) {
 
327
        DLOG("[ubuntumirclient QPA] resize(window=%p) - not resizing, window is maximized or fullscreen", mWindow);
 
328
        return;
 
329
    }
 
330
 
 
331
    if (size.isEmpty()) {
 
332
        DLOG("[ubuntumirclient QPA] resize(window=%p) - not resizing, size is empty", mWindow);
 
333
        return;
 
334
    }
 
335
 
 
336
    Spec spec{mir_connection_create_spec_for_changes(mConnection)};
 
337
    mir_surface_spec_set_width(spec.get(), size.width());
 
338
    mir_surface_spec_set_height(spec.get(), size.height());
 
339
    mir_surface_apply_spec(mMirSurface, spec.get());
 
340
}
 
341
 
 
342
void UbuntuSurface::setState(Qt::WindowState newState)
 
343
{
 
344
    mir_wait_for(mir_surface_set_state(mMirSurface, qtWindowStateToMirSurfaceState(newState)));
 
345
    mWindowState = newState;
 
346
}
 
347
 
 
348
void UbuntuSurface::setVisible(bool visible)
 
349
{
 
350
    if (mVisible == visible)
 
351
        return;
 
352
 
 
353
    mVisible = visible;
 
354
 
 
355
    if (mVisible)
 
356
        updateSurface();
 
357
 
 
358
    // TODO: Use the new mir_surface_state_hidden state instead of mir_surface_state_minimized.
 
359
    //       Will have to change qtmir and unity8 for that.
 
360
    const auto newState = visible ? qtWindowStateToMirSurfaceState(mWindowState) : mir_surface_state_minimized;
 
361
    mir_wait_for(mir_surface_set_state(mMirSurface, newState));
 
362
}
 
363
 
 
364
void UbuntuSurface::updateTitle(const QString& newTitle)
 
365
{
 
366
    const auto title = newTitle.toUtf8();
 
367
    Spec spec{mir_connection_create_spec_for_changes(mConnection)};
 
368
    mir_surface_spec_set_name(spec.get(), title.constData());
 
369
    mir_surface_apply_spec(mMirSurface, spec.get());
 
370
}
 
371
 
 
372
void UbuntuSurface::setSizingConstraints(const QSize& minSize, const QSize& maxSize, const QSize& increment)
 
373
{
 
374
    Spec spec{mir_connection_create_spec_for_changes(mConnection)};
 
375
    ::setSizingConstraints(spec.get(), minSize, maxSize, increment);
 
376
    mir_surface_apply_spec(mMirSurface, spec.get());
 
377
}
 
378
 
 
379
void UbuntuSurface::handleSurfaceResized(int width, int height)
 
380
{
 
381
    QMutexLocker lock(&mTargetSizeMutex);
 
382
 
 
383
    // mir's resize event is mainly a signal that we need to redraw our content. We use the
 
384
    // width/height as identifiers to figure out if this is the latest surface resize event
 
385
    // that has posted, discarding any old ones. This avoids issuing too many redraw events.
 
386
    // see TODO in postEvent as the ideal way we should handle this.
 
387
    // The actual buffer size may or may have not changed at this point, so let the rendering
 
388
    // thread drive the window geometry updates.
 
389
    mNeedsRepaint = mTargetSize.width() == width && mTargetSize.height() == height;
 
390
}
 
391
 
 
392
int UbuntuSurface::needsRepaint() const
 
393
{
 
394
    if (mNeedsRepaint) {
 
395
        if (mTargetSize != mBufferSize) {
 
396
            //If the buffer hasn't changed yet, we need at least two redraws,
 
397
            //once to get the new buffer size and propagate the geometry changes
 
398
            //and the second to redraw the content at the new size
 
399
            return 2;
 
400
        } else {
 
401
            // The buffer size has already been updated so we only need one redraw
 
402
            // to render at the new size
 
403
            return 1;
 
404
        }
 
405
    }
 
406
    return 0;
 
407
}
 
408
 
 
409
void UbuntuSurface::onSwapBuffersDone()
 
410
{
 
411
#if !defined(QT_NO_DEBUG)
 
412
    static int sFrameNumber = 0;
 
413
    ++sFrameNumber;
 
414
#endif
 
415
 
 
416
    EGLint eglSurfaceWidth = -1;
 
417
    EGLint eglSurfaceHeight = -1;
 
418
    eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &eglSurfaceWidth);
 
419
    eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &eglSurfaceHeight);
 
420
 
 
421
    const bool validSize = eglSurfaceWidth > 0 && eglSurfaceHeight > 0;
 
422
 
 
423
    if (validSize && (mBufferSize.width() != eglSurfaceWidth || mBufferSize.height() != eglSurfaceHeight)) {
 
424
 
 
425
        DLOG("[ubuntumirclient QPA] onSwapBuffersDone(window=%p) [%d] - size changed (%d, %d) => (%d, %d)",
 
426
               mWindow, sFrameNumber, mBufferSize.width(), mBufferSize.height(), eglSurfaceWidth, eglSurfaceHeight);
 
427
 
 
428
        mBufferSize.rwidth() = eglSurfaceWidth;
 
429
        mBufferSize.rheight() = eglSurfaceHeight;
 
430
 
 
431
        QRect newGeometry = mPlatformWindow->geometry();
 
432
        newGeometry.setSize(mBufferSize);
 
433
 
 
434
        mPlatformWindow->QPlatformWindow::setGeometry(newGeometry);
 
435
        QWindowSystemInterface::handleGeometryChange(mWindow, newGeometry);
 
436
    } else {
 
437
        DLOG("[ubuntumirclient QPA] onSwapBuffersDone(window=%p) [%d] - buffer size (%d,%d)",
 
438
               mWindow, sFrameNumber, mBufferSize.width(), mBufferSize.height());
 
439
    }
 
440
}
 
441
 
 
442
void UbuntuSurface::surfaceEventCallback(MirSurface *surface, const MirEvent *event, void* context)
 
443
{
 
444
    Q_UNUSED(surface);
 
445
    Q_ASSERT(context != nullptr);
 
446
 
 
447
    auto s = static_cast<UbuntuSurface *>(context);
 
448
    s->postEvent(event);
 
449
}
 
450
 
 
451
void UbuntuSurface::postEvent(const MirEvent *event)
 
452
{
 
453
    if (mir_event_type_resize == mir_event_get_type(event)) {
 
454
        // TODO: The current event queue just accumulates all resize events;
 
455
        // It would be nicer if we could update just one event if that event has not been dispatched.
 
456
        // As a workaround, we use the width/height as an identifier of this latest event
 
457
        // so the event handler (handleSurfaceResized) can discard/ignore old ones.
 
458
        const auto resizeEvent = mir_event_get_resize_event(event);
 
459
        const auto width =  mir_resize_event_get_width(resizeEvent);
 
460
        const auto height =  mir_resize_event_get_height(resizeEvent);
 
461
        DLOG("[ubuntumirclient QPA] resizeEvent(window=%p, width=%d, height=%d)", mWindow, width, height);
 
462
 
 
463
        QMutexLocker lock(&mTargetSizeMutex);
 
464
        mTargetSize.rwidth() = width;
 
465
        mTargetSize.rheight() = height;
 
466
    }
 
467
 
 
468
    mInput->postEvent(mPlatformWindow, event);
 
469
}
 
470
 
 
471
void UbuntuSurface::updateSurface()
 
472
{
 
473
    DLOG("[ubuntumirclient QPA] updateSurface(window=%p)", mWindow);
 
474
 
 
475
    if (!mParented && mWindow->type() == Qt::Dialog) {
 
476
        // The dialog may have been parented after creation time
 
477
        // so morph it into a modal dialog
 
478
        auto parent = transientParentFor(mWindow);
 
479
        if (parent) {
 
480
            DLOG("[ubuntumirclient QPA] updateSurface(window=%p) dialog now parented", mWindow);
 
481
            mParented = true;
 
482
            Spec spec{mir_connection_create_spec_for_changes(mConnection)};
 
483
            mir_surface_spec_set_parent(spec.get(), parent->mirSurface());
 
484
            mir_surface_apply_spec(mMirSurface, spec.get());
 
485
        }
 
486
    }
 
487
}
 
488
 
 
489
UbuntuWindow::UbuntuWindow(QWindow *w, QSharedPointer<UbuntuClipboard> clipboard, UbuntuScreen *screen,
 
490
                           UbuntuInput *input, MirConnection *connection)
 
491
    : QObject(nullptr)
 
492
    , QPlatformWindow(w)
 
493
    , mId(makeId())
 
494
    , mClipboard(clipboard)
 
495
    , mSurface(new UbuntuSurface{this, screen, input, connection})
 
496
{
 
497
    DLOG("[ubuntumirclient QPA] UbuntuWindow(window=%p, screen=%p, input=%p, surf=%p)", w, screen, input, mSurface.get());
 
498
}
 
499
 
 
500
UbuntuWindow::~UbuntuWindow()
 
501
{
 
502
    DLOG("[ubuntumirclient QPA] ~UbuntuWindow(window=%p)", this);
 
503
}
 
504
 
 
505
void UbuntuWindow::handleSurfaceResized(int width, int height)
 
506
{
 
507
    QMutexLocker lock(&mMutex);
 
508
    DLOG("[ubuntumirclient QPA] handleSurfaceResize(window=%p, width=%d, height=%d)", window(), width, height);
 
509
 
 
510
    mSurface->handleSurfaceResized(width, height);
 
511
 
 
512
    // This resize event could have occurred just after the last buffer swap for this window.
 
513
    // This means the client may still be holding a buffer with the older size. The first redraw call
 
514
    // will then render at the old size. After swapping the client now will get a new buffer with the
 
515
    // updated size but it still needs re-rendering so another redraw may be needed.
 
516
    // A mir API to drop the currently held buffer would help here, so that we wouldn't have to redraw twice
 
517
    auto const numRepaints = mSurface->needsRepaint();
 
518
    DLOG("[ubuntumirclient QPA] handleSurfaceResize(window=%p) redraw %d times", window(), numRepaints);
 
519
    for (int i = 0; i < numRepaints; i++) {
 
520
        DLOG("[ubuntumirclient QPA] handleSurfaceResize(window=%p) repainting width=%d, height=%d", window(), geometry().size().width(), geometry().size().height());
 
521
        QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(), geometry().size()));
 
522
    }
 
523
}
 
524
 
 
525
void UbuntuWindow::handleSurfaceFocused()
 
526
{
 
527
    DLOG("[ubuntumirclient QPA] handleSurfaceFocused(window=%p)", window());
 
528
 
 
529
    // System clipboard contents might have changed while this window was unfocused and without
353
530
    // this process getting notified about it because it might have been suspended (due to
354
531
    // application lifecycle policies), thus unable to listen to any changes notified through
355
532
    // D-Bus.
356
533
    // Therefore let's ensure we are up to date with the system clipboard now that we are getting
357
534
    // focused again.
358
 
    if (focused) {
359
 
        d->clipboard->requestDBusClipboardContents();
360
 
    }
361
 
 
362
 
    QWindowSystemInterface::handleWindowActivated(activatedWindow, Qt::ActiveWindowFocusReason);
 
535
    mClipboard->requestDBusClipboardContents();
 
536
    QWindowSystemInterface::handleWindowActivated(window(), Qt::ActiveWindowFocusReason);
363
537
}
364
538
 
365
539
void UbuntuWindow::setWindowState(Qt::WindowState state)
366
540
{
367
 
    QMutexLocker(&d->mutex);
368
 
    DLOG("UbuntuWindow::setWindowState (this=%p, %s)", this,  qtWindowStateToStr(state));
369
 
 
370
 
    if (state == d->state)
371
 
        return;
372
 
 
373
 
    // TODO: Perhaps we should check if the states are applied?
374
 
    mir_wait_for(mir_surface_set_state(d->surface, qtWindowStateToMirSurfaceState(state)));
375
 
    d->state = state;
 
541
    QMutexLocker lock(&mMutex);
 
542
    DLOG("[ubuntumirclient QPA] setWindowState(window=%p, %s)", this, qtWindowStateToStr(state));
 
543
    mSurface->setState(state);
376
544
}
377
545
 
378
546
void UbuntuWindow::setGeometry(const QRect& rect)
379
547
{
380
 
    DLOG("UbuntuWindow::setGeometry (this=%p)", this);
381
 
 
382
 
    bool doMoveResize;
383
 
 
384
 
    {
385
 
        QMutexLocker(&d->mutex);
386
 
        QPlatformWindow::setGeometry(rect);
387
 
        doMoveResize = d->state != Qt::WindowFullScreen && d->state != Qt::WindowMaximized;
388
 
    }
389
 
 
390
 
    if (doMoveResize) {
391
 
        moveResize(rect);
392
 
    }
 
548
    QMutexLocker lock(&mMutex);
 
549
    DLOG("[ubuntumirclient QPA] setGeometry (window=%p, x=%d, y=%d, width=%d, height=%d)",
 
550
           window(), rect.x(), rect.y(), rect.width(), rect.height());
 
551
 
 
552
    //NOTE: mir surfaces cannot be moved by the client so ignore the topLeft coordinates
 
553
    const auto newSize = rect.size();
 
554
    auto newGeometry = geometry();
 
555
    newGeometry.setSize(newSize);
 
556
    QPlatformWindow::setGeometry(newGeometry);
 
557
 
 
558
    mSurface->resize(newSize);
393
559
}
394
560
 
395
561
void UbuntuWindow::setVisible(bool visible)
396
562
{
397
 
    QMutexLocker(&d->mutex);
398
 
    DLOG("UbuntuWindow::setVisible (this=%p, visible=%s)", this, visible ? "true" : "false");
399
 
 
400
 
    if (visible) {
401
 
        mir_wait_for(mir_surface_set_state(d->surface, qtWindowStateToMirSurfaceState(d->state)));
402
 
 
403
 
        QWindowSystemInterface::handleExposeEvent(window(), QRect());
404
 
        QWindowSystemInterface::flushWindowSystemEvents();
405
 
    } else {
406
 
        // TODO: Use the new mir_surface_state_hidden state instead of mir_surface_state_minimized.
407
 
        //       Will have to change qtmir and unity8 for that.
408
 
        mir_wait_for(mir_surface_set_state(d->surface, mir_surface_state_minimized));
409
 
    }
410
 
}
411
 
 
412
 
void UbuntuWindow::setWindowTitle(const QString &title)
413
 
{
414
 
    MirSurfaceSpec *spec = mir_connection_create_spec_for_changes(d->connection);
415
 
    mir_surface_spec_set_name(spec, title.toUtf8().constData());
416
 
    mir_surface_apply_spec(d->surface, spec);
417
 
    mir_surface_spec_release(spec);
 
563
    QMutexLocker lock(&mMutex);
 
564
    DLOG("[ubuntumirclient QPA] setVisible (window=%p, visible=%s)", window(), visible ? "true" : "false");
 
565
 
 
566
    mSurface->setVisible(visible);
 
567
    const QRect& exposeRect = visible ? QRect(QPoint(), geometry().size()) : QRect();
 
568
 
 
569
    lock.unlock();
 
570
    QWindowSystemInterface::handleExposeEvent(window(), exposeRect);
 
571
    QWindowSystemInterface::flushWindowSystemEvents();
 
572
}
 
573
 
 
574
void UbuntuWindow::setWindowTitle(const QString& title)
 
575
{
 
576
    QMutexLocker lock(&mMutex);
 
577
    DLOG("[ubuntumirclient QPA] setWindowTitle(window=%p) title=%s)", window(), title.toUtf8().constData());
 
578
    mSurface->updateTitle(title);
 
579
}
 
580
 
 
581
void UbuntuWindow::propagateSizeHints()
 
582
{
 
583
    QMutexLocker lock(&mMutex);
 
584
    const auto win = window();
 
585
    DLOG("[ubuntumirclient QPA] propagateSizeHints(window=%p) min(%d,%d), max(%d,%d) increment(%d, %d)",
 
586
           win, win->minimumSize().width(), win->minimumSize().height(),
 
587
           win->maximumSize().width(), win->maximumSize().height(),
 
588
           win->sizeIncrement().width(), win->sizeIncrement().height());
 
589
    mSurface->setSizingConstraints(win->minimumSize(), win->maximumSize(), win->sizeIncrement());
418
590
}
419
591
 
420
592
void* UbuntuWindow::eglSurface() const
421
593
{
422
 
    return d->eglSurface;
 
594
    return mSurface->eglSurface();
 
595
}
 
596
 
 
597
MirSurface *UbuntuWindow::mirSurface() const
 
598
{
 
599
    return mSurface->mirSurface();
423
600
}
424
601
 
425
602
WId UbuntuWindow::winId() const
426
603
{
427
 
    return d->id;
 
604
    return mId;
428
605
}
429
606
 
430
 
void UbuntuWindow::onBuffersSwapped_threadSafe(int newBufferWidth, int newBufferHeight)
 
607
void UbuntuWindow::onSwapBuffersDone()
431
608
{
432
 
    QMutexLocker(&d->mutex);
433
 
 
434
 
    bool sizeKnown = newBufferWidth > 0 && newBufferHeight > 0;
435
 
 
436
 
#if !defined(QT_NO_DEBUG)
437
 
    ++d->frameNumber;
438
 
#endif
439
 
 
440
 
    if (sizeKnown && (d->bufferSize.width() != newBufferWidth ||
441
 
                d->bufferSize.height() != newBufferHeight)) {
442
 
        d->resizeCatchUpAttempts = 0;
443
 
 
444
 
        DLOG("UbuntuWindow::onBuffersSwapped_threadSafe [%d] - buffer size changed from (%d,%d) to (%d,%d)"
445
 
               " resizeCatchUpAttempts=%d",
446
 
               d->frameNumber, d->bufferSize.width(), d->bufferSize.height(), newBufferWidth, newBufferHeight,
447
 
               d->resizeCatchUpAttempts);
448
 
 
449
 
        d->bufferSize.rwidth() = newBufferWidth;
450
 
        d->bufferSize.rheight() = newBufferHeight;
451
 
 
452
 
        QRect newGeometry;
453
 
 
454
 
        newGeometry = geometry();
455
 
        newGeometry.setWidth(d->bufferSize.width());
456
 
        newGeometry.setHeight(d->bufferSize.height());
457
 
 
458
 
        QPlatformWindow::setGeometry(newGeometry);
459
 
        QWindowSystemInterface::handleGeometryChange(window(), newGeometry, QRect());
460
 
    } else if (d->resizeCatchUpAttempts > 0) {
461
 
        --d->resizeCatchUpAttempts;
462
 
        DLOG("UbuntuWindow::onBuffersSwapped_threadSafe [%d] - buffer size (%d,%d). Redrawing to catch up a resized buffer."
463
 
               " resizeCatchUpAttempts=%d",
464
 
               d->frameNumber, d->bufferSize.width(), d->bufferSize.height(), d->resizeCatchUpAttempts);
465
 
        QWindowSystemInterface::handleExposeEvent(window(), geometry());
466
 
    } else {
467
 
        DLOG("UbuntuWindow::onBuffersSwapped_threadSafe [%d] - buffer size (%d,%d). resizeCatchUpAttempts=%d",
468
 
               d->frameNumber, d->bufferSize.width(), d->bufferSize.height(), d->resizeCatchUpAttempts);
469
 
    }
 
609
    QMutexLocker lock(&mMutex);
 
610
    mSurface->onSwapBuffersDone();
470
611
}