diff options
Diffstat (limited to 'src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx')
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx | 1582 |
1 files changed, 1582 insertions, 0 deletions
diff --git a/src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx b/src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx new file mode 100644 index 000000000..9738ab861 --- /dev/null +++ b/src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx @@ -0,0 +1,1582 @@ +// +// Implementation of the Wayland window driver. +// +// Copyright 1998-2022 by Bill Spitzak and others. +// +// This library is free software. Distribution and use rights are outlined in +// the file "COPYING" which should have been included with this file. If this +// file is missing or damaged, see the license at: +// +// https://www.fltk.org/COPYING.php +// +// Please see the following page on how to report bugs and issues: +// +// https://www.fltk.org/bugs.php +// + +#include <config.h> +#include <FL/platform.H> +#include "Fl_Wayland_Window_Driver.H" +#include "Fl_Wayland_Screen_Driver.H" +#include "Fl_Wayland_Graphics_Driver.H" +#include "Fl_Wayland_System_Driver.H" +#include <wayland-cursor.h> +#include "../../../libdecor/src/libdecor.h" +#include "xdg-shell-client-protocol.h" +#include <pango/pangocairo.h> +#include <FL/Fl_Overlay_Window.H> +#include <FL/Fl_Menu_Window.H> +#include <FL/Fl_Tooltip.H> +#include <FL/fl_draw.H> +#include <FL/fl_ask.H> +#include <FL/Fl.H> +#include <FL/Fl_Image_Surface.H> +#include <string.h> +#include <sys/mman.h> +#include <math.h> // for ceil() +#include <sys/types.h> // for pid_t +#include <unistd.h> // for getpid() + +struct cursor_image { // as in wayland-cursor.c of the Wayland project source code + struct wl_cursor_image image; + struct wl_cursor_theme *theme; + struct wl_buffer *buffer; + int offset; /* data offset of this image in the shm pool */ +}; + +extern "C" { +# include "../../../libdecor/src/libdecor-plugin.h" + uchar *fl_libdecor_titlebar_buffer(struct libdecor_frame *frame, int *w, int *h, int *stride); +} + +#define fl_max(a,b) ((a) > (b) ? (a) : (b)) + +Window fl_window; + + +void Fl_Wayland_Window_Driver::destroy_double_buffer() { + if (pWindow->as_overlay_window()) fl_delete_offscreen(other_xid); + other_xid = 0; +} + + +Fl_Window_Driver *Fl_Window_Driver::newWindowDriver(Fl_Window *w) +{ + return new Fl_Wayland_Window_Driver(w); +} + + +Fl_Wayland_Window_Driver::Fl_Wayland_Window_Driver(Fl_Window *win) : Fl_Window_Driver(win) +{ + icon_ = new icon_data; + memset(icon_, 0, sizeof(icon_data)); + cursor_ = NULL; + in_handle_configure = false; + screen_num_ = -1; +} + +void Fl_Wayland_Window_Driver::delete_cursor_() { + if (cursor_) { + struct cursor_image *new_image = (struct cursor_image*)cursor_->images[0]; + struct fl_wld_buffer *offscreen = (struct fl_wld_buffer *)wl_buffer_get_user_data(new_image->buffer); + struct wld_window fake_xid; + fake_xid.buffer = offscreen; + Fl_Wayland_Graphics_Driver::buffer_release(&fake_xid); + free(new_image); + free(cursor_->images); + free(cursor_->name); + free(cursor_); + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + if (scr_driver->default_cursor() == cursor_) scr_driver->default_cursor(scr_driver->xc_arrow); + cursor_ = NULL; + } +} + + +Fl_Wayland_Window_Driver::~Fl_Wayland_Window_Driver() +{ + if (shape_data_) { + cairo_surface_t *surface; + cairo_pattern_get_surface(shape_data_->mask_pattern_, &surface); + cairo_pattern_destroy(shape_data_->mask_pattern_); + uchar *data = cairo_image_surface_get_data(surface); + cairo_surface_destroy(surface); + delete[] data; + delete shape_data_; + } + delete icon_; + delete_cursor_(); +} + + +// --- private + +void Fl_Wayland_Window_Driver::decorated_win_size(int &w, int &h) +{ + Fl_Window *win = pWindow; + w = win->w(); + h = win->h(); + if (!win->shown() || win->parent() || !win->border() || !win->visible()) return; + int X, titlebar_height; + libdecor_frame_translate_coordinate(fl_xid(win)->frame, 0, 0, &X, &titlebar_height); +//printf("titlebar_height=%d\n",titlebar_height); + h = win->h() + ceil(titlebar_height / Fl::screen_scale(win->screen_num())); +} + + +// --- window data + +int Fl_Wayland_Window_Driver::decorated_h() +{ + int w, h; + decorated_win_size(w, h); + return h; +} + +int Fl_Wayland_Window_Driver::decorated_w() +{ + int w, h; + decorated_win_size(w, h); + return w; +} + +struct xdg_toplevel *Fl_Wayland_Window_Driver::xdg_toplevel() { + Window w = fl_xid(pWindow); + struct xdg_toplevel *top = NULL; + if (w->kind == DECORATED) top = libdecor_frame_get_xdg_toplevel(w->frame); + else if (w->kind == UNFRAMED) top = w->xdg_toplevel; + return top; +} + +void Fl_Wayland_Window_Driver::take_focus() +{ + Window w = fl_xid(pWindow); + if (w) { + Fl_Window *old_first = Fl::first_window(); + Window first_xid = (old_first ? fl_xid(old_first->top_window()) : NULL); + if (first_xid && first_xid != w && xdg_toplevel()) { + // this will move the target window to the front + Fl_Wayland_Window_Driver *top_dr = Fl_Wayland_Window_Driver::driver(old_first->top_window()); + xdg_toplevel_set_parent(xdg_toplevel(), top_dr->xdg_toplevel()); + // this will remove the parent-child relationship + old_first->wait_for_expose(); + xdg_toplevel_set_parent(xdg_toplevel(), NULL); + } + // this sets the first window + fl_find(w); + } +} + + +void Fl_Wayland_Window_Driver::flush_overlay() +{ + if (!shown()) return; + Fl_Overlay_Window *oWindow = pWindow->as_overlay_window(); + int erase_overlay = (pWindow->damage()&FL_DAMAGE_OVERLAY) | (overlay() == oWindow); + pWindow->clear_damage((uchar)(pWindow->damage()&~FL_DAMAGE_OVERLAY)); + pWindow->make_current(); + if (!other_xid) { + other_xid = fl_create_offscreen(oWindow->w(), oWindow->h()); + oWindow->clear_damage(FL_DAMAGE_ALL); + } + if (oWindow->damage() & ~FL_DAMAGE_EXPOSE) { + Fl_X *myi = Fl_X::i(pWindow); + fl_clip_region(myi->region); myi->region = 0; + fl_begin_offscreen(other_xid); + draw(); + fl_end_offscreen(); + } + if (erase_overlay) fl_clip_region(0); + if (other_xid) { + fl_copy_offscreen(0, 0, oWindow->w(), oWindow->h(), other_xid, 0, 0); + } + if (overlay() == oWindow) oWindow->draw_overlay(); + Window xid = fl_xid(pWindow); + wl_surface_damage_buffer(xid->wl_surface, 0, 0, pWindow->w() * xid->scale, pWindow->h() * xid->scale); +} + + +const Fl_Image* Fl_Wayland_Window_Driver::shape() { + return shape_data_ ? shape_data_->shape_ : NULL; +} + +void Fl_Wayland_Window_Driver::shape_bitmap_(Fl_Image* b) { // needs testing + // complement the bits of the Fl_Bitmap and control its stride too + int i, j, w = b->w(), h = b->h(); + int bytesperrow = cairo_format_stride_for_width(CAIRO_FORMAT_A1, w); + uchar* bits = new uchar[h * bytesperrow]; + const uchar *q = ((Fl_Bitmap*)b)->array; + for (i = 0; i < h; i++) { + uchar *p = bits + i * bytesperrow; + for (j = 0; j < w; j++) { + *p++ = ~*q++; + } + } + cairo_surface_t *mask_surf = cairo_image_surface_create_for_data(bits, CAIRO_FORMAT_A1, w, h, bytesperrow); + shape_data_->mask_pattern_ = cairo_pattern_create_for_surface(mask_surf); + shape_data_->shape_ = b; + shape_data_->lw_ = w; + shape_data_->lh_ = h; +} + +void Fl_Wayland_Window_Driver::shape_alpha_(Fl_Image* img, int offset) { + int i, j, d = img->d(), w = img->w(), h = img->h(); + int bytesperrow = cairo_format_stride_for_width(CAIRO_FORMAT_A1, w); + unsigned u; + uchar byte, onebit; + // build a CAIRO_FORMAT_A1 surface covering the non-fully transparent/black part of the image + uchar* bits = new uchar[h*bytesperrow]; // to store the surface data + const uchar* alpha = (const uchar*)*img->data() + offset; // points to alpha value of rgba pixels + for (i = 0; i < h; i++) { + uchar *p = (uchar*)bits + i * bytesperrow; + byte = 0; + onebit = 1; + for (j = 0; j < w; j++) { + if (d == 3) { + u = *alpha; + u += *(alpha+1); + u += *(alpha+2); + } + else u = *alpha; + if (u > 0) { // if the pixel is not fully transparent/black + byte |= onebit; // turn on the corresponding bit of the bitmap + } + onebit = onebit << 1; // move the single set bit one position to the left + if (onebit == 0 || j == w-1) { + onebit = 1; + *p++ = ~byte; // store in bitmap one pack of bits, complemented + byte = 0; + } + alpha += d; // point to alpha value of next img pixel + } + } + cairo_surface_t *mask_surf = cairo_image_surface_create_for_data(bits, CAIRO_FORMAT_A1, w, h, bytesperrow); + shape_data_->mask_pattern_ = cairo_pattern_create_for_surface(mask_surf); + shape_data_->shape_ = img; + shape_data_->lw_ = w; + shape_data_->lh_ = h; +} + +void Fl_Wayland_Window_Driver::shape(const Fl_Image* img) { + if (shape_data_) { + if (shape_data_->mask_pattern_) { + cairo_surface_t *surface; + cairo_pattern_get_surface(shape_data_->mask_pattern_, &surface); + cairo_pattern_destroy(shape_data_->mask_pattern_); + uchar *data = cairo_image_surface_get_data(surface); + cairo_surface_destroy(surface); + delete[] data; + } + } + else { + shape_data_ = new shape_data_type; + } + memset(shape_data_, 0, sizeof(shape_data_type)); + pWindow->border(false); + int d = img->d(); + if (d && img->count() >= 2) { + shape_pixmap_((Fl_Image*)img); + shape_data_->shape_ = (Fl_Image*)img; + } + else if (d == 0) shape_bitmap_((Fl_Image*)img); + else if (d == 2 || d == 4) shape_alpha_((Fl_Image*)img, d - 1); + else if ((d == 1 || d == 3) && img->count() == 1) shape_alpha_((Fl_Image*)img, 0); +} + +void Fl_Wayland_Window_Driver::draw_end() +{ + if (shape_data_ && shape_data_->mask_pattern_) { + Fl_Wayland_Graphics_Driver *gr_dr = (Fl_Wayland_Graphics_Driver*)fl_graphics_driver; + cairo_t *cr = gr_dr->cr(); + cairo_matrix_t matrix; + cairo_matrix_init_scale(&matrix, double(shape_data_->lw_)/pWindow->w() , double(shape_data_->lh_)/pWindow->h()); + cairo_pattern_set_matrix(shape_data_->mask_pattern_, &matrix); + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + cairo_mask(cr, shape_data_->mask_pattern_); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + } +} + + +void Fl_Wayland_Window_Driver::icons(const Fl_RGB_Image *icons[], int count) { + free_icons(); + + if (count > 0) { + icon_->icons = new Fl_RGB_Image*[count]; + icon_->count = count; + // FIXME: Fl_RGB_Image lacks const modifiers on methods + for (int i = 0;i < count;i++) { + icon_->icons[i] = (Fl_RGB_Image*)((Fl_RGB_Image*)icons[i])->copy(); + icon_->icons[i]->normalize(); + } + } +} + +const void *Fl_Wayland_Window_Driver::icon() const { + return icon_->legacy_icon; +} + +void Fl_Wayland_Window_Driver::icon(const void * ic) { + free_icons(); + icon_->legacy_icon = ic; +} + +void Fl_Wayland_Window_Driver::free_icons() { + int i; + icon_->legacy_icon = 0L; + if (icon_->icons) { + for (i = 0;i < icon_->count;i++) + delete icon_->icons[i]; + delete [] icon_->icons; + icon_->icons = 0L; + } + icon_->count = 0; +} + + +/* Returns images of the captures of the window title-bar, and the left, bottom and right window borders + (or NULL if a particular border is absent). + Returned images can be deleted after use. Their depth and size may be platform-dependent. + The top and bottom images extend from left of the left border to right of the right border. + */ +void Fl_Wayland_Window_Driver::capture_titlebar_and_borders(Fl_RGB_Image*& top, Fl_RGB_Image*& left, Fl_RGB_Image*& bottom, Fl_RGB_Image*& right) +{ + top = left = bottom = right = NULL; + if (pWindow->decorated_h() == h()) return; + int htop = pWindow->decorated_h() - pWindow->h(); + struct wld_window *wwin = fl_xid(pWindow); + int width, height, stride; + uchar *cairo_data = fl_libdecor_titlebar_buffer(wwin->frame, &width, &height, &stride); + if (!cairo_data) return; + uchar *data = new uchar[width * height * 3]; + uchar *p = data; + for (int j = 0; j < height; j++) { + uchar *q = cairo_data + j * stride; + for (int i = 0; i < width; i++) { + *p++ = *(q+2); // R + *p++ = *(q+1); // G + *p++ = *q; // B + q += 4; + } + } + top = new Fl_RGB_Image(data, width, height, 3); + top->alloc_array = 1; + top->scale(pWindow->w(), htop); +} + +// used only to support progressive drawing +static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time); + +static const struct wl_callback_listener surface_frame_listener = { + .done = surface_frame_done, +}; + +static void surface_frame_done(void *data, struct wl_callback *cb, uint32_t time) { + Window window = (Window)data; +//fprintf(stderr,"surface_frame_done: destroy cb=%p draw_buffer_needs_commit=%d\n", cb, window->buffer->draw_buffer_needs_commit); + wl_callback_destroy(cb); + window->buffer->cb = NULL; + if (window->buffer->draw_buffer_needs_commit) { + wl_surface_damage_buffer(window->wl_surface, 0, 0, 1000000, 1000000); + window->buffer->cb = wl_surface_frame(window->wl_surface); +//fprintf(stderr,"surface_frame_done: new cb=%p \n", window->buffer->cb); + wl_callback_add_listener(window->buffer->cb, &surface_frame_listener, window); + Fl_Wayland_Graphics_Driver::buffer_commit(window); + } +} + + +// make drawing go into this window (called by subclass flush() impl.) +void Fl_Wayland_Window_Driver::make_current() { + if (!shown()) { + static const char err_message[] = "Fl_Window::make_current(), but window is not shown()."; + fl_alert(err_message); + Fl::fatal(err_message); + } + + struct wld_window *window = fl_xid(pWindow); + float scale = Fl::screen_scale(pWindow->screen_num()) * window->scale; + + // to support progressive drawing + if ( (!Fl_Wayland_Window_Driver::in_flush) && window && window->buffer && (!window->buffer->cb)) { + wl_surface_damage_buffer(window->wl_surface, 0, 0, pWindow->w() * scale, pWindow->h() * scale); + window->buffer->cb = wl_surface_frame(window->wl_surface); + //fprintf(stderr, "direct make_current: new cb=%p\n", window->buffer->cb); + wl_callback_add_listener(window->buffer->cb, &surface_frame_listener, window); + Fl_Wayland_Graphics_Driver::buffer_commit(window); + } + + fl_graphics_driver->clip_region(0); + fl_window = window; + if (!window->buffer) window->buffer = Fl_Wayland_Graphics_Driver::create_shm_buffer( + pWindow->w() * scale, pWindow->h() * scale); + ((Fl_Wayland_Graphics_Driver*)fl_graphics_driver)->activate(window->buffer, scale); + +#ifdef FLTK_USE_CAIRO + // update the cairo_t context + if (Fl::cairo_autolink_context()) Fl::cairo_make_current(pWindow); +#endif +} + + +void Fl_Wayland_Window_Driver::flush() { + if (!pWindow->damage()) return; + if (pWindow->as_gl_window()) { + int W = pWindow->w(); + int H = pWindow->h(); + float scale = fl_graphics_driver->scale(); + Fl_Wayland_Window_Driver::in_flush = true; + Fl_Window_Driver::flush(); + Fl_Wayland_Window_Driver::in_flush = false; + static Fl_Wayland_Plugin *plugin = NULL; + if (!plugin) { + Fl_Plugin_Manager pm("wayland.fltk.org"); + plugin = (Fl_Wayland_Plugin*)pm.plugin("gl.wayland.fltk.org"); + } + if (plugin) { + plugin->do_swap(pWindow); // useful only for GL win with overlay + if (scale != fl_graphics_driver->scale() || W != pWindow->w() || H != pWindow->h()) plugin->invalidate(pWindow); + } + return; + } + struct wld_window *window = fl_xid(pWindow); + if (!window || !window->configured_width) return; + + Fl_X *i = Fl_X::i(pWindow); + Fl_Region r = i->region; + float f = Fl::screen_scale(pWindow->screen_num()); + if (r && window->buffer) { + for (int i = 0; i < r->count; i++) { + int left = r->rects[i].x * window->scale * f; + int top = r->rects[i].y * window->scale * f; + int width = r->rects[i].width * window->scale * f; + int height = r->rects[i].height * window->scale * f; + wl_surface_damage_buffer(window->wl_surface, left, top, width, height); +//fprintf(stderr, "damage %dx%d %dx%d\n", left, top, width, height); + } + } else { + wl_surface_damage_buffer(window->wl_surface, 0, 0, + pWindow->w() * window->scale * f, pWindow->h() * window->scale * f); +//fprintf(stderr, "damage 0x0 %dx%d\n", pWindow->w() * window->scale, pWindow->h() * window->scale); + } + + Fl_Wayland_Window_Driver::in_flush = true; + Fl_Window_Driver::flush(); + Fl_Wayland_Window_Driver::in_flush = false; + + wl_surface_frame(window->wl_surface); + Fl_Wayland_Graphics_Driver::buffer_commit(window); +} + + +void Fl_Wayland_Window_Driver::show() { + if (!shown()) { + fl_open_display(); + makeWindow(); + } else { + // Wayland itself gives no way to programmatically unminimize a minimized window + Fl::handle(FL_SHOW, pWindow); + } +} + + +static void popup_done(void *data, struct xdg_popup *xdg_popup); + +static void delayed_delete_Fl_X(Fl_X *i) { + delete i; +} + + +void Fl_Wayland_Window_Driver::hide() { + Fl_X* ip = Fl_X::i(pWindow); + if (hide_common()) return; + if (ip->region) { + Fl_Graphics_Driver::default_driver().XDestroyRegion(ip->region); + ip->region = 0; + } + screen_num_ = -1; + struct wld_window *wld_win = ip->xid; + if (wld_win) { // this test makes sure ip->xid has not been destroyed already + Fl_Wayland_Graphics_Driver::buffer_release(wld_win); +//fprintf(stderr, "Before hide: sub=%p frame=%p xdg=%p top=%p pop=%p surf=%p\n", wld_win->subsurface, wld_win->frame, wld_win->xdg_surface, wld_win->xdg_toplevel, wld_win->xdg_popup, wld_win->wl_surface); + if (wld_win->kind == SUBWINDOW && wld_win->subsurface) { + wl_subsurface_destroy(wld_win->subsurface); + wld_win->subsurface = NULL; + } + if (wld_win->kind == DECORATED) { + libdecor_frame_unref(wld_win->frame); + wld_win->frame = NULL; + wld_win->xdg_surface = NULL; + } else { + if (wld_win->kind == POPUP) { + popup_done(wld_win, wld_win->xdg_popup); + wld_win->xdg_popup = NULL; + } + if (wld_win->kind == UNFRAMED && wld_win->xdg_toplevel) { + xdg_toplevel_destroy(wld_win->xdg_toplevel); + wld_win->xdg_toplevel = NULL; + } + if (wld_win->xdg_surface) { + xdg_surface_destroy(wld_win->xdg_surface); + wld_win->xdg_surface = NULL; + } + } + if (wld_win->wl_surface) { + wl_surface_destroy(wld_win->wl_surface); + wld_win->wl_surface = NULL; + } + Fl_Wayland_Window_Driver::window_output *window_output, *tmp; + wl_list_for_each_safe(window_output, tmp, &wld_win->outputs, link) { + wl_list_remove(&window_output->link); + free(window_output); + } +//fprintf(stderr, "After hide: sub=%p frame=%p xdg=%p top=%p pop=%p surf=%p\n", wld_win->subsurface, wld_win->frame, wld_win->xdg_surface, wld_win->xdg_toplevel, wld_win->xdg_popup, wld_win->wl_surface); + } + free(wld_win); + if (pWindow->as_gl_window() && in_flush) { + ip->xid = NULL; + ip->next = NULL; // to end the loop in calling Fl::flush() + Fl::add_timeout(.01, (Fl_Timeout_Handler)delayed_delete_Fl_X, ip); + } else { + delete ip; + } +} + + +void Fl_Wayland_Window_Driver::map() { + Fl_X* ip = Fl_X::i(pWindow); + struct wld_window *wl_win = ip->xid; + if (wl_win->kind == DECORATED) libdecor_frame_map(wl_win->frame);//needs checking + else if (pWindow->parent() && !wl_win->subsurface) { + struct wld_window *parent = fl_xid(pWindow->window()); + if (parent) { + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + wl_win->subsurface = wl_subcompositor_get_subsurface(scr_driver->wl_subcompositor, wl_win->wl_surface, parent->wl_surface); + float f = Fl::screen_scale(pWindow->top_window()->screen_num()); + wl_subsurface_set_position(wl_win->subsurface, pWindow->x() * f, pWindow->y() * f); + wl_subsurface_set_desync(wl_win->subsurface); // important + wl_subsurface_place_above(wl_win->subsurface, parent->wl_surface); + wl_win->configured_width = pWindow->w(); + wl_win->configured_height = pWindow->h(); + wl_win->scale = parent->scale; + wait_for_expose_value = 0; + pWindow->redraw(); + } + } +} + + +void Fl_Wayland_Window_Driver::unmap() { + Fl_X* ip = Fl_X::i(pWindow); + struct wld_window *wl_win = ip->xid; + if (wl_win->kind == DECORATED && wl_win->frame) { libdecor_frame_close(wl_win->frame);//needs checking + } else if (wl_win->kind == SUBWINDOW && wl_win->wl_surface) { + wl_surface_attach(wl_win->wl_surface, NULL, 0, 0); + Fl_Wayland_Graphics_Driver::buffer_release(wl_win); + wl_subsurface_destroy(wl_win->subsurface); + wl_win->subsurface = NULL; + } +} + + +void Fl_Wayland_Window_Driver::size_range() { + if (shown()) { + Fl_X* ip = Fl_X::i(pWindow); + struct wld_window *wl_win = ip->xid; + float f = Fl::screen_scale(pWindow->screen_num()); + if (wl_win->kind == DECORATED && wl_win->frame) { + libdecor_frame_set_min_content_size(wl_win->frame, minw()*f, minh()*f); + libdecor_frame_set_max_content_size(wl_win->frame, maxw()*f, maxh()*f); + int X,Y,W,H; + Fl::screen_work_area(X,Y,W,H, Fl::screen_num(x(),y(),w(),h())); + if (maxw() && maxw() < W && maxh() && maxh() < H) { + libdecor_frame_unset_capabilities(wl_win->frame, LIBDECOR_ACTION_FULLSCREEN); + } else { + libdecor_frame_set_capabilities(wl_win->frame, LIBDECOR_ACTION_FULLSCREEN); + } + if (maxw() && maxh() && (minw() >= maxw() || minh() >= maxh())) { + libdecor_frame_unset_capabilities(wl_win->frame, LIBDECOR_ACTION_RESIZE); + } else { + libdecor_frame_set_capabilities(wl_win->frame, LIBDECOR_ACTION_RESIZE); + } + } else if (wl_win->kind == UNFRAMED && wl_win->xdg_toplevel) { + xdg_toplevel_set_min_size(wl_win->xdg_toplevel, minw()*f, minh()*f); + if (maxw() && maxh()) + xdg_toplevel_set_max_size(wl_win->xdg_toplevel, maxw()*f, maxh()*f); + } + } +} + + +void Fl_Wayland_Window_Driver::iconize() { + Fl_X* ip = Fl_X::i(pWindow); + struct wld_window *wl_win = ip->xid; + if (wl_win->kind == DECORATED) { + libdecor_frame_set_minimized(wl_win->frame); + Fl::handle(FL_HIDE, pWindow); + } + else if (wl_win->kind == UNFRAMED && wl_win->xdg_toplevel) xdg_toplevel_set_minimized(wl_win->xdg_toplevel); +} + + +void Fl_Wayland_Window_Driver::decoration_sizes(int *top, int *left, int *right, int *bottom) { + // Ensure border is on screen; these values are generic enough + // to work with many window managers, and are based on KDE defaults. + *top = 20; + *left = 4; + *right = 4; + *bottom = 8; +} + +void Fl_Wayland_Window_Driver::show_with_args_begin() { + // Get defaults for drag-n-drop and focus... + const char *key = 0; + + if (Fl::first_window()) key = Fl::first_window()->xclass(); + if (!key) key = "fltk"; + + /*const char *val = XGetDefault(fl_display, key, "dndTextOps"); + if (val) Fl::dnd_text_ops(strcasecmp(val, "true") == 0 || + strcasecmp(val, "on") == 0 || + strcasecmp(val, "yes") == 0); + + val = XGetDefault(fl_display, key, "tooltips"); + if (val) Fl_Tooltip::enable(strcasecmp(val, "true") == 0 || + strcasecmp(val, "on") == 0 || + strcasecmp(val, "yes") == 0); + + val = XGetDefault(fl_display, key, "visibleFocus"); + if (val) Fl::visible_focus(strcasecmp(val, "true") == 0 || + strcasecmp(val, "on") == 0 || + strcasecmp(val, "yes") == 0);*/ +} + + +void Fl_Wayland_Window_Driver::show_with_args_end(int argc, char **argv) { + if (argc) { + // set the command string, used by state-saving window managers: + int j; + int n=0; for (j=0; j<argc; j++) n += strlen(argv[j])+1; + char *buffer = new char[n]; + char *p = buffer; + for (j=0; j<argc; j++) for (const char *q = argv[j]; (*p++ = *q++);); + //XChangeProperty(fl_display, fl_xid(pWindow), XA_WM_COMMAND, XA_STRING, 8, 0, + // (unsigned char *)buffer, p-buffer-1); + delete[] buffer; + } +} + + +int Fl_Wayland_Window_Driver::scroll(int src_x, int src_y, int src_w, int src_h, int dest_x, int dest_y, + void (*draw_area)(void*, int,int,int,int), void* data) +{ + Window xid = fl_xid(pWindow); + struct fl_wld_buffer *buffer = xid->buffer; + int s = xid->scale; + if (s != 1) { + src_x *= s; src_y *= s; src_w *= s; src_h *= s; dest_x *= s; dest_y *= s; + } + if (src_x == dest_x) { // vertical scroll + int i, to, step; + if (src_y > dest_y) { + i = 0; to = src_h; step = 1; + } else { + i = src_h - 1; to = -1; step = -1; + } + while (i != to) { + memcpy(buffer->draw_buffer + (dest_y + i) * buffer->stride + 4 * dest_x, + buffer->draw_buffer + (src_y + i) * buffer->stride + 4 * src_x, 4 * src_w); + i += step; + } + } else { // horizontal scroll + int i, to, step; + if (src_x > dest_x) { + i = 0; to = src_h; step = 1; + } else { + i = src_h - 1; to = -1; step = -1; + } + while (i != to) { + memmove(buffer->draw_buffer + (src_y + i) * buffer->stride + 4 * dest_x, + buffer->draw_buffer + (src_y + i) * buffer->stride + 4 * src_x, 4 * src_w); + i += step; + } + } + return 0; +} + + +static void handle_error(struct libdecor *libdecor_context, enum libdecor_error error, const char *message) +{ + Fl::fatal("Caught error (%d): %s\n", error, message); +} + +static struct libdecor_interface libdecor_iface = { + .error = handle_error, +}; + +static void surface_enter(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) +{ + struct wld_window *window = (struct wld_window*)data; + Fl_Wayland_Window_Driver::window_output *window_output; + + if (!Fl_Wayland_Screen_Driver::own_output(wl_output)) + return; + + Fl_Wayland_Screen_Driver::output *output = (Fl_Wayland_Screen_Driver::output*)wl_output_get_user_data(wl_output); + if (output == NULL) + return; + + window_output = (Fl_Wayland_Window_Driver::window_output*)calloc(1, sizeof *window_output); + window_output->output = output; + wl_list_insert(&window->outputs, &window_output->link); + Fl_Wayland_Window_Driver *win_driver = Fl_Wayland_Window_Driver::driver(window->fl_win); + win_driver->update_scale(); + if (!window->fl_win->parent()) { // for top-level, set its screen number + Fl_Wayland_Screen_Driver::output *running_output; + Fl_Wayland_Screen_Driver *scr_dr = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + int i = 0; + wl_list_for_each(running_output, &scr_dr->outputs, link) { // each screen of the system + if (running_output == output) { // we've found our screen of the system + win_driver->screen_num(i); +//fprintf(stderr,"window %p is on screen #%d\n", window->fl_win, i); + break; + } + i++; + } + } +} + +static void surface_leave(void *data, struct wl_surface *wl_surface, struct wl_output *wl_output) +{ + struct wld_window *window = (struct wld_window*)data; + if (! window->wl_surface) return; + Fl_Wayland_Window_Driver::window_output *window_output; + + wl_list_for_each(window_output, &window->outputs, link) { + if (window_output->output->wl_output == wl_output) { + wl_list_remove(&window_output->link); + free(window_output); + Fl_Wayland_Window_Driver *win_driver = Fl_Wayland_Window_Driver::driver(window->fl_win); + win_driver->update_scale(); + break; + } + } +} + +static struct wl_surface_listener surface_listener = { + surface_enter, + surface_leave, +}; + + +static void handle_configure(struct libdecor_frame *frame, + struct libdecor_configuration *configuration, void *user_data) +{ + struct wld_window *window = (struct wld_window*)user_data; + if (!window->wl_surface) return; + int width, height; + enum libdecor_window_state window_state; + struct libdecor_state *state; + Fl_Wayland_Window_Driver *driver = Fl_Wayland_Window_Driver::driver(window->fl_win); + // true exactly for the 2nd run of handle_configure() for this window + bool is_2nd_run = (window->xdg_surface != 0 && driver->wait_for_expose_value); + float f = Fl::screen_scale(window->fl_win->screen_num()); + + if (!window->xdg_surface) window->xdg_surface = libdecor_frame_get_xdg_surface(frame); + + if (window->fl_win->fullscreen_active()) { + libdecor_frame_set_fullscreen(window->frame, NULL); + } else if (driver->show_iconic()) { + libdecor_frame_set_minimized(window->frame); + driver->show_iconic(0); + } + if (!libdecor_configuration_get_window_state(configuration, &window_state)) + window_state = LIBDECOR_WINDOW_STATE_NONE; + window->state = window_state; + + // Weston, KDE and recent versions of Mutter, on purpose, don't set the + // window width x height when xdg_toplevel_configure runs twice + // during resizable window creation (see https://gitlab.freedesktop.org/wayland/wayland-protocols/-/issues/6). + // Consequently, libdecor_configuration_get_content_size() may return false twice. + // In that case libdecor_frame_get_content_{width,height}() give the desired window size + if (!libdecor_configuration_get_content_size(configuration, frame, &width, &height)) { + if (is_2nd_run) { + width = libdecor_frame_get_content_width(frame); + height = libdecor_frame_get_content_height(frame); + if (!window->fl_win->resizable()) { + libdecor_frame_set_min_content_size(frame, width, height); + libdecor_frame_set_max_content_size(frame, width, height); + } + } else { width = height = 0; } + } + + if (width == 0) { + width = window->floating_width; + height = window->floating_height; + //fprintf(stderr,"handle_configure: using floating %dx%d\n",width,height); + } + + driver->in_handle_configure = true; + window->fl_win->resize(0, 0, ceil(width / f), ceil(height / f)); + driver->in_handle_configure = false; + + if (ceil(width / f) != window->configured_width || ceil(height / f) != window->configured_height) { + if (window->buffer) { + Fl_Wayland_Graphics_Driver::buffer_release(window); + } + } + window->configured_width = ceil(width / f); + window->configured_height = ceil(height / f); + if (is_2nd_run) driver->wait_for_expose_value = 0; +//fprintf(stderr, "handle_configure fl_win=%p size:%dx%d state=%x wait_for_expose_value=%d is_2nd_run=%d\n", window->fl_win, width,height,window_state,driver->wait_for_expose_value, is_2nd_run); + +/* We would like to do FL_HIDE when window is minimized but : + "There is no way to know if the surface is currently minimized, nor is there any way to + unset minimization on this surface. If you are looking to throttle redrawing when minimized, + please instead use the wl_surface.frame event" */ + if (window_state == LIBDECOR_WINDOW_STATE_NONE) { + Fl::handle(FL_UNFOCUS, window->fl_win); + } + else if (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) { + if (!window->fl_win->border()) libdecor_frame_set_visibility(window->frame, false); + else if (!libdecor_frame_is_visible(window->frame)) libdecor_frame_set_visibility(window->frame, true); + Fl::handle(FL_FOCUS, window->fl_win); + fl_find(window); + } + + if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) state = libdecor_state_new(width, height); + else state = libdecor_state_new(int(ceil(width/f)*f), int(ceil(height/f)*f)); + libdecor_frame_commit(frame, state, configuration); + if (libdecor_frame_is_floating(frame)) { // store floating dimensions + window->floating_width = int(ceil(width/f)*f); + window->floating_height = int(ceil(height/f)*f); + //fprintf(stderr,"set floating_width+height %dx%d\n",width,height); + } + libdecor_state_free(state); + + window->fl_win->redraw(); + // necessary with SSD + driver->in_handle_configure = true; + if (!window->fl_win->as_gl_window()) { + driver->flush(); + } else { + driver->Fl_Window_Driver::flush(); // GL window + } + driver->in_handle_configure = false; +} + + +void Fl_Wayland_Window_Driver::wait_for_expose() +{ + Fl_Window_Driver::wait_for_expose(); + if (pWindow->fullscreen_active()) { + Window xid = fl_xid(pWindow); + if (xid->kind == DECORATED) { + while (!(xid->state & LIBDECOR_WINDOW_STATE_FULLSCREEN) || !(xid->state & LIBDECOR_WINDOW_STATE_ACTIVE)) { + wl_display_dispatch(fl_display); + } + } else if (xid->kind == UNFRAMED) { + wl_display_roundtrip(fl_display); + } + } +} + + +static void handle_close(struct libdecor_frame *frame, void *user_data) +{ + struct wld_window* wl_win = (struct wld_window*)user_data; + Fl::handle(FL_CLOSE, wl_win->fl_win); +} + + +static void handle_commit(struct libdecor_frame *frame, void *user_data) +{ + struct wld_window* wl_win = (struct wld_window*)user_data; + if (wl_win->wl_surface) wl_surface_commit(wl_win->wl_surface); +} + +static void handle_dismiss_popup(struct libdecor_frame *frame, const char *seat_name, void *user_data) +{ +} + +static struct libdecor_frame_interface libdecor_frame_iface = { + handle_configure, + handle_close, + handle_commit, + handle_dismiss_popup, +}; + + +static void xdg_surface_configure(void *data, struct xdg_surface *xdg_surface, uint32_t serial) +{ + // runs for borderless windows and popup (menu,tooltip) windows + struct wld_window *window = (struct wld_window*)data; + xdg_surface_ack_configure(xdg_surface, serial); +//fprintf(stderr, "xdg_surface_configure: surface=%p\n", window->wl_surface); + + if (window->fl_win->w() != window->configured_width || window->fl_win->h() != window->configured_height) { + if (window->buffer) { + Fl_Wayland_Graphics_Driver::buffer_release(window); + } + } + window->configured_width = window->fl_win->w(); + window->configured_height = window->fl_win->h(); + window->fl_win->redraw(); + Fl_Window_Driver::driver(window->fl_win)->flush(); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + .configure = xdg_surface_configure, +}; + + +static void xdg_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel, + int32_t width, int32_t height, struct wl_array *states) +{ + // runs for borderless top-level windows + struct wld_window *window = (struct wld_window*)data; +//fprintf(stderr, "xdg_toplevel_configure: surface=%p size: %dx%d\n", window->wl_surface, width, height); + if (window->fl_win->fullscreen_active()) xdg_toplevel_set_fullscreen(xdg_toplevel, NULL); + if (window->configured_width) Fl_Window_Driver::driver(window->fl_win)->wait_for_expose_value = 0; + float f = Fl::screen_scale(window->fl_win->screen_num()); + if (width == 0 || height == 0) { + width = window->fl_win->w() * f; + height = window->fl_win->h() * f; + } + window->fl_win->size(ceil(width / f), ceil(height / f)); + if (window->buffer && (ceil(width / f) != window->configured_width || ceil(height / f) != window->configured_height)) { + Fl_Wayland_Graphics_Driver::buffer_release(window); + } + window->configured_width = ceil(width / f); + window->configured_height = ceil(height / f); +} + + +static void xdg_toplevel_close(void *data, struct xdg_toplevel *toplevel) +{ +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + .configure = xdg_toplevel_configure, + .close = xdg_toplevel_close, +}; + + +static void popup_configure(void *data, struct xdg_popup *xdg_popup, int32_t x, int32_t y, int32_t width, int32_t height) { + struct wld_window *window = (struct wld_window*)data; + Fl_Window_Driver::driver(window->fl_win)->wait_for_expose_value = 0; +} + +#define USE_GRAB_POPUP 0 +#if USE_GRAB_POPUP // nearly OK except that menutitle window does not always show +static Fl_Window *mem_parent = NULL; +static struct xdg_popup *mem_grabbing_popup = NULL; + +static void nothing_popup(void *, struct xdg_popup *) {} +#endif + +static void popup_done(void *data, struct xdg_popup *xdg_popup) { +//fprintf(stderr, "popup_done: popup=%p \n", xdg_popup); +#if USE_GRAB_POPUP + if (mem_grabbing_popup == xdg_popup) { + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + libdecor_frame_popup_ungrab(fl_xid(mem_parent)->frame, scr_driver->get_seat_name()); + mem_grabbing_popup = NULL; + mem_parent = NULL; + } +#endif + xdg_popup_destroy(xdg_popup); +} + +static const struct xdg_popup_listener popup_listener = { + .configure = popup_configure, +#if USE_GRAB_POPUP + .popup_done = nothing_popup, +#else + .popup_done = popup_done, +#endif +}; + +bool Fl_Wayland_Window_Driver::in_flush = false; + +// Compute the parent window of the transient scale window +static Fl_Window *calc_transient_parent(int ¢er_x, int ¢er_y) { + // Find top, the topmost window, but not a transient window itself + Fl_Window *top = Fl::first_window()->top_window(); + while (top && top->user_data() == &Fl_Screen_Driver::transient_scale_display) + top = Fl::next_window(top); + Fl_Window *target = top; + // search if top's center belongs to one of its subwindows + center_x = top->w()/2; center_y = top->h()/2; + while (target) { + Fl_Window *child = Fl::first_window(); + while (child) { + if (child->window() == target && child->user_data() != &Fl_Screen_Driver::transient_scale_display && + child->x() <= center_x && child->x()+child->w() > center_x && + child->y() <= center_y && child->y()+child->h() > center_y) { + break; // child contains the center of top + } + child = Fl::next_window(child); + } + if (!child) break; // no more subwindow contains the center of top + target = child; + center_x -= child->x(); // express center coordinates relatively to child + center_y -= child->y(); + } + return target; +} + + +static char *get_prog_name() { + pid_t pid = getpid(); + char fname[100]; + sprintf(fname, "/proc/%u/cmdline", pid); + FILE *in = fopen(fname, "r"); + if (in) { + static char line[200]; + char *p = fgets(line, sizeof(line), in); + fclose(in); + p = strrchr(line, '/'); if (!p) p = line; else p++; + return p; + } + return NULL; +} + + +Fl_X *Fl_Wayland_Window_Driver::makeWindow() +{ + struct wld_window *new_window; + Fl_Wayland_Screen_Driver::output *output; + wait_for_expose_value = 1; + + if (pWindow->parent() && !pWindow->window()->shown()) return NULL; + + new_window = (struct wld_window *)calloc(1, sizeof *new_window); + new_window->fl_win = pWindow; + new_window->scale = 1; + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + wl_list_for_each(output, &(scr_driver->outputs), link) { + new_window->scale = MAX(new_window->scale, output->wld_scale); + } + wl_list_init(&new_window->outputs); + + new_window->wl_surface = wl_compositor_create_surface(scr_driver->wl_compositor); + //Fl::warning("makeWindow:%p wayland-scale=%d user-scale=%.2f\n", pWindow, new_window->scale, Fl::screen_scale(0)); + wl_surface_add_listener(new_window->wl_surface, &surface_listener, new_window); + + if (pWindow->user_data() == &Fl_Screen_Driver::transient_scale_display && Fl::first_window()) { + // put transient scale win at center of top window by making it a child of top + int center_x, center_y; + Fl_Window *top = calc_transient_parent(center_x, center_y); + if (top) { + top->add(pWindow); + pWindow->position(center_x - pWindow->w()/2 , center_y - pWindow->h()/2); + } + } + + if (pWindow->menu_window() || pWindow->tooltip_window()) { // a menu window or tooltip + new_window->kind = POPUP; + new_window->xdg_surface = xdg_wm_base_get_xdg_surface(scr_driver->xdg_wm_base, new_window->wl_surface); + xdg_surface_add_listener(new_window->xdg_surface, &xdg_surface_listener, new_window); + struct xdg_positioner *positioner = xdg_wm_base_create_positioner(scr_driver->xdg_wm_base); + //xdg_positioner_get_version(positioner) <== gives 1 under Debian + Fl_Widget *target = (pWindow->tooltip_window() ? + Fl_Tooltip::current() : Fl_Window_Driver::menu_parent() ); + if (!target) target = Fl::belowmouse(); + if (!target) target = Fl::first_window(); + Fl_Window *parent_win = target->top_window(); + while (parent_win && parent_win->menu_window()) parent_win = Fl::next_window(parent_win); + Window parent_xid = fl_xid(parent_win); + struct xdg_surface *parent_xdg = parent_xid->xdg_surface; + float f = Fl::screen_scale(parent_win->screen_num()); +//fprintf(stderr, "menu parent_win=%p pos:%dx%d size:%dx%d\n", parent_win, pWindow->x(), pWindow->y(), pWindow->w(), pWindow->h()); + int popup_x = pWindow->x() * f, popup_y = pWindow->y() * f; + if (parent_xid->kind == DECORATED) + libdecor_frame_translate_coordinate(parent_xid->frame, popup_x, popup_y, &popup_x, &popup_y); + xdg_positioner_set_anchor_rect(positioner, popup_x, popup_y, 1, 1); + xdg_positioner_set_size(positioner, pWindow->w() * f , pWindow->h() * f ); + xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); + xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); + new_window->xdg_popup = xdg_surface_get_popup(new_window->xdg_surface, parent_xdg, positioner); + xdg_positioner_destroy(positioner); + xdg_popup_add_listener(new_window->xdg_popup, &popup_listener, new_window); +#if USE_GRAB_POPUP + if (!mem_grabbing_popup) { + mem_parent = parent_win; + mem_grabbing_popup = new_window->xdg_popup; + xdg_popup_grab(new_window->xdg_popup, scr_driver->get_wl_seat(), scr_driver->get_serial()); + libdecor_frame_popup_grab(parent_xid->frame, scr_driver->get_seat_name()); + } +#endif + wl_surface_commit(new_window->wl_surface); + + } else if ( pWindow->border() && !pWindow->parent() ) { // a decorated window + new_window->kind = DECORATED; + if (!scr_driver->libdecor_context) scr_driver->libdecor_context = libdecor_new(fl_display, &libdecor_iface); + new_window->frame = libdecor_decorate(scr_driver->libdecor_context, new_window->wl_surface, + &libdecor_frame_iface, new_window); +//fprintf(stderr, "makeWindow: libdecor_decorate=%p pos:%dx%d\n", new_window->frame, pWindow->x(), pWindow->y()); + libdecor_frame_set_app_id(new_window->frame, get_prog_name()); // appears in the Gnome desktop menu bar + libdecor_frame_set_title(new_window->frame, pWindow->label()?pWindow->label():""); + if (!pWindow->resizable()) { + libdecor_frame_unset_capabilities(new_window->frame, LIBDECOR_ACTION_RESIZE); + libdecor_frame_unset_capabilities(new_window->frame, LIBDECOR_ACTION_FULLSCREEN); + } + libdecor_frame_map(new_window->frame); + float f = Fl::screen_scale(pWindow->screen_num()); + new_window->floating_width = pWindow->w() * f; + new_window->floating_height = pWindow->h() * f; + + } else if (pWindow->parent()) { // for subwindows (GL or non-GL) + new_window->kind = SUBWINDOW; + struct wld_window *parent = fl_xid(pWindow->window()); + new_window->subsurface = wl_subcompositor_get_subsurface(scr_driver->wl_subcompositor, new_window->wl_surface, parent->wl_surface); +//fprintf(stderr, "makeWindow: subsurface=%p\n", new_window->subsurface); + float f = Fl::screen_scale(pWindow->top_window()->screen_num()); + wl_subsurface_set_position(new_window->subsurface, pWindow->x() * f, pWindow->y() * f); + wl_subsurface_set_desync(new_window->subsurface); // important + wl_subsurface_place_above(new_window->subsurface, parent->wl_surface); + // next 3 statements ensure the subsurface will be mapped because: + // "A sub-surface becomes mapped, when a non-NULL wl_buffer is applied and the parent surface is mapped." + new_window->configured_width = pWindow->w(); + new_window->configured_height = pWindow->h(); + wait_for_expose_value = 0; + pWindow->border(0); + + } else { // a window without decoration + new_window->kind = UNFRAMED; + new_window->xdg_surface = xdg_wm_base_get_xdg_surface(scr_driver->xdg_wm_base, new_window->wl_surface); +//fprintf(stderr, "makeWindow: xdg_wm_base_get_xdg_surface=%p\n", new_window->xdg_surface); + xdg_surface_add_listener(new_window->xdg_surface, &xdg_surface_listener, new_window); + new_window->xdg_toplevel = xdg_surface_get_toplevel(new_window->xdg_surface); + xdg_toplevel_add_listener(new_window->xdg_toplevel, &xdg_toplevel_listener, new_window); + if (pWindow->label()) xdg_toplevel_set_title(new_window->xdg_toplevel, pWindow->label()); + wl_surface_commit(new_window->wl_surface); + pWindow->border(0); + } + + Fl_Window *old_first = Fl::first_window(); + Window first_xid = (old_first ? fl_xid(old_first) : NULL); + Fl_X *xp = new Fl_X; + xp->xid = new_window; + other_xid = 0; + xp->w = pWindow; + i(xp); + xp->region = 0; + if (!pWindow->parent()) { + xp->next = Fl_X::first; + Fl_X::first = xp; + } else if (Fl_X::first) { + xp->next = Fl_X::first->next; + Fl_X::first->next = xp; + } else { + xp->next = NULL; + Fl_X::first = xp; + } + + if (pWindow->modal() || pWindow->non_modal()) { + if (pWindow->modal()) Fl::modal_ = pWindow; + if (new_window->kind == DECORATED && first_xid && first_xid->kind == DECORATED) { + libdecor_frame_set_parent(new_window->frame, first_xid->frame); + } else if (new_window->kind == UNFRAMED && new_window->xdg_toplevel && first_xid) { + Fl_Wayland_Window_Driver *top_dr = Fl_Wayland_Window_Driver::driver(first_xid->fl_win); + if (top_dr->xdg_toplevel()) xdg_toplevel_set_parent(new_window->xdg_toplevel, + top_dr->xdg_toplevel()); + } + } + + size_range(); + pWindow->set_visible(); + int old_event = Fl::e_number; + pWindow->handle(Fl::e_number = FL_SHOW); // get child windows to appear + Fl::e_number = old_event; + pWindow->redraw(); + + return xp; +} + +Fl_Wayland_Window_Driver::type_for_resize_window_between_screens Fl_Wayland_Window_Driver::data_for_resize_window_between_screens_ = {0, false}; + +void Fl_Wayland_Window_Driver::resize_after_screen_change(void *data) { + Fl_Window *win = (Fl_Window*)data; + float f = Fl::screen_driver()->scale(data_for_resize_window_between_screens_.screen); + Fl_Window_Driver::driver(win)->resize_after_scale_change(data_for_resize_window_between_screens_.screen, f, f); + data_for_resize_window_between_screens_.busy = false; +} + + +int Fl_Wayland_Window_Driver::screen_num() { + if (pWindow->parent()) { + screen_num_ = Fl_Window_Driver::driver(pWindow->top_window())->screen_num(); + } + return screen_num_ >= 0 ? screen_num_ : 0; +} + + +int Fl_Wayland_Window_Driver::set_cursor(Fl_Cursor c) { + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + + // Cursor names are the files of directory /usr/share/icons/XXXX/cursors/ + // where XXXX is the name of the current 'cursor theme'. + switch (c) { + case FL_CURSOR_ARROW: + if (!scr_driver->xc_arrow) scr_driver->xc_arrow = scr_driver->cache_cursor("left_ptr"); + scr_driver->default_cursor(scr_driver->xc_arrow); + break; + case FL_CURSOR_NS: + if (!scr_driver->xc_ns) scr_driver->xc_ns = scr_driver->cache_cursor("sb_v_double_arrow"); + if (!scr_driver->xc_ns) return 0; + scr_driver->default_cursor(scr_driver->xc_ns); + break; + case FL_CURSOR_CROSS: + if (!scr_driver->xc_cross) scr_driver->xc_cross = scr_driver->cache_cursor("cross"); + if (!scr_driver->xc_cross) return 0; + scr_driver->default_cursor(scr_driver->xc_cross); + break; + case FL_CURSOR_WAIT: + if (!scr_driver->xc_wait) scr_driver->xc_wait = scr_driver->cache_cursor("wait"); + if (!scr_driver->xc_wait) scr_driver->xc_wait = scr_driver->cache_cursor("watch"); + if (!scr_driver->xc_wait) return 0; + scr_driver->default_cursor(scr_driver->xc_wait); + break; + case FL_CURSOR_INSERT: + if (!scr_driver->xc_insert) scr_driver->xc_insert = scr_driver->cache_cursor("xterm"); + if (!scr_driver->xc_insert) return 0; + scr_driver->default_cursor(scr_driver->xc_insert); + break; + case FL_CURSOR_HAND: + if (!scr_driver->xc_hand) scr_driver->xc_hand = scr_driver->cache_cursor("hand"); + if (!scr_driver->xc_hand) scr_driver->xc_hand = scr_driver->cache_cursor("hand1"); + if (!scr_driver->xc_hand) return 0; + scr_driver->default_cursor(scr_driver->xc_hand); + break; + case FL_CURSOR_HELP: + if (!scr_driver->xc_help) scr_driver->xc_help = scr_driver->cache_cursor("help"); + if (!scr_driver->xc_help) return 0; + scr_driver->default_cursor(scr_driver->xc_help); + break; + case FL_CURSOR_MOVE: + if (!scr_driver->xc_move) scr_driver->xc_move = scr_driver->cache_cursor("move"); + if (!scr_driver->xc_move) return 0; + scr_driver->default_cursor(scr_driver->xc_move); + break; + case FL_CURSOR_WE: + if (!scr_driver->xc_we) scr_driver->xc_we = scr_driver->cache_cursor("sb_h_double_arrow"); + if (!scr_driver->xc_we) return 0; + scr_driver->default_cursor(scr_driver->xc_we); + break; + case FL_CURSOR_N: + if (!scr_driver->xc_north) scr_driver->xc_north = scr_driver->cache_cursor("top_side"); + if (!scr_driver->xc_north) return 0; + scr_driver->default_cursor(scr_driver->xc_north); + break; + case FL_CURSOR_E: + if (!scr_driver->xc_east) scr_driver->xc_east = scr_driver->cache_cursor("right_side"); + if (!scr_driver->xc_east) return 0; + scr_driver->default_cursor(scr_driver->xc_east); + break; + case FL_CURSOR_W: + if (!scr_driver->xc_west) scr_driver->xc_west = scr_driver->cache_cursor("left_side"); + if (!scr_driver->xc_west) return 0; + scr_driver->default_cursor(scr_driver->xc_west); + break; + case FL_CURSOR_S: + if (!scr_driver->xc_south) scr_driver->xc_south = scr_driver->cache_cursor("bottom_side"); + if (!scr_driver->xc_south) return 0; + scr_driver->default_cursor(scr_driver->xc_south); + break; + case FL_CURSOR_NESW: + if (!scr_driver->xc_nesw) scr_driver->xc_nesw = scr_driver->cache_cursor("fd_double_arrow"); + if (!scr_driver->xc_nesw) return 0; + scr_driver->default_cursor(scr_driver->xc_nesw); + break; + case FL_CURSOR_NWSE: + if (!scr_driver->xc_nwse) scr_driver->xc_nwse = scr_driver->cache_cursor("bd_double_arrow"); + if (!scr_driver->xc_nwse) return 0; + scr_driver->default_cursor(scr_driver->xc_nwse); + break; + case FL_CURSOR_SW: + if (!scr_driver->xc_sw) scr_driver->xc_sw = scr_driver->cache_cursor("bottom_left_corner"); + if (!scr_driver->xc_sw) return 0; + scr_driver->default_cursor(scr_driver->xc_sw); + break; + case FL_CURSOR_SE: + if (!scr_driver->xc_se) scr_driver->xc_se = scr_driver->cache_cursor("bottom_right_corner"); + if (!scr_driver->xc_se) return 0; + scr_driver->default_cursor(scr_driver->xc_se); + break; + case FL_CURSOR_NE: + if (!scr_driver->xc_ne) scr_driver->xc_ne = scr_driver->cache_cursor("top_right_corner"); + if (!scr_driver->xc_ne) return 0; + scr_driver->default_cursor(scr_driver->xc_ne); + break; + case FL_CURSOR_NW: + if (!scr_driver->xc_nw) scr_driver->xc_nw = scr_driver->cache_cursor("top_left_corner"); + if (!scr_driver->xc_nw) return 0; + scr_driver->default_cursor(scr_driver->xc_nw); + break; + + default: + return 0; + } + if (cursor_) delete_cursor_(); + scr_driver->set_cursor(); + return 1; +} + + +void Fl_Wayland_Window_Driver::update_scale() +{ + struct wld_window *window = fl_xid(pWindow); + int scale = 0; + Fl_Wayland_Window_Driver::window_output *window_output; + + wl_list_for_each(window_output, &window->outputs, link) { + scale = fl_max(scale, window_output->output->wld_scale); + } + if (scale && scale != window->scale) { + window->scale = scale; + if (window->buffer || window->fl_win->as_gl_window()) { + Fl_Wayland_Graphics_Driver::buffer_release(window); + window->fl_win->damage(FL_DAMAGE_ALL); + Fl_Window_Driver::driver(window->fl_win)->flush(); + } + } +} + + +void Fl_Wayland_Window_Driver::use_border() { + if (!shown() || pWindow->parent()) return; + struct libdecor_frame *frame = fl_xid(pWindow)->frame; + if (frame && Fl_Wayland_Screen_Driver::compositor != Fl_Wayland_Screen_Driver::KDE) { + libdecor_frame_set_visibility(frame, pWindow->border()); + pWindow->redraw(); + } else { + Fl_Window_Driver::use_border(); + } +} + + +/* Change an existing window to fullscreen */ +void Fl_Wayland_Window_Driver::fullscreen_on() { + int top, bottom, left, right; + + top = fullscreen_screen_top(); + bottom = fullscreen_screen_bottom(); + left = fullscreen_screen_left(); + right = fullscreen_screen_right(); + + if ((top < 0) || (bottom < 0) || (left < 0) || (right < 0)) { + top = screen_num(); + bottom = top; + left = top; + right = top; + } + pWindow->wait_for_expose(); // make sure ->xdg_toplevel is initialized + if (xdg_toplevel()) { + xdg_toplevel_set_fullscreen(xdg_toplevel(), NULL); + pWindow->_set_fullscreen(); + wl_display_roundtrip(fl_display); // OK, but try to find something more specific + wl_display_roundtrip(fl_display); + Fl::handle(FL_FULLSCREEN, pWindow); + } +} + + +void Fl_Wayland_Window_Driver::fullscreen_off(int X, int Y, int W, int H) { + if (!border()) pWindow->Fl_Group::resize(X, Y, W, H); + xdg_toplevel_unset_fullscreen(xdg_toplevel()); + pWindow->_clear_fullscreen(); + Fl::handle(FL_FULLSCREEN, pWindow); +} + + +void Fl_Wayland_Window_Driver::label(const char *name, const char *iname) { + if (shown() && !parent()) { + if (!name) name = ""; + if (!iname) iname = fl_filename_name(name); + libdecor_frame_set_title(fl_xid(pWindow)->frame, name); + } +} + + +int Fl_Wayland_Window_Driver::set_cursor(const Fl_RGB_Image *rgb, int hotx, int hoty) { +// build a new wl_cursor and its image + struct wl_cursor *new_cursor = (struct wl_cursor*)malloc(sizeof(struct wl_cursor)); + struct cursor_image *new_image = (struct cursor_image*)calloc(1, sizeof(struct cursor_image)); + int scale = fl_xid(pWindow)->scale; + new_image->image.width = rgb->w() * scale; + new_image->image.height = rgb->h() * scale; + new_image->image.hotspot_x = hotx * scale; + new_image->image.hotspot_y = hoty * scale; + new_image->image.delay = 0; + new_image->offset = 0; + //create a Wayland buffer and have it used as an image of the new cursor + struct fl_wld_buffer *offscreen = Fl_Wayland_Graphics_Driver::create_shm_buffer(new_image->image.width, new_image->image.height); + new_image->buffer = offscreen->wl_buffer; + wl_buffer_set_user_data(new_image->buffer, offscreen); + new_cursor->image_count = 1; + new_cursor->images = (struct wl_cursor_image**)malloc(sizeof(struct wl_cursor_image*)); + new_cursor->images[0] = (struct wl_cursor_image*)new_image; + new_cursor->name = strdup("custom cursor"); + // draw the rgb image to the cursor's drawing buffer + Fl_Image_Surface *img_surf = new Fl_Image_Surface(new_image->image.width, new_image->image.height, 0, offscreen); + Fl_Surface_Device::push_current(img_surf); + Fl_Wayland_Graphics_Driver *driver = (Fl_Wayland_Graphics_Driver*)img_surf->driver(); + cairo_scale(driver->cr(), scale, scale); + memset(offscreen->draw_buffer, 0, offscreen->data_size); + ((Fl_RGB_Image*)rgb)->draw(0, 0); + Fl_Surface_Device::pop_current(); + delete img_surf; + memcpy(offscreen->data, offscreen->draw_buffer, offscreen->data_size); + // delete the previous custom cursor, if there was one + delete_cursor_(); + //have this new cursor used + cursor_ = new_cursor; + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + scr_driver->default_cursor(cursor_); + return 1; +} + + +#if defined(USE_SYSTEM_LIBDECOR) && USE_SYSTEM_LIBDECOR +// This is only to fix a bug in libdecor where what libdecor_frame_set_min_content_size() +// does is often destroyed by libdecor-cairo. +static void delayed_minsize(Fl_Window *win) { + struct wld_window *wl_win = fl_xid(win); + Fl_Window_Driver *driver = Fl_Window_Driver::driver(win); + if (wl_win->kind == Fl_Wayland_Window_Driver::DECORATED) { + float f = Fl::screen_scale(win->screen_num()); + libdecor_frame_set_min_content_size(wl_win->frame, driver->minw()*f, driver->minh()*f); + } + bool need_resize = false; + int W = win->w(), H = win->h(); + if (W < driver->minw()) { W = driver->minw(); need_resize = true; } + if (H < driver->minh()) { H = driver->minh(); need_resize = true; } + if (need_resize) win->size(W, H); +} +#endif + + +void Fl_Wayland_Window_Driver::resize(int X, int Y, int W, int H) { + int is_a_move = (X != x() || Y != y() || Fl_Window::is_a_rescale()); + int is_a_resize = (W != w() || H != h() || Fl_Window::is_a_rescale()); + if (is_a_move) force_position(1); + else if (!is_a_resize && !is_a_move) return; + if (is_a_resize) { + pWindow->Fl_Group::resize(X,Y,W,H); +//fprintf(stderr, "resize: win=%p to %dx%d\n", pWindow, W, H); + if (shown()) {pWindow->redraw();} + } else { + if (pWindow->parent() || pWindow->menu_window() || pWindow->tooltip_window()) { + x(X); y(Y); +//fprintf(stderr, "move menuwin=%p x()=%d\n", pWindow, X); + } else { + //"a deliberate design trait of Wayland makes application windows ignorant of their exact placement on screen" + x(0); y(0); + } + } + + if (shown()) { + struct wld_window *fl_win = fl_xid(pWindow); + float f = Fl::screen_scale(pWindow->screen_num()); + if (is_a_resize) { + if (fl_win->kind == DECORATED) { // a decorated window + if (fl_win->buffer) { + Fl_Wayland_Graphics_Driver::buffer_release(fl_win); + } + fl_win->configured_width = W; + fl_win->configured_height = H; + if (!in_handle_configure && xdg_toplevel()) { + struct libdecor_state *state = libdecor_state_new(int(W * f), int(H * f)); + libdecor_frame_commit(fl_win->frame, state, NULL); // necessary only if resize is initiated by prog + libdecor_state_free(state); + if (libdecor_frame_is_floating(fl_win->frame)) { + fl_win->floating_width = int(W*f); + fl_win->floating_height = int(H*f); + } + } + } else if (fl_win->kind == SUBWINDOW && fl_win->subsurface) { // a subwindow + wl_subsurface_set_position(fl_win->subsurface, X * f, Y * f); + if (!pWindow->as_gl_window()) Fl_Wayland_Graphics_Driver::buffer_release(fl_win); + fl_win->configured_width = W; + fl_win->configured_height = H; + } else if (fl_win->xdg_surface) { // a window without border + if (!pWindow->as_gl_window()) Fl_Wayland_Graphics_Driver::buffer_release(fl_win); + fl_win->configured_width = W; + fl_win->configured_height = H; + xdg_surface_set_window_geometry(fl_win->xdg_surface, 0, 0, W * f, H * f); + } +#if defined(USE_SYSTEM_LIBDECOR) && USE_SYSTEM_LIBDECOR + if (W < minw() || H < minh()) { + Fl::add_timeout(0.01, (Fl_Timeout_Handler)delayed_minsize, pWindow); + } +#endif + } else { + if (!in_handle_configure && xdg_toplevel()) { + // Wayland doesn't seem to provide a reliable way for the app to set the + // window position on screen. This is functional when the move is mouse-driven. + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + xdg_toplevel_move(xdg_toplevel(), scr_driver->seat->wl_seat, scr_driver->seat->serial); + } else if (fl_win->kind == SUBWINDOW && fl_win->subsurface) { + wl_subsurface_set_position(fl_win->subsurface, pWindow->x() * f, pWindow->y() * f); + } + } + } +} + +void Fl_Wayland_Window_Driver::reposition_menu_window(int x, int y) { + Window xid_menu = fl_xid(pWindow); + if (y == pWindow->y() && y >= 0) return; + int true_y = y; + int y_offset = 0; + if (y < 0) { + y_offset = y-1; + y = 1; + } + + //printf("should move menuwindow to %d y_offset=%d y=%d\n", true_y, y_offset, pWindow->y()); + struct xdg_popup *old_popup = xid_menu->xdg_popup; + struct xdg_surface *old_xdg = xid_menu->xdg_surface; + struct wl_surface *old_surface = xid_menu->wl_surface; + // create a new popup at position (x,y) and display it above the current one + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + xid_menu->wl_surface = wl_compositor_create_surface(scr_driver->wl_compositor); + wl_surface_add_listener(xid_menu->wl_surface, &surface_listener, xid_menu); + xid_menu->xdg_surface = xdg_wm_base_get_xdg_surface(scr_driver->xdg_wm_base, xid_menu->wl_surface); + xdg_surface_add_listener(xid_menu->xdg_surface, &xdg_surface_listener, xid_menu); + struct xdg_positioner *positioner = xdg_wm_base_create_positioner(scr_driver->xdg_wm_base); + Window parent_xid = fl_xid(Fl_Window_Driver::menu_parent()); + float f = Fl::screen_scale(Fl_Window_Driver::menu_parent()->screen_num()); + int popup_x = x * f, popup_y = y * f; + if (parent_xid->kind == DECORATED) + libdecor_frame_translate_coordinate(parent_xid->frame, popup_x, popup_y, &popup_x, &popup_y); + xdg_positioner_set_anchor_rect(positioner, popup_x, popup_y, 1, 1); + xdg_positioner_set_size(positioner, pWindow->w() * f , pWindow->h() * f ); + xdg_positioner_set_anchor(positioner, XDG_POSITIONER_ANCHOR_TOP_LEFT); + xdg_positioner_set_gravity(positioner, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); + if (y_offset) xdg_positioner_set_offset(positioner, 0, y_offset); + xid_menu->xdg_popup = xdg_surface_get_popup(xid_menu->xdg_surface, parent_xid->xdg_surface, positioner); + xdg_positioner_destroy(positioner); + xdg_popup_add_listener(xid_menu->xdg_popup, &popup_listener, xid_menu); + wl_surface_commit(xid_menu->wl_surface); + wl_display_roundtrip(fl_display); + // delete the previous popup + xdg_popup_destroy(old_popup); + xdg_surface_destroy(old_xdg); + wl_surface_destroy(old_surface); + wl_display_roundtrip(fl_display); + this->y(true_y); +} + + +void Fl_Wayland_Window_Driver::menu_window_area(int &X, int &Y, int &W, int &H, int nscreen) { + Fl_Window *parent = Fl_Window_Driver::menu_parent(); + if (parent) { + X = parent->x(); + Y = parent->y(); + W = parent->w(); + H = parent->h(); + //printf("menu_window_area: %dx%d - %dx%d\n",X,Y,W,H); + } else Fl_Window_Driver::menu_window_area(X, Y, W, H, nscreen); +} |
