From 870bcf6e1af5a493b2e2583d98b3b12b7ab3a05d Mon Sep 17 00:00:00 2001 From: Christian Rauch Date: Sat, 25 Jul 2020 15:58:29 +0100 Subject: GHOST/wayland: adapt window and cursor surface scale to support HiDPI screens --- intern/ghost/intern/GHOST_SystemWayland.cpp | 228 ++++++++++++++++++++-------- intern/ghost/intern/GHOST_SystemWayland.h | 14 ++ intern/ghost/intern/GHOST_WindowWayland.cpp | 99 +++++++++++- intern/ghost/intern/GHOST_WindowWayland.h | 17 +++ 4 files changed, 288 insertions(+), 70 deletions(-) (limited to 'intern') diff --git a/intern/ghost/intern/GHOST_SystemWayland.cpp b/intern/ghost/intern/GHOST_SystemWayland.cpp index d6e46f27918..1e9a2940c32 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.cpp +++ b/intern/ghost/intern/GHOST_SystemWayland.cpp @@ -53,15 +53,6 @@ #include -struct output_t { - struct wl_output *output; - int32_t width, height; - int transform; - int scale; - std::string make; - std::string model; -}; - struct buffer_t { void *data; size_t size; @@ -73,6 +64,12 @@ struct cursor_t { struct wl_buffer *buffer; struct wl_cursor_image image; struct buffer_t *file_buffer = nullptr; + struct wl_cursor_theme *theme = nullptr; + int size; + std::string theme_name; + // outputs on which the cursor is visible + std::unordered_set outputs; + int scale = 1; }; struct data_offer_t { @@ -146,7 +143,10 @@ struct display_t { struct wl_shm *shm = nullptr; std::vector outputs; std::vector inputs; - struct wl_cursor_theme *cursor_theme = nullptr; + struct { + std::string theme; + int size; + } cursor; struct wl_data_device_manager *data_device_manager = nullptr; struct zwp_relative_pointer_manager_v1 *relative_pointer_manager = nullptr; struct zwp_pointer_constraints_v1 *pointer_constraints = nullptr; @@ -155,6 +155,8 @@ struct display_t { std::vector os_egl_windows; }; +static GHOST_WindowManager *window_manager = nullptr; + static void display_destroy(display_t *d) { if (d->data_device_manager) { @@ -189,6 +191,9 @@ static void display_destroy(display_t *d) if (input->cursor.surface) { wl_surface_destroy(input->cursor.surface); } + if (input->cursor.theme) { + wl_cursor_theme_destroy(input->cursor.theme); + } if (input->pointer) { wl_pointer_destroy(input->pointer); } @@ -211,10 +216,6 @@ static void display_destroy(display_t *d) delete input; } - if (d->cursor_theme) { - wl_cursor_theme_destroy(d->cursor_theme); - } - if (d->shm) { wl_shm_destroy(d->shm); } @@ -801,6 +802,69 @@ const struct wl_buffer_listener cursor_buffer_listener = { cursor_buffer_release, }; +static GHOST_IWindow *get_window(struct wl_surface *surface) +{ + if (!surface) { + return nullptr; + } + + for (GHOST_IWindow *win : window_manager->getWindows()) { + if (surface == static_cast(win)->surface()) { + return win; + } + } + return nullptr; +} + +static bool update_cursor_scale(cursor_t &cursor, wl_shm *shm) +{ + int scale = 0; + for (const output_t *output : cursor.outputs) { + if (output->scale > scale) + scale = output->scale; + } + + if (scale > 0 && cursor.scale != scale) { + cursor.scale = scale; + wl_surface_set_buffer_scale(cursor.surface, scale); + wl_cursor_theme_destroy(cursor.theme); + cursor.theme = wl_cursor_theme_load(cursor.theme_name.c_str(), scale * cursor.size, shm); + return true; + } + return false; +} + +static void cursor_surface_enter(void *data, + struct wl_surface * /*wl_surface*/, + struct wl_output *output) +{ + input_t *input = static_cast(data); + for (const output_t *reg_output : input->system->outputs()) { + if (reg_output->output == output) { + input->cursor.outputs.insert(reg_output); + } + } + update_cursor_scale(input->cursor, input->system->shm()); +} + +static void cursor_surface_leave(void *data, + struct wl_surface * /*wl_surface*/, + struct wl_output *output) +{ + input_t *input = static_cast(data); + for (const output_t *reg_output : input->system->outputs()) { + if (reg_output->output == output) { + input->cursor.outputs.erase(reg_output); + } + } + update_cursor_scale(input->cursor, input->system->shm()); +} + +struct wl_surface_listener cursor_surface_listener = { + cursor_surface_enter, + cursor_surface_leave, +}; + static void pointer_enter(void *data, struct wl_pointer * /*wl_pointer*/, uint32_t serial, @@ -808,22 +872,28 @@ static void pointer_enter(void *data, wl_fixed_t surface_x, wl_fixed_t surface_y) { - if (!surface) { + GHOST_WindowWayland *win = static_cast(get_window(surface)); + + if (!win) { return; } + + win->activate(); + input_t *input = static_cast(data); input->pointer_serial = serial; - input->x = wl_fixed_to_int(surface_x); - input->y = wl_fixed_to_int(surface_y); + input->x = win->scale() * wl_fixed_to_int(surface_x); + input->y = win->scale() * wl_fixed_to_int(surface_y); input->focus_pointer = surface; - input->system->pushEvent( - new GHOST_EventCursor(input->system->getMilliSeconds(), - GHOST_kEventCursorMove, - static_cast(wl_surface_get_user_data(surface)), - input->x, - input->y, - GHOST_TABLET_DATA_NONE)); + win->setCursorShape(win->getCursorShape()); + + input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), + GHOST_kEventCursorMove, + static_cast(win), + input->x, + input->y, + GHOST_TABLET_DATA_NONE)); } static void pointer_leave(void *data, @@ -831,9 +901,14 @@ static void pointer_leave(void *data, uint32_t /*serial*/, struct wl_surface *surface) { - if (surface != nullptr) { - static_cast(data)->focus_pointer = nullptr; + GHOST_IWindow *win = get_window(surface); + + if (!win) { + return; } + + static_cast(data)->focus_pointer = nullptr; + static_cast(win)->deactivate(); } static void pointer_motion(void *data, @@ -844,21 +919,20 @@ static void pointer_motion(void *data, { input_t *input = static_cast(data); - GHOST_IWindow *win = static_cast( - wl_surface_get_user_data(input->focus_pointer)); + GHOST_WindowWayland *win = static_cast(get_window(input->focus_pointer)); if (!win) { return; } - input->x = wl_fixed_to_int(surface_x); - input->y = wl_fixed_to_int(surface_y); + input->x = win->scale() * wl_fixed_to_int(surface_x); + input->y = win->scale() * wl_fixed_to_int(surface_y); input->system->pushEvent(new GHOST_EventCursor(input->system->getMilliSeconds(), GHOST_kEventCursorMove, win, - wl_fixed_to_int(surface_x), - wl_fixed_to_int(surface_y), + input->x, + input->y, GHOST_TABLET_DATA_NONE)); } @@ -869,6 +943,14 @@ static void pointer_button(void *data, uint32_t button, uint32_t state) { + input_t *input = static_cast(data); + + GHOST_IWindow *win = get_window(input->focus_pointer); + + if (!win) { + return; + } + GHOST_TEventType etype = GHOST_kEventUnknown; switch (state) { case WL_POINTER_BUTTON_STATE_RELEASED: @@ -892,9 +974,6 @@ static void pointer_button(void *data, break; } - input_t *input = static_cast(data); - GHOST_IWindow *win = static_cast( - wl_surface_get_user_data(input->focus_pointer)); input->data_source->source_serial = serial; input->buttons.set(ebutton, state == WL_POINTER_BUTTON_STATE_PRESSED); input->system->pushEvent(new GHOST_EventButton( @@ -907,12 +986,18 @@ static void pointer_axis(void *data, uint32_t axis, wl_fixed_t value) { + input_t *input = static_cast(data); + + GHOST_IWindow *win = get_window(input->focus_pointer); + + if (!win) { + return; + } + if (axis != WL_POINTER_AXIS_VERTICAL_SCROLL) { return; } - input_t *input = static_cast(data); - GHOST_IWindow *win = static_cast( - wl_surface_get_user_data(input->focus_pointer)); + input->system->pushEvent( new GHOST_EventWheel(input->system->getMilliSeconds(), win, std::signbit(value) ? +1 : -1)); } @@ -1142,7 +1227,12 @@ static void seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capa input->cursor.visible = true; input->cursor.buffer = nullptr; input->cursor.file_buffer = new buffer_t; + if (!get_cursor_settings(input->cursor.theme_name, input->cursor.size)) { + input->cursor.theme_name = std::string(); + input->cursor.size = default_cursor_size; + } wl_pointer_add_listener(input->pointer, &pointer_listener, data); + wl_surface_add_listener(input->cursor.surface, &cursor_surface_listener, data); } if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD) { @@ -1165,8 +1255,8 @@ static void output_geometry(void *data, struct wl_output * /*wl_output*/, int32_t /*x*/, int32_t /*y*/, - int32_t /*physical_width*/, - int32_t /*physical_height*/, + int32_t physical_width, + int32_t physical_height, int32_t /*subpixel*/, const char *make, const char *model, @@ -1176,6 +1266,8 @@ static void output_geometry(void *data, output->transform = transform; output->make = std::string(make); output->model = std::string(model); + output->width_mm = physical_width; + output->height_mm = physical_height; } static void output_mode(void *data, @@ -1186,8 +1278,8 @@ static void output_mode(void *data, int32_t /*refresh*/) { output_t *output = static_cast(data); - output->width = width; - output->height = height; + output->width_pxl = width; + output->height_pxl = height; } /** @@ -1232,7 +1324,7 @@ static void global_add(void *data, struct display_t *display = static_cast(data); if (!strcmp(interface, wl_compositor_interface.name)) { display->compositor = static_cast( - wl_registry_bind(wl_registry, name, &wl_compositor_interface, 1)); + wl_registry_bind(wl_registry, name, &wl_compositor_interface, 3)); } else if (!strcmp(interface, xdg_wm_base_interface.name)) { display->xdg_shell = static_cast( @@ -1340,19 +1432,6 @@ GHOST_SystemWayland::GHOST_SystemWayland() : GHOST_System(), d(new display_t) wl_data_device_add_listener(input->data_device, &data_device_listener, input); } } - - std::string theme; - int size; - if (!get_cursor_settings(theme, size)) { - theme = std::string(); - size = default_cursor_size; - } - - d->cursor_theme = wl_cursor_theme_load(theme.c_str(), size, d->shm); - if (!d->cursor_theme) { - display_destroy(d); - throw std::runtime_error("Wayland: unable to access cursor themes!"); - } } GHOST_SystemWayland::~GHOST_SystemWayland() @@ -1479,8 +1558,8 @@ void GHOST_SystemWayland::getMainDisplayDimensions(GHOST_TUns32 &width, GHOST_TU { if (getNumDisplays() > 0) { /* We assume first output as main. */ - width = uint32_t(d->outputs[0]->width); - height = uint32_t(d->outputs[0]->height); + width = uint32_t(d->outputs[0]->width_pxl) / d->outputs[0]->scale; + height = uint32_t(d->outputs[0]->height_pxl) / d->outputs[0]->scale; } } @@ -1538,6 +1617,11 @@ GHOST_IWindow *GHOST_SystemWayland::createWindow(const char *title, const bool is_dialog, const GHOST_IWindow *parentWindow) { + /* globally store pointer to window manager */ + if (!window_manager) { + window_manager = getWindowManager(); + } + GHOST_WindowWayland *window = new GHOST_WindowWayland( this, title, @@ -1582,6 +1666,16 @@ xdg_wm_base *GHOST_SystemWayland::shell() return d->xdg_shell; } +const std::vector &GHOST_SystemWayland::outputs() const +{ + return d->outputs; +} + +wl_shm *GHOST_SystemWayland::shm() const +{ + return d->shm; +} + void GHOST_SystemWayland::setSelection(const std::string &selection) { this->selection = selection; @@ -1603,8 +1697,8 @@ static void set_cursor_buffer(input_t *input, wl_buffer *buffer) wl_pointer_set_cursor(input->pointer, input->pointer_serial, input->cursor.surface, - int32_t(input->cursor.image.hotspot_x), - int32_t(input->cursor.image.hotspot_y)); + int32_t(input->cursor.image.hotspot_x) / input->cursor.scale, + int32_t(input->cursor.image.hotspot_y) / input->cursor.scale); } } @@ -1616,7 +1710,15 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape) const std::string cursor_name = cursors.count(shape) ? cursors.at(shape) : cursors.at(GHOST_kStandardCursorDefault); - wl_cursor *cursor = wl_cursor_theme_get_cursor(d->cursor_theme, cursor_name.c_str()); + input_t *input = d->inputs[0]; + cursor_t *c = &input->cursor; + + if (!c->theme) { + /* The cursor surface hasn't entered an output yet. Initialise theme with scale 1. */ + c->theme = wl_cursor_theme_load(c->theme_name.c_str(), c->size, d->inputs[0]->system->shm()); + } + + wl_cursor *cursor = wl_cursor_theme_get_cursor(c->theme, cursor_name.c_str()); if (!cursor) { GHOST_PRINT("cursor '" << cursor_name << "' does not exist" << std::endl); @@ -1628,11 +1730,11 @@ GHOST_TSuccess GHOST_SystemWayland::setCursorShape(GHOST_TStandardCursor shape) if (!buffer) { return GHOST_kFailure; } - cursor_t *c = &d->inputs[0]->cursor; + c->buffer = buffer; c->image = *image; - set_cursor_buffer(d->inputs[0], buffer); + set_cursor_buffer(input, buffer); return GHOST_kSuccess; } diff --git a/intern/ghost/intern/GHOST_SystemWayland.h b/intern/ghost/intern/GHOST_SystemWayland.h index 10b9ef6bd62..2457bed5def 100644 --- a/intern/ghost/intern/GHOST_SystemWayland.h +++ b/intern/ghost/intern/GHOST_SystemWayland.h @@ -34,6 +34,16 @@ class GHOST_WindowWayland; struct display_t; +struct output_t { + struct wl_output *output; + int32_t width_pxl, height_pxl; // dimensions in pixel + int32_t width_mm, height_mm; // dimensions in millimeter + int transform; + int scale; + std::string make; + std::string model; +}; + class GHOST_SystemWayland : public GHOST_System { public: GHOST_SystemWayland(); @@ -84,6 +94,10 @@ class GHOST_SystemWayland : public GHOST_System { xdg_wm_base *shell(); + const std::vector &outputs() const; + + wl_shm *shm() const; + void setSelection(const std::string &selection); GHOST_TSuccess setCursorShape(GHOST_TStandardCursor shape); diff --git a/intern/ghost/intern/GHOST_WindowWayland.cpp b/intern/ghost/intern/GHOST_WindowWayland.cpp index 1412c496512..70cfe35d628 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.cpp +++ b/intern/ghost/intern/GHOST_WindowWayland.cpp @@ -29,9 +29,15 @@ #include +static constexpr size_t base_dpi = 96; + struct window_t { GHOST_WindowWayland *w; wl_surface *surface; + // outputs on which the window is currently shown on + std::unordered_set outputs; + GHOST_TUns16 dpi = 0; + int scale = 1; struct xdg_surface *xdg_surface; struct xdg_toplevel *xdg_toplevel; wl_egl_window *egl_window; @@ -97,13 +103,14 @@ static void surface_configure(void *data, xdg_surface *xdg_surface, uint32_t ser { window_t *win = static_cast(data); - int w, h; - wl_egl_window_get_attached_size(win->egl_window, &w, &h); - if (win->pending_width != 0 && win->pending_height != 0 && win->pending_width != w && - win->pending_height != h) { - win->width = win->pending_width; - win->height = win->pending_height; - wl_egl_window_resize(win->egl_window, win->pending_width, win->pending_height, 0, 0); + if (win->xdg_surface != xdg_surface) { + return; + } + + if (win->pending_width != 0 && win->pending_height != 0) { + win->width = win->scale * win->pending_width; + win->height = win->scale * win->pending_height; + wl_egl_window_resize(win->egl_window, win->width, win->height, 0, 0); win->pending_width = 0; win->pending_height = 0; win->w->notify_size(); @@ -123,6 +130,52 @@ static const xdg_surface_listener surface_listener = { surface_configure, }; +static bool update_scale(GHOST_WindowWayland *window) +{ + int scale = 0; + for (const output_t *output : window->outputs_active()) { + if (output->scale > scale) + scale = output->scale; + } + + if (scale > 0 && window->scale() != scale) { + window->scale() = scale; + // using the real DPI will cause wrong scaling of the UI + // use a multiplier for the default DPI as workaround + window->dpi() = scale * base_dpi; + wl_surface_set_buffer_scale(window->surface(), scale); + return true; + } + return false; +} + +static void surface_enter(void *data, struct wl_surface * /*wl_surface*/, struct wl_output *output) +{ + GHOST_WindowWayland *w = static_cast(data); + for (const output_t *reg_output : w->outputs()) { + if (reg_output->output == output) { + w->outputs_active().insert(reg_output); + } + } + update_scale(w); +} + +static void surface_leave(void *data, struct wl_surface * /*wl_surface*/, struct wl_output *output) +{ + GHOST_WindowWayland *w = static_cast(data); + for (const output_t *reg_output : w->outputs()) { + if (reg_output->output == output) { + w->outputs_active().erase(reg_output); + } + } + update_scale(w); +} + +struct wl_surface_listener wl_surface_listener = { + surface_enter, + surface_leave, +}; + /** \} */ /* -------------------------------------------------------------------- */ @@ -161,6 +214,8 @@ GHOST_WindowWayland::GHOST_WindowWayland(GHOST_SystemWayland *system, /* Window surfaces. */ w->surface = wl_compositor_create_surface(m_system->compositor()); + wl_surface_add_listener(w->surface, &wl_surface_listener, this); + w->egl_window = wl_egl_window_create(w->surface, int(width), int(height)); w->xdg_surface = xdg_wm_base_get_xdg_surface(m_system->shell(), w->surface); @@ -229,6 +284,31 @@ GHOST_TSuccess GHOST_WindowWayland::notify_size() new GHOST_Event(m_system->getMilliSeconds(), GHOST_kEventWindowSize, this)); } +wl_surface *GHOST_WindowWayland::surface() const +{ + return w->surface; +} + +const std::vector &GHOST_WindowWayland::outputs() const +{ + return m_system->outputs(); +} + +std::unordered_set &GHOST_WindowWayland::outputs_active() +{ + return w->outputs; +} + +uint16_t &GHOST_WindowWayland::dpi() +{ + return w->dpi; +} + +int &GHOST_WindowWayland::scale() +{ + return w->scale; +} + GHOST_TSuccess GHOST_WindowWayland::setWindowCursorGrab(GHOST_TGrabCursorMode mode) { return m_system->setCursorGrab(mode, w->surface); @@ -320,6 +400,11 @@ GHOST_WindowWayland::~GHOST_WindowWayland() delete w; } +GHOST_TUns16 GHOST_WindowWayland::getDPIHint() +{ + return w->dpi; +} + GHOST_TSuccess GHOST_WindowWayland::setWindowCursorVisibility(bool visible) { return m_system->setCursorVisibility(visible); diff --git a/intern/ghost/intern/GHOST_WindowWayland.h b/intern/ghost/intern/GHOST_WindowWayland.h index b62b5c24d60..dbddc7c469e 100644 --- a/intern/ghost/intern/GHOST_WindowWayland.h +++ b/intern/ghost/intern/GHOST_WindowWayland.h @@ -24,9 +24,14 @@ #include "GHOST_Window.h" +#include +#include + class GHOST_SystemWayland; struct window_t; +struct wl_surface; +struct output_t; class GHOST_WindowWayland : public GHOST_Window { public: @@ -47,6 +52,8 @@ class GHOST_WindowWayland : public GHOST_Window { ~GHOST_WindowWayland() override; + GHOST_TUns16 getDPIHint() override; + GHOST_TSuccess close(); GHOST_TSuccess activate(); @@ -55,6 +62,16 @@ class GHOST_WindowWayland : public GHOST_Window { GHOST_TSuccess notify_size(); + wl_surface *surface() const; + + const std::vector &outputs() const; + + std::unordered_set &outputs_active(); + + uint16_t &dpi(); + + int &scale(); + protected: GHOST_TSuccess setWindowCursorGrab(GHOST_TGrabCursorMode mode) override; -- cgit v1.2.3