diff options
| author | ManoloFLTK <41016272+ManoloFLTK@users.noreply.github.com> | 2022-03-04 15:40:29 +0100 |
|---|---|---|
| committer | ManoloFLTK <41016272+ManoloFLTK@users.noreply.github.com> | 2022-03-04 15:41:00 +0100 |
| commit | 3718effc431f5622a23c55b254153efdfe4e72c4 (patch) | |
| tree | d8a805870c6a3785022e2f52f0c3715410e29a37 /src/drivers/Wayland | |
| parent | a773fdc44bfb818f1830e9e48ba765881e68c942 (diff) | |
Add the Wayland platform to FLTK 1.4
Diffstat (limited to 'src/drivers/Wayland')
| -rw-r--r-- | src/drivers/Wayland/Fl_Font.H | 33 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_Copy_Surface_Driver.cxx | 74 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_Gl_Window_Driver.cxx | 401 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_Graphics_Driver.H | 158 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_Graphics_Driver.cxx | 1039 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_Image_Surface_Driver.cxx | 107 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_Screen_Driver.H | 175 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx | 1428 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_System_Driver.H | 34 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_System_Driver.cxx | 98 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_Window_Driver.H | 172 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_Wayland_Window_Driver.cxx | 1582 | ||||
| -rw-r--r-- | src/drivers/Wayland/Fl_wayland.cxx | 672 |
13 files changed, 5973 insertions, 0 deletions
diff --git a/src/drivers/Wayland/Fl_Font.H b/src/drivers/Wayland/Fl_Font.H new file mode 100644 index 000000000..eca2f32ca --- /dev/null +++ b/src/drivers/Wayland/Fl_Font.H @@ -0,0 +1,33 @@ +// +// Font definitions for the Fast Light Tool Kit (FLTK). +// +// Copyright 2021 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 +// + +#ifndef FL_FONT_ +#define FL_FONT_ + +#include <config.h> +#include "Fl_Wayland_Graphics_Driver.H" + +class Fl_Wayland_Font_Descriptor : public Fl_Font_Descriptor { +public: + Fl_Wayland_Font_Descriptor(const char* fontname, Fl_Fontsize size); + FL_EXPORT ~Fl_Wayland_Font_Descriptor(); + PangoFontDescription *fontref; + int **width; // array of arrays of character widths +}; + +extern FL_EXPORT Fl_Fontdesc *fl_fonts; // the table + +#endif // FL_FONT_ diff --git a/src/drivers/Wayland/Fl_Wayland_Copy_Surface_Driver.cxx b/src/drivers/Wayland/Fl_Wayland_Copy_Surface_Driver.cxx new file mode 100644 index 000000000..addcfbebd --- /dev/null +++ b/src/drivers/Wayland/Fl_Wayland_Copy_Surface_Driver.cxx @@ -0,0 +1,74 @@ +// +// Copy-to-clipboard code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2021 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/Fl_Copy_Surface.H> +#include <FL/Fl_Image_Surface.H> +#include "Fl_Wayland_Graphics_Driver.H" +#include "Fl_Wayland_Screen_Driver.H" +#include "Fl_Wayland_Window_Driver.H" +#include <FL/platform.H> + +class Fl_Wayland_Copy_Surface_Driver : public Fl_Copy_Surface_Driver { + friend class Fl_Copy_Surface_Driver; + Fl_Image_Surface *img_surf; +protected: + Fl_Wayland_Copy_Surface_Driver(int w, int h); + ~Fl_Wayland_Copy_Surface_Driver(); + void set_current(); + void translate(int x, int y); + void untranslate(); +}; + + +Fl_Copy_Surface_Driver *Fl_Copy_Surface_Driver::newCopySurfaceDriver(int w, int h) +{ + return new Fl_Wayland_Copy_Surface_Driver(w, h); +} + + +Fl_Wayland_Copy_Surface_Driver::Fl_Wayland_Copy_Surface_Driver(int w, int h) : Fl_Copy_Surface_Driver(w, h) { + int os_scale = (fl_window ? fl_window->scale : 1); + img_surf = new Fl_Image_Surface(w * os_scale, h * os_scale); + driver(img_surf->driver()); + driver()->scale(os_scale); +} + + +Fl_Wayland_Copy_Surface_Driver::~Fl_Wayland_Copy_Surface_Driver() { + Fl_RGB_Image *rgb = img_surf->image(); + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + scr_driver->copy_image(rgb->array, rgb->data_w(), rgb->data_h()); + delete rgb; + delete img_surf; + driver(NULL); +} + + +void Fl_Wayland_Copy_Surface_Driver::set_current() { + Fl_Surface_Device::set_current(); + ((Fl_Wayland_Graphics_Driver*)driver())->activate(img_surf->offscreen(), driver()->scale()); +} + + +void Fl_Wayland_Copy_Surface_Driver::translate(int x, int y) { + ((Fl_Wayland_Graphics_Driver*)driver())->ps_translate(x, y); +} + + +void Fl_Wayland_Copy_Surface_Driver::untranslate() { + ((Fl_Wayland_Graphics_Driver*)driver())->ps_untranslate(); +} diff --git a/src/drivers/Wayland/Fl_Wayland_Gl_Window_Driver.cxx b/src/drivers/Wayland/Fl_Wayland_Gl_Window_Driver.cxx new file mode 100644 index 000000000..290a52361 --- /dev/null +++ b/src/drivers/Wayland/Fl_Wayland_Gl_Window_Driver.cxx @@ -0,0 +1,401 @@ +// +// Class Fl_Wayland_Gl_Window_Driver for the Fast Light Tool Kit (FLTK). +// +// Copyright 2021-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> +#if HAVE_GL +#include <FL/platform.H> +#include <FL/Fl_Image_Surface.H> +#include "../../Fl_Gl_Choice.H" +#include "../../Fl_Screen_Driver.H" +#include "Fl_Wayland_Window_Driver.H" +#include "Fl_Wayland_Graphics_Driver.H" +#include "../../Fl_Gl_Window_Driver.H" +#include <wayland-egl.h> +#include <EGL/egl.h> +#include <FL/gl.h> + +/* Implementation note about OpenGL drawing on the Wayland platform + +After eglCreateWindowSurface() with attributes {EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER, EGL_NONE}, +eglQueryContext() reports that EGL_RENDER_BUFFER equals EGL_BACK_BUFFER. +This experiment suggests that the platform only supports double-buffer drawing. +Consequently, FL_DOUBLE is enforced in all Fl_Gl_Window::mode_ values under Wayland. +*/ + +class Fl_Wayland_Gl_Window_Driver : public Fl_Gl_Window_Driver { + friend class Fl_Gl_Window_Driver; + bool egl_resize_in_progress; +protected: + Fl_Wayland_Gl_Window_Driver(Fl_Gl_Window *win); + virtual float pixels_per_unit(); + virtual void make_current_before(); + virtual int mode_(int m, const int *a); + virtual void swap_buffers(); + virtual void resize(int is_a_resize, int w, int h); + virtual char swap_type(); + virtual Fl_Gl_Choice *find(int m, const int *alistp); + virtual GLContext create_gl_context(Fl_Window* window, const Fl_Gl_Choice* g, int layer = 0); + virtual void set_gl_context(Fl_Window* w, GLContext context); + virtual void delete_gl_context(GLContext); + virtual void make_overlay_current(); + virtual void redraw_overlay(); + virtual void waitGL(); + virtual void gl_start(); + virtual Fl_RGB_Image* capture_gl_rectangle(int x, int y, int w, int h); + char *alpha_mask_for_string(const char *str, int n, int w, int h, Fl_Fontsize fs); +public: + static EGLDisplay egl_display; + static EGLint configs_count; + static struct wl_event_queue *gl_event_queue; + void init(); + struct wl_egl_window *egl_window; + EGLSurface egl_surface; +}; + +// Describes crap needed to create a GLContext. +class Fl_Wayland_Gl_Choice : public Fl_Gl_Choice { + friend class Fl_Wayland_Gl_Window_Driver; +private: + EGLConfig egl_conf; +public: + Fl_Wayland_Gl_Choice(int m, const int *alistp, Fl_Gl_Choice *n) : Fl_Gl_Choice(m, alistp, n) { + egl_conf = 0; + } +}; + +EGLDisplay Fl_Wayland_Gl_Window_Driver::egl_display = EGL_NO_DISPLAY; +EGLint Fl_Wayland_Gl_Window_Driver::configs_count = 0; +struct wl_event_queue *Fl_Wayland_Gl_Window_Driver::gl_event_queue = NULL; + + +Fl_Wayland_Gl_Window_Driver::Fl_Wayland_Gl_Window_Driver(Fl_Gl_Window *win) : Fl_Gl_Window_Driver(win) { + if (egl_display == EGL_NO_DISPLAY) init(); + egl_window = NULL; + egl_surface = NULL; + egl_resize_in_progress = false; +} + + +void Fl_Wayland_Gl_Window_Driver::init() { + EGLint major, minor; + + if (!fl_display) Fl::screen_driver()->open_display(); + egl_display = eglGetDisplay((EGLNativeDisplayType) fl_display); + if (egl_display == EGL_NO_DISPLAY) { + Fl::fatal("Can't create egl display\n"); + } + + if (eglInitialize(egl_display, &major, &minor) != EGL_TRUE) { + Fl::fatal("Can't initialise egl display\n"); + } + //printf("EGL major: %d, minor %d\n", major, minor); + + eglGetConfigs(egl_display, NULL, 0, &configs_count); + //printf("EGL has %d configs\n", configs_count); + eglBindAPI(EGL_OPENGL_API); + + gl_event_queue = wl_display_create_queue(fl_display); +} + + +char *Fl_Wayland_Gl_Window_Driver::alpha_mask_for_string(const char *str, int n, int w, int h, Fl_Fontsize fs) +{ + // write str to a bitmap just big enough + Window save_win = fl_window; + fl_window = NULL; + Fl_Image_Surface *surf = new Fl_Image_Surface(w, h); + fl_window = save_win; + Fl_Font f=fl_font(); + Fl_Surface_Device::push_current(surf); + fl_color(FL_BLACK); + fl_rectf(0, 0, w, h); + fl_color(FL_WHITE); + fl_font(f, fs); + fl_draw(str, n, 0, fl_height() - fl_descent()); + // get the R channel only of the bitmap + char *alpha_buf = new char[w*h], *r = alpha_buf, *q; + for (int i = 0; i < h; i++) { + q = (char*)surf->offscreen()->draw_buffer + i * surf->offscreen()->stride; + for (int j = 0; j < w; j++) { + *r++ = *q; + q += 4; + } + } + Fl_Surface_Device::pop_current(); + delete surf; + return alpha_buf; +} + + +Fl_Gl_Choice *Fl_Wayland_Gl_Window_Driver::find(int m, const int *alistp) +{ + m |= FL_DOUBLE; + Fl_Wayland_Gl_Choice *g = (Fl_Wayland_Gl_Choice*)Fl_Gl_Window_Driver::find_begin(m, alistp); + if (g) return g; + + EGLint n; + EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RED_SIZE, 8, + EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, + EGL_RENDERABLE_TYPE, EGL_OPENGL_BIT, + EGL_DEPTH_SIZE, 0, // set at 11 + EGL_SAMPLE_BUFFERS, 0, // set at 13 + EGL_STENCIL_SIZE, 0, // set at 15 + EGL_NONE + }; + + if (m & FL_DEPTH) config_attribs[11] = 1; + if (m & FL_MULTISAMPLE) config_attribs[13] = 1; + if (m & FL_STENCIL) config_attribs[15] = 1; + + static EGLConfig *configs = (void**)calloc(configs_count, sizeof(EGLConfig)); + eglChooseConfig(egl_display, config_attribs, configs, configs_count, &n); + if (n == 0 && (m & FL_MULTISAMPLE)) { + config_attribs[13] = 0; + eglChooseConfig(egl_display, config_attribs, configs, configs_count, &n); + } + if (n == 0) { + Fl::fatal("failed to choose an EGL config\n"); + } + + g = new Fl_Wayland_Gl_Choice(m, alistp, first); + /*for (int i = 0; i < n; i++) { + EGLint size; + eglGetConfigAttrib(egl_display, configs[i], EGL_BUFFER_SIZE, &size); + printf("Buffer size for config %d is %d\n", i, size); + eglGetConfigAttrib(egl_display, configs[i], EGL_RED_SIZE, &size); + printf("Red size for config %d is %d\n", i, size); + // just choose the first one + g->egl_conf = configs[i]; + break; + }*/ + // just choose the first config + g->egl_conf = configs[0]; + first = g; + return g; +} + + +GLContext Fl_Wayland_Gl_Window_Driver::create_gl_context(Fl_Window* window, const Fl_Gl_Choice* g, int layer) { + GLContext shared_ctx = 0; + if (context_list && nContext) shared_ctx = context_list[0]; + + static const EGLint context_attribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; + GLContext ctx = (GLContext)eglCreateContext(egl_display, ((Fl_Wayland_Gl_Choice*)g)->egl_conf, shared_ctx?shared_ctx:EGL_NO_CONTEXT, context_attribs); +//fprintf(stderr, "eglCreateContext=%p shared_ctx=%p\n", ctx, shared_ctx); + if (ctx) + add_context(ctx); + return ctx; +} + + +void Fl_Wayland_Gl_Window_Driver::set_gl_context(Fl_Window* w, GLContext context) { + struct wld_window *win = fl_xid(w); + if (!win || !egl_surface) return; + if (context != cached_context || w != cached_window) { + cached_context = context; + cached_window = w; + if (eglMakeCurrent(egl_display, egl_surface, egl_surface, (EGLContext)context)) { +//fprintf(stderr, "EGLContext %p made current\n", context); + } else { + Fl::error("eglMakeCurrent() failed\n"); + } + } +} + +void Fl_Wayland_Gl_Window_Driver::delete_gl_context(GLContext context) { + if (cached_context == context) { + cached_context = 0; + cached_window = 0; + } +//EGLBoolean b = + eglDestroyContext(egl_display, context); +//fprintf(stderr,"EGL context %p destroyed %s\n", context, b==EGL_TRUE?"successfully":"w/ error"); +//b = + eglDestroySurface(egl_display, egl_surface); +//fprintf(stderr,"EGLSurface %p destroyed %s\n", egl_surface, b==EGL_TRUE?"successfully":"w/ error"); + egl_surface = NULL; + wl_egl_window_destroy(egl_window); + egl_window = NULL; + del_context(context); +} + + +void Fl_Wayland_Gl_Window_Driver::make_overlay_current() { +//fprintf(stderr, "make_overlay_current\n"); + glDrawBuffer(GL_FRONT); +} + +void Fl_Wayland_Gl_Window_Driver::redraw_overlay() { +//fprintf(stderr, "redraw_overlay\n"); + pWindow->redraw(); +} + + +Fl_Gl_Window_Driver *Fl_Gl_Window_Driver::newGlWindowDriver(Fl_Gl_Window *w) +{ + return new Fl_Wayland_Gl_Window_Driver(w); +} + + +static void gl_frame_ready(void *data, struct wl_callback *cb, uint32_t time) { + *(bool*)data = true; +} + + +static const struct wl_callback_listener gl_surface_frame_listener = { + .done = gl_frame_ready, +}; + + +void Fl_Wayland_Gl_Window_Driver::make_current_before() { + if (!egl_window) { + struct wld_window *win = fl_xid(pWindow); + struct wl_surface *surface = win->wl_surface; + egl_window = wl_egl_window_create(surface, pWindow->pixel_w(), pWindow->pixel_h()); + if (egl_window == EGL_NO_SURFACE) { + Fl::fatal("Can't create egl window with wl_egl_window_create()\n"); + } else { + //fprintf(stderr, "Created egl window=%p\n", egl_window); + } + Fl_Wayland_Gl_Choice *g = (Fl_Wayland_Gl_Choice*)this->g(); + egl_surface = eglCreateWindowSurface(egl_display, g->egl_conf, egl_window, NULL); +//fprintf(stderr, "Created egl surface=%p at scale=%d\n", egl_surface, win->scale); + wl_surface_set_buffer_scale(surface, win->scale); + if (Fl_Wayland_Screen_Driver::compositor == Fl_Wayland_Screen_Driver::WESTON) { + bool done = false; + struct wl_callback *callback = wl_surface_frame(surface); + wl_surface_commit(surface); + wl_callback_add_listener(callback, &gl_surface_frame_listener, &done); + while (!done) wl_display_dispatch(fl_display); + } + } +} + + +float Fl_Wayland_Gl_Window_Driver::pixels_per_unit() +{ + int ns = Fl_Window_Driver::driver(pWindow)->screen_num(); + int wld_scale = pWindow->shown() ? fl_xid(pWindow)->scale : 1; + return wld_scale * Fl::screen_driver()->scale(ns); +} + + +int Fl_Wayland_Gl_Window_Driver::mode_(int m, const int *a) { + mode(m | FL_DOUBLE); + return 1; +} + + +void Fl_Wayland_Gl_Window_Driver::swap_buffers() { + if (overlay()) { + static bool overlay_buffer = true; + int wo = pWindow->pixel_w(), ho = pWindow->pixel_h(); + GLint matrixmode; + GLfloat pos[4]; + glGetIntegerv(GL_MATRIX_MODE, &matrixmode); + glGetFloatv(GL_CURRENT_RASTER_POSITION, pos); // save original glRasterPos + glMatrixMode(GL_PROJECTION); // save proj/model matrices + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glScalef(2.0f/wo, 2.0f/ho, 1.0f); + glTranslatef(-wo/2.0f, -ho/2.0f, 0.0f); // set transform so 0,0 is bottom/left of Gl_Window + glRasterPos2i(0,0); // set glRasterPos to bottom left corner + { + // Emulate overlay by doing copypixels + glReadBuffer(overlay_buffer?GL_BACK:GL_FRONT); + glDrawBuffer(overlay_buffer?GL_FRONT:GL_BACK); + overlay_buffer = ! overlay_buffer; + glCopyPixels(0, 0, wo, ho, GL_COLOR); + } + glPopMatrix(); // GL_MODELVIEW // restore model/proj matrices + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(matrixmode); + glRasterPos3f(pos[0], pos[1], pos[2]); // restore original glRasterPos + if (!overlay_buffer) return; // don't call eglSwapBuffers until overlay has been drawn + } + + if (egl_surface) { + //eglSwapInterval(egl_display, 0); // doesn't sem to have any effect in this context + if (!egl_resize_in_progress) { + while (wl_display_prepare_read(fl_display) != 0) { + wl_display_dispatch_pending(fl_display); + } + wl_display_read_events(fl_display); + wl_display_dispatch_queue_pending(fl_display, gl_event_queue); + } + egl_resize_in_progress = false; + eglSwapBuffers(Fl_Wayland_Gl_Window_Driver::egl_display, egl_surface); + } +} + + +class Fl_Wayland_Gl_Plugin : public Fl_Wayland_Plugin { +public: + Fl_Wayland_Gl_Plugin() : Fl_Wayland_Plugin(name()) { } + virtual const char *name() { return "gl.wayland.fltk.org"; } + virtual void do_swap(Fl_Window *w) { + Fl_Gl_Window_Driver *gldr = Fl_Gl_Window_Driver::driver(w->as_gl_window()); + if (gldr->overlay() == w) gldr->swap_buffers(); + } + virtual void invalidate(Fl_Window *w) { + w->as_gl_window()->valid(0); + } +}; + +static Fl_Wayland_Gl_Plugin Gl_Overlay_Plugin; + + +void Fl_Wayland_Gl_Window_Driver::resize(int is_a_resize, int W, int H) { + if (!egl_window) return; + struct wld_window *win = fl_xid(pWindow); + float f = Fl::screen_scale(pWindow->screen_num()); + W = (W * win->scale) * f; + H = (H * win->scale) * f; + int W2, H2; + wl_egl_window_get_attached_size(egl_window, &W2, &H2); + if (W2 != W || H2 != H) { + wl_egl_window_resize(egl_window, W, H, 0, 0); + //fprintf(stderr, "Fl_Wayland_Gl_Window_Driver::resize to %dx%d\n", W, H); + egl_resize_in_progress = true; + } +} + +char Fl_Wayland_Gl_Window_Driver::swap_type() { + return copy; +} + +void Fl_Wayland_Gl_Window_Driver::waitGL() { +} + +void Fl_Wayland_Gl_Window_Driver::gl_start() { +} + + +Fl_RGB_Image* Fl_Wayland_Gl_Window_Driver::capture_gl_rectangle(int x, int y, int w, int h) { + Fl_Surface_Device::push_current(Fl_Display_Device::display_device()); + Fl_RGB_Image *rgb = Fl_Gl_Window_Driver::capture_gl_rectangle(x, y, w, h); + Fl_Surface_Device::pop_current(); + return rgb; +} + +#endif // HAVE_GL diff --git a/src/drivers/Wayland/Fl_Wayland_Graphics_Driver.H b/src/drivers/Wayland/Fl_Wayland_Graphics_Driver.H new file mode 100644 index 000000000..19991c531 --- /dev/null +++ b/src/drivers/Wayland/Fl_Wayland_Graphics_Driver.H @@ -0,0 +1,158 @@ +// +// Definition of class Fl_Wayland_Graphics_Driver. +// +// Copyright 2021-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 +// + +/** + \file Fl_Wayland_Graphics_Driver.H + \brief Definition of Wayland graphics driver. + */ + +#ifndef FL_WAYLAND_GRAPHICS_DRIVER_H +#define FL_WAYLAND_GRAPHICS_DRIVER_H + + +/* Implementation note about buffers FLTK uses to support display graphics under Wayland. + + Each window is associated to an FLTK-defined object of type struct wld_window + containing itself an FLTK-defined struct fl_wld_buffer object holding all graphics data. + Among members of this latter structure are: + - struct wl_buffer wl_buffer + is a Wayland-defined type for a graphics buffer able to be attached to a wl_surface; + - void *data + points to the beginning of the memory zone where wl_buffer stores its graphics data; + - unsigned char *draw_buffer + contains a graphics buffer to which all Cairo drawings are directed; + draw_buffer and data both have the same organization called CAIRO_FORMAT_ARGB32 in Cairo parlance + and WL_SHM_FORMAT_ARGB8888 in Wayland parlance which means BGRA byte order. + - int width + gives the pixel width of the graphics buffer; + - int stride + gives the stride of this buffer; + - size_t data_size + gives the total buffer size in bytes (thus, data_size / stride gives the buffer height); + - bool draw_buffer_needs_commit + is TRUE when draw_buffer has been modified and needs being committed for display, and + FALSE after having been committed but before having been modified; + - struct wl_callback *cb + is used to synchronize drawing with the compositor during progressive drawing. + + When a graphics scene is to be committed, the data_size bytes of draw_buffer are copied by memcpy() + starting at data, and wl_buffer is attached to the wl_surface which is committed for display + by wl_surface_commit(). Finally, draw_buffer_needs_commit is set to FALSE. + + All drawing functions have Cairo write to draw_buffer and turn draw_buffer_needs_commit to TRUE. +*/ + + +#include "../Cairo/Fl_Cairo_Graphics_Driver.H" +#include <cairo/cairo.h> +#include <stdint.h> // for uint32_t +typedef struct _PangoLayout PangoLayout; + +struct fl_wld_buffer { + struct wl_buffer *wl_buffer; + void *data; + size_t data_size; // of wl_buffer and draw_buffer + int stride; + int width; + unsigned char *draw_buffer; + struct wl_callback *cb; + bool draw_buffer_needs_commit; + cairo_t *cairo_; + PangoLayout *pango_layout_; +}; +struct wld_window; + +class FL_EXPORT Fl_Wayland_Graphics_Driver : public Fl_Cairo_Graphics_Driver { +private: + struct fl_wld_buffer *buffer_; + PangoLayout *dummy_pango_layout_; // used to measure text width before showing a window + int linestyle_; + void draw_cached_pattern_(Fl_Image *img, cairo_pattern_t *pat, int X, int Y, int W, int H, int cx, int cy); +public: + Fl_Wayland_Graphics_Driver(); + ~Fl_Wayland_Graphics_Driver(); + static const uint32_t wld_format; + static const cairo_format_t cairo_format; + void activate(struct fl_wld_buffer *buffer, float scale); + void font(Fl_Font fnum, Fl_Fontsize s); + Fl_Font font() { return Fl_Graphics_Driver::font(); } + void draw(const char* s, int nBytes, int x, int y) { draw(s, nBytes, float(x), float(y)); } + void draw(const char* s, int nBytes, float x, float y); + void draw(int angle, const char *str, int n, int x, int y); + void rtl_draw(const char* str, int n, int x, int y); + int height(); + int descent(); + double width(const char *str, int n); + double width(unsigned c); + void text_extents(const char* txt, int n, int& dx, int& dy, int& w, int& h); + int not_clipped(int x, int y, int w, int h); + int clip_box(int x, int y, int w, int h, int &X, int &Y, int &W, int &H); + void restore_clip(); + void clip_region(Fl_Region r); + void line_style(int style, int width=0, char* dashes=0); + Fl_Region XRectangleRegion(int x, int y, int w, int h); + void add_rectangle_to_region(Fl_Region r, int X, int Y, int W, int H); + void XDestroyRegion(Fl_Region r); + void set_color(Fl_Color i, unsigned c); + Fl_Font set_fonts(const char* pattern_name); + const char *font_name(int num); + void font_name(int num, const char *name); + const char* get_font_name(Fl_Font fnum, int* ap); + int get_font_sizes(Fl_Font fnum, int*& sizep); + void point(int x, int y); + void copy_offscreen(int x, int y, int w, int h, Fl_Offscreen osrc, int srcx, int srcy); + void draw_image(const uchar *data, int ix, int iy, int iw, int ih, int D, int LD); + void curve(double x, double y, double x1, double y1, double x2, double y2, double x3, double y3); + void begin_points(); + void end_points(); + void transformed_vertex(double x, double y); + void draw_rgb(Fl_RGB_Image *rgb,int XP, int YP, int WP, int HP, int cx, int cy); + void cache(Fl_RGB_Image *rgb); + void uncache(Fl_RGB_Image *img, fl_uintptr_t &id_, fl_uintptr_t &mask_); + void draw_bitmap(Fl_Bitmap *bm,int XP, int YP, int WP, int HP, int cx, int cy); + void cache(Fl_Bitmap *img); + void delete_bitmask(Fl_Bitmask bm); + void cache(Fl_Pixmap *pxm); + void draw_pixmap(Fl_Pixmap *rgb,int XP, int YP, int WP, int HP, int cx, int cy); + void uncache_pixmap(fl_uintptr_t p); + void overlay_rect(int x, int y, int w , int h); + static void init_built_in_fonts(); + static struct fl_wld_buffer *create_shm_buffer(int width, int height); + static void buffer_release(struct wld_window *window); + static void buffer_commit(struct wld_window *window); + static void cairo_init(struct fl_wld_buffer *buffer, int width, int height, int stride, cairo_format_t format); + void line(int x1, int y1, int x2, int y2); + void line(int x1, int y1, int x2, int y2, int x3, int y3); + void xyline(int x, int y, int x1); + void xyline(int x, int y, int x1, int y2); + void xyline(int x, int y, int x1, int y2, int x3); + void yxline(int x, int y, int y1); + void yxline(int x, int y, int y1, int x2); + void yxline(int x, int y, int y1, int x2, int y3); + void loop(int x0, int y0, int x1, int y1, int x2, int y2); + void loop(int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3); + void rect(int x, int y, int w, int h); + void rectf(int x, int y, int w, int h); + void polygon(int x0, int y0, int x1, int y1, int x2, int y2); + void polygon(int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3); + void end_loop(); + void end_line(); + void end_polygon(); + void set_spot(int font, int height, int x, int y, int w, int h, Fl_Window *win); + void reset_spot(); +}; + +#endif // FL_WAYLAND_GRAPHICS_DRIVER_H diff --git a/src/drivers/Wayland/Fl_Wayland_Graphics_Driver.cxx b/src/drivers/Wayland/Fl_Wayland_Graphics_Driver.cxx new file mode 100644 index 000000000..55f6de8d9 --- /dev/null +++ b/src/drivers/Wayland/Fl_Wayland_Graphics_Driver.cxx @@ -0,0 +1,1039 @@ +// +// Implementation of the Wayland graphics driver. +// +// Copyright 2021-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_Graphics_Driver.H" +#include "Fl_Wayland_Screen_Driver.H" +#include "Fl_Wayland_Window_Driver.H" +#include "Fl_Font.H" +#include "text-input-client-protocol.h" +#include <pango/pangocairo.h> +#if ! PANGO_VERSION_CHECK(1,22,0) +# error "Requires Pango 1.22 or higher" +#endif +#define _GNU_SOURCE 1 +#include <sys/mman.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +extern unsigned fl_cmap[256]; // defined in fl_color.cxx + + +static int create_anonymous_file(int size, char **pshared) +{ + int ret; + int fd = memfd_create("FLTK-for-Wayland", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd < 0) { + Fl::fatal("memfd_create failed: %s\n", strerror(errno)); + } + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK); + do { + ret = posix_fallocate(fd, 0, size); + } while (ret == EINTR); + if (ret != 0) { + close(fd); + errno = ret; + Fl::fatal("creating anonymous file of size %d failed: %s\n", size, strerror(errno)); + } + *pshared = (char*)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (*pshared == MAP_FAILED) { + close(fd); + Fl::fatal("mmap failed: %s\n", strerror(errno)); + } +//printf("create_anonymous_file: %d\n",size); + return fd; +} + + +struct fl_wld_buffer *Fl_Wayland_Graphics_Driver::create_shm_buffer(int width, int height) +{ + struct fl_wld_buffer *buffer; + int stride = cairo_format_stride_for_width(Fl_Wayland_Graphics_Driver::cairo_format, width); + int size = stride * height; + static char *pool_memory = NULL; + static int pool_size = 10000000; // gets increased if necessary + static int chunk_offset = pool_size; + static int fd = -1; + static struct wl_shm_pool *pool = NULL; + if (chunk_offset + size > pool_size) { + chunk_offset = 0; + if (pool) { + wl_shm_pool_destroy(pool); + close(fd); + } + if (size > pool_size) pool_size = 2 * size; + fd = create_anonymous_file(pool_size, &pool_memory); + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + pool = wl_shm_create_pool(scr_driver->wl_shm, fd, pool_size); + } + buffer = (struct fl_wld_buffer*)calloc(1, sizeof(struct fl_wld_buffer)); + buffer->stride = stride; + buffer->wl_buffer = wl_shm_pool_create_buffer(pool, chunk_offset, width, height, stride, Fl_Wayland_Graphics_Driver::wld_format); + buffer->data = (void*)(pool_memory + chunk_offset); + chunk_offset += size; + buffer->data_size = size; + buffer->width = width; + buffer->draw_buffer = new uchar[buffer->data_size]; + buffer->draw_buffer_needs_commit = false; +//fprintf(stderr, "create_shm_buffer: %dx%d = %d\n", width, height, size); + cairo_init(buffer, width, height, stride, Fl_Wayland_Graphics_Driver::cairo_format); + return buffer; +} + + +void Fl_Wayland_Graphics_Driver::buffer_commit(struct wld_window *window) { + cairo_surface_t *surf = cairo_get_target(window->buffer->cairo_); + cairo_surface_flush(surf); + memcpy(window->buffer->data, window->buffer->draw_buffer, window->buffer->data_size); + wl_surface_attach(window->wl_surface, window->buffer->wl_buffer, 0, 0); + wl_surface_set_buffer_scale(window->wl_surface, window->scale); + wl_surface_commit(window->wl_surface); + window->buffer->draw_buffer_needs_commit = false; +//fprintf(stderr,"buffer_commit %s\n", window->fl_win->parent()?"child":"top"); +} + + +void Fl_Wayland_Graphics_Driver::cairo_init(struct fl_wld_buffer *buffer, int width, int height, int stride, cairo_format_t format) { + cairo_surface_t *surf = cairo_image_surface_create_for_data(buffer->draw_buffer, format, + width, height, stride); + if (cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS) { + Fl::fatal("Can't create Cairo surface with cairo_image_surface_create_for_data()\n"); + return; + } + buffer->cairo_ = cairo_create(surf); + cairo_status_t err; + if ((err = cairo_status(buffer->cairo_)) != CAIRO_STATUS_SUCCESS) { + Fl::fatal("Cairo error during cairo_create() %s\n", cairo_status_to_string(err)); + return; + } + cairo_set_source_rgba(buffer->cairo_, 1.0, 1.0, 1.0, 0.); + cairo_paint(buffer->cairo_); + cairo_set_source_rgba(buffer->cairo_, .0, .0, .0, 1.0); // Black default color + buffer->pango_layout_ = pango_cairo_create_layout(buffer->cairo_); + cairo_save(buffer->cairo_); +} + + +void Fl_Wayland_Graphics_Driver::buffer_release(struct wld_window *window) +{ + if (window->buffer) { + wl_buffer_destroy(window->buffer->wl_buffer); + delete[] window->buffer->draw_buffer; + window->buffer->draw_buffer = NULL; + cairo_surface_t *surf = cairo_get_target(window->buffer->cairo_); + cairo_destroy(window->buffer->cairo_); + cairo_surface_destroy(surf); + g_object_unref(window->buffer->pango_layout_); + free(window->buffer); + window->buffer = NULL; + } +} + +// these 2 refer to the same memory layout for pixel data +const uint32_t Fl_Wayland_Graphics_Driver::wld_format = WL_SHM_FORMAT_ARGB8888; +const cairo_format_t Fl_Wayland_Graphics_Driver::cairo_format = CAIRO_FORMAT_ARGB32; + + +Fl_Wayland_Graphics_Driver::Fl_Wayland_Graphics_Driver () : Fl_Cairo_Graphics_Driver() { + dummy_pango_layout_ = NULL; + linestyle_ = 0; +} + + +Fl_Graphics_Driver *Fl_Graphics_Driver::newMainGraphicsDriver() +{ + fl_graphics_driver = new Fl_Wayland_Graphics_Driver(); + return fl_graphics_driver; +} + + +Fl_Wayland_Graphics_Driver::~Fl_Wayland_Graphics_Driver() { + if (pango_layout_) g_object_unref(pango_layout_); +} + + +void Fl_Wayland_Graphics_Driver::activate(struct fl_wld_buffer *buffer, float scale) { + if (dummy_pango_layout_) { + cairo_surface_t *surf = cairo_get_target(cairo_); + cairo_destroy(cairo_); + cairo_surface_destroy(surf); + g_object_unref(dummy_pango_layout_); + dummy_pango_layout_ = NULL; + pango_layout_ = NULL; + } + cairo_ = buffer->cairo_; + if (pango_layout_ != buffer->pango_layout_) { + if (pango_layout_) g_object_unref(pango_layout_); + pango_layout_ = buffer->pango_layout_; + g_object_ref(pango_layout_); + Fl_Graphics_Driver::font(-1, -1); // signal that no font is current yet + } + this->buffer_ = buffer; + cairo_restore(cairo_); + cairo_save(cairo_); + cairo_scale(cairo_, scale, scale); + cairo_translate(cairo_, 0.5, 0.5); + line_style(0); +} + + +static Fl_Fontdesc built_in_table[] = { // Pango font names + {"Sans"}, + {"Sans Bold"}, + {"Sans Italic"}, + {"Sans Bold Italic"}, + {"Monospace"}, + {"Monospace Bold"}, + {"Monospace Italic"}, + {"Monospace Bold Italic"}, + {"Serif"}, + {"Serif Bold"}, + {"Serif Italic"}, + {"Serif Bold Italic"}, + {"Standard Symbols PS"}, // FL_SYMBOL + {"Monospace"}, // FL_SCREEN + {"Monospace Bold"}, // FL_SCREEN_BOLD + {"D050000L"}, // FL_ZAPF_DINGBATS +}; + +FL_EXPORT Fl_Fontdesc *fl_fonts = built_in_table; + + +static Fl_Font_Descriptor* find(Fl_Font fnum, Fl_Fontsize size) { + Fl_Fontdesc* s = fl_fonts+fnum; + if (!s->name) s = fl_fonts; // use 0 if fnum undefined + Fl_Font_Descriptor* f; + for (f = s->first; f; f = f->next) + if (f->size == size) return f; + f = new Fl_Wayland_Font_Descriptor(s->name, size); + f->next = s->first; + s->first = f; + return f; +} + + +Fl_Wayland_Font_Descriptor::Fl_Wayland_Font_Descriptor(const char* name, Fl_Fontsize size) : Fl_Font_Descriptor(name, size) { + char string[70]; + strcpy(string, name); + sprintf(string + strlen(string), " %d", int(size * 0.7 + 0.5) ); // why reduce size? + fontref = pango_font_description_from_string(string); + width = NULL; + static PangoFontMap *def_font_map = pango_cairo_font_map_get_default(); // 1.10 + static PangoContext *pango_context = pango_font_map_create_context(def_font_map); // 1.22 + static PangoLanguage *language = pango_language_get_default(); // 1.16 + PangoFontset *fontset = pango_font_map_load_fontset(def_font_map, pango_context, fontref, language); + PangoFontMetrics *metrics = pango_fontset_get_metrics(fontset); + ascent = pango_font_metrics_get_ascent(metrics)/PANGO_SCALE; + descent = pango_font_metrics_get_descent(metrics)/PANGO_SCALE; + q_width = pango_font_metrics_get_approximate_char_width(metrics)/PANGO_SCALE; + pango_font_metrics_unref(metrics); + g_object_unref(fontset); +//fprintf(stderr, "[%s](%d) ascent=%d descent=%d q_width=%d\n", name, size, ascent, descent, q_width); +} + + +Fl_Wayland_Font_Descriptor::~Fl_Wayland_Font_Descriptor() { + pango_font_description_free(fontref); + if (width) { + for (int i = 0; i < 64; i++) delete[] width[i]; + } + delete[] width; +} + + +int Fl_Wayland_Graphics_Driver::height() { + return (font_descriptor()->ascent + font_descriptor()->descent)*1.1 /*1.15 scale=1*/; +} + + +int Fl_Wayland_Graphics_Driver::descent() { + return font_descriptor()->descent; +} + + +void Fl_Wayland_Graphics_Driver::font(Fl_Font fnum, Fl_Fontsize s) { + if (font() == fnum && size() == s) return; + if (!font_descriptor()) fl_open_display(); + if (!pango_layout_) { + cairo_surface_t *surf = cairo_image_surface_create(Fl_Wayland_Graphics_Driver::cairo_format, 100, 100); + cairo_ = cairo_create(surf); + dummy_pango_layout_ = pango_cairo_create_layout(cairo_); + pango_layout_ = dummy_pango_layout_; + } + if (fnum == -1) { + Fl_Graphics_Driver::font(0, 0); + return; + } + Fl_Graphics_Driver::font(fnum, s); + font_descriptor( find(fnum, s) ); + pango_layout_set_font_description(pango_layout_, ((Fl_Wayland_Font_Descriptor*)font_descriptor())->fontref); +} + + +static int font_name_process(const char *name, char &face) { + int l = strlen(name); + face = ' '; + if (!memcmp(name + l - 8, " Regular", 8)) l -= 8; + else if (!memcmp(name + l - 6, " Plain", 6)) l -= 6; + else if (!memcmp(name + l - 12, " Bold Italic", 12)) {l -= 12; face='P';} + else if (!memcmp(name + l - 7, " Italic", 7)) {l -= 7; face='I';} + else if (!memcmp(name + l - 5, " Bold", 5)) {l -= 5; face='B';} + return l; +} + +typedef int (*sort_f_type)(const void *aa, const void *bb); + + +static int font_sort(Fl_Fontdesc *fa, Fl_Fontdesc *fb) { + char face_a, face_b; + int la = font_name_process(fa->name, face_a); + int lb = font_name_process(fb->name, face_b); + int c = strncasecmp(fa->name, fb->name, la >= lb ? lb : la); + return (c == 0 ? face_a - face_b : c); +} + + +Fl_Font Fl_Wayland_Graphics_Driver::set_fonts(const char* pattern_name) +{ + fl_open_display(); + int n_families, count = 0; + PangoFontFamily **families; + static PangoFontMap *pfmap_ = pango_cairo_font_map_get_default(); // 1.10 + Fl_Wayland_Graphics_Driver::init_built_in_fonts(); + pango_font_map_list_families(pfmap_, &families, &n_families); + for (int fam = 0; fam < n_families; fam++) { + PangoFontFace **faces; + int n_faces; + const char *fam_name = pango_font_family_get_name (families[fam]); + int l = strlen(fam_name); + pango_font_family_list_faces(families[fam], &faces, &n_faces); + for (int j = 0; j < n_faces; j++) { + const char *p = pango_font_face_get_face_name(faces[j]); + // build the font's FLTK name + l += strlen(p) + 2; + char *q = new char[l]; + sprintf(q, "%s %s", fam_name, p); + Fl::set_font((Fl_Font)(count++ + FL_FREE_FONT), q); + } + /*g_*/free(faces); // glib source code shows that g_free is equivalent to free + } + /*g_*/free(families); + // Sort the list into alphabetic order + qsort(fl_fonts + FL_FREE_FONT, count, sizeof(Fl_Fontdesc), (sort_f_type)font_sort); + return FL_FREE_FONT + count; +} + + +void Fl_Wayland_Graphics_Driver::init_built_in_fonts() { + static int i = 0; + if (!i) { + while (i < FL_FREE_FONT) { + i++; + Fl::set_font((Fl_Font)i-1, built_in_table[i-1].name); + } + } +} + + +const char *Fl_Wayland_Graphics_Driver::font_name(int num) { + return fl_fonts[num].name; +} + + +void Fl_Wayland_Graphics_Driver::font_name(int num, const char *name) { + Fl_Fontdesc *s = fl_fonts + num; + if (s->name) { + if (!strcmp(s->name, name)) {s->name = name; return;} + for (Fl_Font_Descriptor* f = s->first; f;) { + Fl_Font_Descriptor* n = f->next; delete f; f = n; + } + s->first = 0; + } + s->name = name; + s->fontname[0] = 0; + s->first = 0; +} + +#define ENDOFBUFFER sizeof(fl_fonts->fontname)-1 + +// turn a stored font name into a pretty name: +const char* Fl_Wayland_Graphics_Driver::get_font_name(Fl_Font fnum, int* ap) { + Fl_Fontdesc *f = fl_fonts + fnum; + if (!f->fontname[0]) { + strcpy(f->fontname, f->name); // to check + const char* thisFont = f->name; + if (!thisFont || !*thisFont) {if (ap) *ap = 0; return "";} + int type = 0; + if (strstr(f->name, "Bold")) type |= FL_BOLD; + if (strstr(f->name, "Italic") || strstr(f->name, "Oblique")) type |= FL_ITALIC; + f->fontname[ENDOFBUFFER] = (char)type; + } + if (ap) *ap = f->fontname[ENDOFBUFFER]; + return f->fontname; +} + + +int Fl_Wayland_Graphics_Driver::get_font_sizes(Fl_Font fnum, int*& sizep) { + static int array[128]; + if (!fl_fonts) fl_fonts = calc_fl_fonts(); + Fl_Fontdesc *s = fl_fonts+fnum; + if (!s->name) s = fl_fonts; // empty slot in table, use entry 0 + int cnt = 0; + + array[0] = 0; + sizep = array; + cnt = 1; + + return cnt; +} + + +void Fl_Wayland_Graphics_Driver::draw(const char* str, int n, float x, float y) { + if (!n) return; + cairo_save(cairo_); + cairo_translate(cairo_, x, y - height() + descent() -1); + pango_layout_set_text(pango_layout_, str, n); + pango_cairo_show_layout(cairo_, pango_layout_); + cairo_restore(cairo_); + buffer_->draw_buffer_needs_commit = true; +} + + +void Fl_Wayland_Graphics_Driver::draw(int rotation, const char *str, int n, int x, int y) +{ + cairo_save(cairo_); + cairo_translate(cairo_, x, y); + cairo_rotate(cairo_, -rotation * M_PI / 180); + this->draw(str, n, 0, 0); + cairo_restore(cairo_); +} + + +void Fl_Wayland_Graphics_Driver::rtl_draw(const char* str, int n, int x, int y) { + int w = (int)width(str, n); + draw(str, n, x - w, y); +} + + +double Fl_Wayland_Graphics_Driver::width(const char* c, int n) { + if (!font_descriptor()) return -1.0; + int i = 0, w = 0, l; + const char *end = c + n; + unsigned int ucs; + while (i < n) { + ucs = fl_utf8decode(c + i, end, &l); + i += l; + w += width(ucs); + } + return (double)w; +} + + +double Fl_Wayland_Graphics_Driver::width(unsigned int c) { + unsigned int r = 0; + Fl_Wayland_Font_Descriptor *desc = NULL; + if (c <= 0xFFFF) { // when inside basic multilingual plane + desc = (Fl_Wayland_Font_Descriptor*)font_descriptor(); + r = (c & 0xFC00) >> 10; + if (!desc->width) { + desc->width = (int**)new int*[64]; + memset(desc->width, 0, 64*sizeof(int*)); + } + if (!desc->width[r]) { + desc->width[r] = (int*)new int[0x0400]; + for (int i = 0; i < 0x0400; i++) desc->width[r][i] = -1; + } else { + if ( desc->width[r][c & 0x03FF] >= 0 ) { // already cached + return (double) desc->width[r][c & 0x03FF]; + } + } + } + char buf[4]; + int n = fl_utf8encode(c, buf); + pango_layout_set_text(pango_layout_, buf, n); + int W = 0, H; + pango_layout_get_pixel_size(pango_layout_, &W, &H); + if (c <= 0xFFFF) desc->width[r][c & 0x03FF] = W; + return (double)W; +} + + +void Fl_Wayland_Graphics_Driver::text_extents(const char* txt, int n, int& dx, int& dy, int& w, int& h) { + pango_layout_set_text(pango_layout_, txt, n); + PangoRectangle ink_rect; + pango_layout_get_pixel_extents(pango_layout_, &ink_rect, NULL); + dx = ink_rect.x; + dy = ink_rect.y - height() + descent(); + w = ink_rect.width; + h = ink_rect.height; +} + + +int Fl_Wayland_Graphics_Driver::not_clipped(int x, int y, int w, int h) { + if (!clip_) return 1; + if (clip_->w < 0) return 1; + int X = 0, Y = 0, W = 0, H = 0; + clip_box(x, y, w, h, X, Y, W, H); + if (W) return 1; + return 0; +} + +int Fl_Wayland_Graphics_Driver::clip_box(int x, int y, int w, int h, int &X, int &Y, int &W, int &H) { + if (!clip_) { + X = x; Y = y; W = w; H = h; + return 0; + } + if (clip_->w < 0) { + X = x; Y = y; W = w; H = h; + return 1; + } + int ret = 0; + if (x > (X=clip_->x)) {X=x; ret=1;} + if (y > (Y=clip_->y)) {Y=y; ret=1;} + if ((x+w) < (clip_->x+clip_->w)) { + W=x+w-X; + + ret=1; + + }else + W = clip_->x + clip_->w - X; + if(W<0){ + W=0; + return 1; + } + if ((y+h) < (clip_->y+clip_->h)) { + H=y+h-Y; + ret=1; + }else + H = clip_->y + clip_->h - Y; + if(H<0){ + W=0; + H=0; + return 1; + } + return ret; +} + +void Fl_Wayland_Graphics_Driver::restore_clip() { + if (cairo_) cairo_reset_clip(cairo_); +} + +void Fl_Wayland_Graphics_Driver::clip_region(Fl_Region r) { + if (cairo_) { + cairo_reset_clip(cairo_); + if (r) { + for (int i = 0; i < r->count; i++) { + cairo_rectangle(cairo_, r->rects[i].x-0.5 , r->rects[i].y-0.5 , r->rects[i].width , r->rects[i].height); + } + cairo_clip(cairo_); + } + } +} + + +Fl_Region Fl_Wayland_Graphics_Driver::XRectangleRegion(int x, int y, int w, int h) { + Fl_Region R = (Fl_Region)malloc(sizeof(*R)); + R->count = 1; + R->rects = (cairo_rectangle_t *)malloc(sizeof(cairo_rectangle_t)); + R->rects->x=x, R->rects->y=y, R->rects->width=w; R->rects->height=h; + return R; +} + + +// r1 ⊂ r2 +static bool CairoRectContainsRect(cairo_rectangle_t *r1, cairo_rectangle_t *r2) { + return r1->x >= r2->x && r1->y >= r2->y && r1->x+r1->width <= r2->x+r2->width && + r1->y+r1->height <= r2->y+r2->height; +} + + +void Fl_Wayland_Graphics_Driver::add_rectangle_to_region(Fl_Region r, int X, int Y, int W, int H) { + cairo_rectangle_t arg = {double(X), double(Y), double(W), double(H)}; + int j; // don't add a rectangle totally inside the Fl_Region + for (j = 0; j < r->count; j++) { + if (CairoRectContainsRect(&arg, &(r->rects[j]))) break; + } + if (j >= r->count) { + r->rects = (cairo_rectangle_t*)realloc(r->rects, (++(r->count)) * sizeof(cairo_rectangle_t)); + r->rects[r->count - 1] = arg; + } +} + + +void Fl_Wayland_Graphics_Driver::XDestroyRegion(Fl_Region r) { + if (r) { + free(r->rects); + free(r); + } +} + + +void Fl_Wayland_Graphics_Driver::set_color(Fl_Color i, unsigned c) { + if (fl_cmap[i] != c) { + fl_cmap[i] = c; + } +} + + +void Fl_Wayland_Graphics_Driver::point(int x, int y) { + rectf(x, y, 1, 1); +} + + +void Fl_Wayland_Graphics_Driver::copy_offscreen(int x, int y, int w, int h, Fl_Offscreen osrc, int srcx, int srcy) { + // draw portion srcx,srcy,w,h of osrc to position x,y (top-left) of the graphics driver's surface + int height = osrc->data_size / osrc->stride; + cairo_matrix_t matrix; + cairo_get_matrix(cairo_, &matrix); + double s = matrix.xx; + cairo_save(cairo_); + cairo_rectangle(cairo_, x, y, w, h); + cairo_clip(cairo_); + cairo_surface_t *surf = cairo_image_surface_create_for_data(osrc->draw_buffer, Fl_Wayland_Graphics_Driver::cairo_format, osrc->width, height, osrc->stride); + cairo_pattern_t *pat = cairo_pattern_create_for_surface(surf); + cairo_set_source(cairo_, pat); + cairo_matrix_init_scale(&matrix, s, s); + cairo_matrix_translate(&matrix, -(x - srcx), -(y - srcy)); + cairo_pattern_set_matrix(pat, &matrix); + cairo_mask(cairo_, pat); + cairo_pattern_destroy(pat); + cairo_surface_destroy(surf); + cairo_restore(cairo_); +} + + +struct callback_data { + const uchar *data; + int D, LD; +}; + + +static void draw_image_cb(void *data, int x, int y, int w, uchar *buf) { + struct callback_data *cb_data; + const uchar *curdata; + + cb_data = (struct callback_data*)data; + int last = x+w; + const size_t aD = abs(cb_data->D); + curdata = cb_data->data + x*cb_data->D + y*cb_data->LD; + for (; x<last; x++) { + memcpy(buf, curdata, aD); + buf += aD; + curdata += cb_data->D; + } +} + + +void Fl_Wayland_Graphics_Driver::draw_image(const uchar *data, int ix, int iy, int iw, int ih, int D, int LD) { + if (abs(D)<3){ //mono + draw_image_mono(data, ix, iy, iw, ih, D, LD); + return; + } + struct callback_data cb_data; + if (!LD) LD = iw*abs(D); + if (D<0) data += iw*abs(D); + cb_data.data = data; + cb_data.D = D; + cb_data.LD = LD; + Fl_Cairo_Graphics_Driver::draw_image(draw_image_cb, &cb_data, ix, iy, iw, ih, abs(D)); +} + + +void Fl_Wayland_Graphics_Driver::curve(double x, double y, double x1, double y1, double x2, double y2, double x3, double y3) { + if (shape_ == POINTS) Fl_Graphics_Driver::curve(x, y, x1, y1, x2, y2, x3, y3); + else Fl_Cairo_Graphics_Driver::curve(x, y, x1, y1, x2, y2, x3, y3); +} + + +void Fl_Wayland_Graphics_Driver::begin_points() { + cairo_save(cairo_); + gap_=1; + shape_=POINTS; +} + + +void Fl_Wayland_Graphics_Driver::end_points() { + cairo_restore(cairo_); +} + + +void Fl_Wayland_Graphics_Driver::transformed_vertex(double x, double y) { + if (shape_ == POINTS){ + cairo_move_to(cairo_, x, y); + point(x, y); + gap_ = 1; + } else { + Fl_Cairo_Graphics_Driver::transformed_vertex(x, y); + } +} + +void Fl_Wayland_Graphics_Driver::line_style(int style, int width, char* dashes) { + linestyle_ = style; + Fl_Cairo_Graphics_Driver::line_style(style, width, dashes); +} + +void Fl_Wayland_Graphics_Driver::overlay_rect(int x, int y, int w , int h) { + cairo_save(cairo_); + cairo_matrix_t mat; + cairo_get_matrix(cairo_, &mat); + float s = (float)mat.xx; + cairo_matrix_init_identity(&mat); + cairo_set_matrix(cairo_, &mat); // use drawing units + int lwidth = s < 1 ? 1 : int(s); + cairo_set_line_width(cairo_, lwidth); + cairo_translate(cairo_, lwidth/2., lwidth/2.); // translate by half of line width + double ddash = (lwidth > 2 ? lwidth : 2); + if (linestyle_ == FL_DOT){ + cairo_set_dash(cairo_, &ddash, 1, 0); // dash size = line width + } + // rectangle in drawing units + int Xs = Fl_Scalable_Graphics_Driver::floor(x, s); + int Ws = Fl_Scalable_Graphics_Driver::floor(x+w-1, s) - Xs; + int Ys = Fl_Scalable_Graphics_Driver::floor(y, s); + int Hs = Fl_Scalable_Graphics_Driver::floor(y+h-1, s) - Ys; + cairo_move_to(cairo_, Xs, Ys); + cairo_line_to(cairo_, Xs+Ws, Ys); + cairo_line_to(cairo_, Xs+Ws, Ys+Hs); + cairo_line_to(cairo_, Xs, Ys+Hs); + cairo_close_path(cairo_); + cairo_stroke(cairo_); + cairo_restore(cairo_); + buffer_->draw_buffer_needs_commit = true; +} + + +void Fl_Wayland_Graphics_Driver::draw_cached_pattern_(Fl_Image *img, cairo_pattern_t *pat, int X, int Y, int W, int H, int cx, int cy) { + // compute size of output image in drawing units + cairo_matrix_t matrix; + cairo_get_matrix(cairo_, &matrix); + float s = (float)matrix.xx; + int Xs = Fl_Scalable_Graphics_Driver::floor(X - cx, s); + int Ws = Fl_Scalable_Graphics_Driver::floor(X - cx + img->w(), s) - Xs ; + int Ys = Fl_Scalable_Graphics_Driver::floor(Y - cy, s); + int Hs = Fl_Scalable_Graphics_Driver::floor(Y - cy + img->h(), s) - Ys; + if (Ws == 0 || Hs == 0) return; + cairo_save(cairo_); + if (cx || cy || W < img->w() || H < img->h()) { // clip when necessary + cairo_rectangle(cairo_, X-0.5, Y-0.5, W+1, H+1); + cairo_clip(cairo_); + } + // remove any scaling and the current "0.5" translation useful for lines but bad for images + matrix.xx = matrix.yy = 1; + matrix.x0 -= 0.5 * s; matrix.y0 -= 0.5 * s; + cairo_set_matrix(cairo_, &matrix); + if (img->d() >= 1) cairo_set_source(cairo_, pat); + int offset = 0; + if (Ws >= img->data_w()*1.09 || Hs >= img->data_h()*1.09) { + // When enlarging while drawing, 1 pixel around target area seems unpainted, + // so we increase a bit the target area and move it int(s) pixels to left and top. + Ws = (img->w()+2)*s, Hs = (img->h()+2)*s; + offset = int(s); + } + +//fprintf(stderr,"WHs=%dx%d dataWH=%dx%d s=%.1f offset=%d\n",Ws,Hs,img->data_w(),img->data_h(),s,offset); + cairo_matrix_init_scale(&matrix, double(img->data_w())/Ws, double(img->data_h())/Hs); + cairo_matrix_translate(&matrix, -Xs + offset, -Ys + offset); + cairo_pattern_set_matrix(pat, &matrix); + cairo_mask(cairo_, pat); + cairo_restore(cairo_); + buffer_->draw_buffer_needs_commit = true; +} + + +void Fl_Wayland_Graphics_Driver::draw_rgb(Fl_RGB_Image *rgb,int XP, int YP, int WP, int HP, int cx, int cy) { + int X, Y, W, H; + // Don't draw an empty image... + if (!rgb->d() || !rgb->array) { + Fl_Graphics_Driver::draw_empty(rgb, XP, YP); + return; + } + if (start_image(rgb, XP, YP, WP, HP, cx, cy, X, Y, W, H)) { + return; + } + cairo_pattern_t *pat = (cairo_pattern_t*)*Fl_Graphics_Driver::id(rgb); + if (!pat) { + cache(rgb); + pat = (cairo_pattern_t*)*Fl_Graphics_Driver::id(rgb); + } + draw_cached_pattern_(rgb, pat, X, Y, W, H, cx, cy); +} + + +static cairo_user_data_key_t data_key_for_surface = {}; + +static void dealloc_surface_data(void *data) { + delete[] (uchar*)data; +} + + +void Fl_Wayland_Graphics_Driver::cache(Fl_RGB_Image *rgb) { + int stride = cairo_format_stride_for_width(Fl_Wayland_Graphics_Driver::cairo_format, rgb->data_w()); + uchar *BGRA = new uchar[stride * rgb->data_h()]; + memset(BGRA, 0, stride * rgb->data_h()); + int lrgb = rgb->ld() ? rgb->ld() : rgb->data_w() * rgb->d(); + uchar A = 0xff, R,G,B, *q; + const uchar *r; + float f = 1; + if (rgb->d() >= 3) { // color images + for (int j = 0; j < rgb->data_h(); j++) { + r = rgb->array + j * lrgb; + q = BGRA + j * stride; + for (int i = 0; i < rgb->data_w(); i++) { + R = *r; + G = *(r+1); + B = *(r+2); + if (rgb->d() == 4) { + A = *(r+3); + f = float(A)/0xff; + } + *q = B * f; + *(q+1) = G * f; + *(q+2) = R * f; + *(q+3) = A; + r += rgb->d(); q += 4; + } + } + } else if (rgb->d() == 1 || rgb->d() == 2) { // B&W + for (int j = 0; j < rgb->data_h(); j++) { + r = rgb->array + j * lrgb; + q = BGRA + j * stride; + for (int i = 0; i < rgb->data_w(); i++) { + G = *r; + if (rgb->d() == 2) { + A = *(r+1); + f = float(A)/0xff; + } + *(q) = G * f; + *(q+1) = G * f; + *(q+2) = G * f; + *(q+3) = A; + r += rgb->d(); q += 4; + } + } + } + cairo_surface_t *surf = cairo_image_surface_create_for_data(BGRA, Fl_Wayland_Graphics_Driver::cairo_format, rgb->data_w(), rgb->data_h(), stride); + if (cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS) return; + (void)cairo_surface_set_user_data(surf, &data_key_for_surface, BGRA, dealloc_surface_data); + cairo_pattern_t *pat = cairo_pattern_create_for_surface(surf); + *Fl_Graphics_Driver::id(rgb) = (fl_uintptr_t)pat; +} + + +void Fl_Wayland_Graphics_Driver::uncache(Fl_RGB_Image *img, fl_uintptr_t &id_, fl_uintptr_t &mask_) { + cairo_pattern_t *pat = (cairo_pattern_t*)id_; + if (pat) { + cairo_surface_t *surf; + cairo_pattern_get_surface(pat, &surf); + cairo_pattern_destroy(pat); + cairo_surface_destroy(surf); + id_ = 0; + } +} + + +void Fl_Wayland_Graphics_Driver::draw_bitmap(Fl_Bitmap *bm,int XP, int YP, int WP, int HP, int cx, int cy) { + int X, Y, W, H; + if (!bm->array) { + draw_empty(bm, XP, YP); + return; + } + if (start_image(bm, XP,YP,WP,HP,cx,cy,X,Y,W,H)) return; + cairo_pattern_t *pat = (cairo_pattern_t*)*Fl_Graphics_Driver::id(bm); + if (!pat) { + cache(bm); + pat = (cairo_pattern_t*)*Fl_Graphics_Driver::id(bm); + } + if (pat) { + draw_cached_pattern_(bm, pat, X, Y, W, H, cx, cy); + } +} + + +void Fl_Wayland_Graphics_Driver::cache(Fl_Bitmap *bm) { + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_A1, bm->data_w()); + uchar *BGRA = new uchar[stride * bm->data_h()]; + memset(BGRA, 0, stride * bm->data_h()); + uchar *r, p; + unsigned *q; + for (int j = 0; j < bm->data_h(); j++) { + r = (uchar*)bm->array + j * ((bm->data_w() + 7)/8); + q = (unsigned*)(BGRA + j * stride); + unsigned k = 0, mask32 = 1; + p = *r; + for (int i = 0; i < bm->data_w(); i++) { + if (p&1) (*q) |= mask32; + k++; + if (k % 8 != 0) p >>= 1; else p = *(++r); + if (k % 32 != 0) mask32 <<= 1; else {q++; mask32 = 1;} + } + } + cairo_surface_t *surf = cairo_image_surface_create_for_data(BGRA, CAIRO_FORMAT_A1, bm->data_w(), bm->data_h(), stride); + if (cairo_surface_status(surf) == CAIRO_STATUS_SUCCESS) { + (void)cairo_surface_set_user_data(surf, &data_key_for_surface, BGRA, dealloc_surface_data); + cairo_pattern_t *pat = cairo_pattern_create_for_surface(surf); + *Fl_Graphics_Driver::id(bm) = (fl_uintptr_t)pat; + } +} + + +void Fl_Wayland_Graphics_Driver::delete_bitmask(Fl_Bitmask bm) { + cairo_pattern_t *pat = (cairo_pattern_t*)bm; + if (pat) { + cairo_surface_t *surf; + cairo_pattern_get_surface(pat, &surf); + cairo_pattern_destroy(pat); + cairo_surface_destroy(surf); + } +} + + +void Fl_Wayland_Graphics_Driver::draw_pixmap(Fl_Pixmap *pxm,int XP, int YP, int WP, int HP, int cx, int cy) { + int X, Y, W, H; + // Don't draw an empty image... + if (!pxm->data() || !pxm->w()) { + Fl_Graphics_Driver::draw_empty(pxm, XP, YP); + return; + } + if (start_image(pxm, XP, YP, WP, HP, cx, cy, X, Y, W, H)) { + return; + } + cairo_pattern_t *pat = (cairo_pattern_t*)*Fl_Graphics_Driver::id(pxm); + if (!pat) { + cache(pxm); + pat = (cairo_pattern_t*)*Fl_Graphics_Driver::id(pxm); + } + draw_cached_pattern_(pxm, pat, X, Y, W, H, cx, cy); +} + + +void Fl_Wayland_Graphics_Driver::cache(Fl_Pixmap *pxm) { + Fl_RGB_Image *rgb = new Fl_RGB_Image(pxm); + cache(rgb); + *Fl_Graphics_Driver::id(pxm) = *Fl_Graphics_Driver::id(rgb); + *Fl_Graphics_Driver::id(rgb) = 0; + delete rgb; +} + + +void Fl_Wayland_Graphics_Driver::uncache_pixmap(fl_uintptr_t p) { + cairo_pattern_t *pat = (cairo_pattern_t*)p; + if (pat) { + cairo_surface_t *surf; + cairo_pattern_get_surface(pat, &surf); + cairo_pattern_destroy(pat); + cairo_surface_destroy(surf); + } +} + + +void Fl_Wayland_Graphics_Driver::set_spot(int font, int height, int x, int y, int w, int h, Fl_Window *win) { + Fl_Wayland_Screen_Driver::insertion_point_location(x, y, height); +} + + +void Fl_Wayland_Graphics_Driver::reset_spot() { + Fl::compose_state = 0; + Fl_Wayland_Screen_Driver::next_marked_length = 0; + Fl_Wayland_Screen_Driver::insertion_point_location_is_valid = false; +} + + +void Fl_Wayland_Graphics_Driver::line(int x1, int y1, int x2, int y2) { + Fl_Cairo_Graphics_Driver::line(x1, y1, x2, y2); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::line(int x1, int y1, int x2, int y2, int x3, int y3) { + Fl_Cairo_Graphics_Driver::line(x1, y1, x2, y2, x3, y3); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::xyline(int x, int y, int x1) { + Fl_Cairo_Graphics_Driver::xyline(x, y, x1); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::xyline(int x, int y, int x1, int y2) { + Fl_Cairo_Graphics_Driver::xyline(x, y, x1, y2); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::xyline(int x, int y, int x1, int y2, int x3) { + Fl_Cairo_Graphics_Driver::xyline(x, y, x1, y2, x3); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::yxline(int x, int y, int y1) { + Fl_Cairo_Graphics_Driver::yxline(x, y, y1); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::yxline(int x, int y, int y1, int x2) { + Fl_Cairo_Graphics_Driver::yxline(x, y, y1, x2); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::yxline(int x, int y, int y1, int x2, int y3) { + Fl_Cairo_Graphics_Driver::yxline(x, y, y1, x2, y3); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::loop(int x0, int y0, int x1, int y1, int x2, int y2) { + Fl_Cairo_Graphics_Driver::loop(x0, y0, x1, y1, x2, y2); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::loop(int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3) { + Fl_Cairo_Graphics_Driver::loop(x0, y0, x1, y1, x2, y2, x3, y3); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::rectf(int x, int y, int w, int h) { + Fl_Cairo_Graphics_Driver::rectf(x, y, w, h); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::rect(int x, int y, int w, int h) { + Fl_Cairo_Graphics_Driver::rect(x, y, w, h); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::polygon(int x0, int y0, int x1, int y1, int x2, int y2) { + Fl_Cairo_Graphics_Driver::polygon(x0, y0, x1, y1, x2, y2); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::polygon(int x0, int y0, int x1, int y1, int x2, int y2, int x3, int y3) { + Fl_Cairo_Graphics_Driver::polygon(x0, y0, x1, y1, x2, y2, x3, y3); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::end_line() { + Fl_Cairo_Graphics_Driver::end_line(); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::end_loop(){ + Fl_Cairo_Graphics_Driver::end_loop(); + buffer_->draw_buffer_needs_commit = true; +} + +void Fl_Wayland_Graphics_Driver::end_polygon() { + Fl_Cairo_Graphics_Driver::end_polygon(); + buffer_->draw_buffer_needs_commit = true; +} diff --git a/src/drivers/Wayland/Fl_Wayland_Image_Surface_Driver.cxx b/src/drivers/Wayland/Fl_Wayland_Image_Surface_Driver.cxx new file mode 100644 index 000000000..d79ad0f2e --- /dev/null +++ b/src/drivers/Wayland/Fl_Wayland_Image_Surface_Driver.cxx @@ -0,0 +1,107 @@ +// +// Draw-to-image code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2021 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_Graphics_Driver.H" +#include "Fl_Wayland_Window_Driver.H" +#include <FL/Fl_Image_Surface.H> + +class Fl_Wayland_Image_Surface_Driver : public Fl_Image_Surface_Driver { + virtual void end_current(); +public: + Fl_Wayland_Image_Surface_Driver(int w, int h, int high_res, Fl_Offscreen off); + ~Fl_Wayland_Image_Surface_Driver(); + void set_current(); + void translate(int x, int y); + void untranslate(); + Fl_RGB_Image *image(); +}; + +Fl_Image_Surface_Driver *Fl_Image_Surface_Driver::newImageSurfaceDriver(int w, int h, int high_res, Fl_Offscreen off) +{ + return new Fl_Wayland_Image_Surface_Driver(w, h, high_res, off); +} + +Fl_Wayland_Image_Surface_Driver::Fl_Wayland_Image_Surface_Driver(int w, int h, int high_res, Fl_Offscreen off) : Fl_Image_Surface_Driver(w, h, high_res, off) { + float d = 1; + if (!off) { + fl_open_display(); + if (fl_window) { + d = fl_window->scale; + } + d *= fl_graphics_driver->scale(); + if (d != 1 && high_res) { + w = int(w*d); + h = int(h*d); + } + offscreen = (struct fl_wld_buffer*)calloc(1, sizeof(struct fl_wld_buffer)); + offscreen->stride = cairo_format_stride_for_width(CAIRO_FORMAT_RGB24, w); + offscreen->data_size = offscreen->stride * h; + offscreen->draw_buffer = (uchar*)malloc(offscreen->data_size); + offscreen->width = w; + Fl_Wayland_Graphics_Driver::cairo_init(offscreen, w, h, offscreen->stride, CAIRO_FORMAT_RGB24); + } + driver(new Fl_Wayland_Graphics_Driver()); + if (d != 1 && high_res) driver()->scale(d); +} + + +Fl_Wayland_Image_Surface_Driver::~Fl_Wayland_Image_Surface_Driver() { + if (offscreen && !external_offscreen) { + free(offscreen->draw_buffer); + free(offscreen); + } + delete driver(); +} + +void Fl_Wayland_Image_Surface_Driver::set_current() { + Fl_Surface_Device::set_current(); + ((Fl_Wayland_Graphics_Driver*)fl_graphics_driver)->activate(offscreen, driver()->scale()); +} + +void Fl_Wayland_Image_Surface_Driver::end_current() { + cairo_surface_t *surf = cairo_get_target(offscreen->cairo_); + cairo_surface_flush(surf); +} + +void Fl_Wayland_Image_Surface_Driver::translate(int x, int y) { + ((Fl_Wayland_Graphics_Driver*)driver())->ps_translate(x, y); +} + +void Fl_Wayland_Image_Surface_Driver::untranslate() { + ((Fl_Wayland_Graphics_Driver*)driver())->ps_untranslate(); +} + +Fl_RGB_Image* Fl_Wayland_Image_Surface_Driver::image() { + // Convert depth-4 image in draw_buffer to a depth-3 image while exchanging R and B colors + int height = offscreen->data_size / offscreen->stride; + uchar *rgb = new uchar[offscreen->width * height * 3]; + uchar *p = rgb; + uchar *q; + for (int j = 0; j < height; j++) { + q = offscreen->draw_buffer + j*offscreen->stride; + for (int i = 0; i < offscreen->width; i++) { // exchange R and B colors, transmit G + *p = *(q+2); + *(p+1) = *(q+1); + *(p+2) = *q; + p += 3; q += 4; + } + } + Fl_RGB_Image *image = new Fl_RGB_Image(rgb, offscreen->width, height, 3); + image->alloc_array = 1; + return image; +} diff --git a/src/drivers/Wayland/Fl_Wayland_Screen_Driver.H b/src/drivers/Wayland/Fl_Wayland_Screen_Driver.H new file mode 100644 index 000000000..ce10884c5 --- /dev/null +++ b/src/drivers/Wayland/Fl_Wayland_Screen_Driver.H @@ -0,0 +1,175 @@ +// +// Definition of X11 Screen interface +// for the Fast Light Tool Kit (FLTK). +// +// Copyright 2010-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 +// + +/** + \file Fl_Wayland_Screen_Driver.H + \brief Definition of Wayland Screen interface + */ + +#ifndef FL_WAYLAND_SCREEN_DRIVER_H +#define FL_WAYLAND_SCREEN_DRIVER_H + +#include "../../Fl_Screen_Driver.H" +#include <wayland-client.h> + +class Fl_Window; + +struct seat { + struct wl_seat *wl_seat; + struct wl_pointer *wl_pointer; + struct wl_keyboard *wl_keyboard; + uint32_t keyboard_enter_serial; + struct wl_surface *keyboard_surface; + struct wl_list link; + struct wl_list pointer_outputs; + struct wl_cursor_theme *cursor_theme; + struct wl_cursor *default_cursor; + struct wl_surface *cursor_surface; + struct wl_surface *pointer_focus; + int pointer_scale; + uint32_t serial; + struct wl_data_device_manager *data_device_manager; + struct wl_data_device *data_device; + struct wl_data_source *data_source; + struct xkb_state *xkb_state; + struct xkb_context *xkb_context; + struct xkb_keymap *xkb_keymap; + struct xkb_compose_state *xkb_compose_state; + char *name; + struct zwp_text_input_v3 *text_input; +}; + +class FL_EXPORT Fl_Wayland_Screen_Driver : public Fl_Screen_Driver +{ + friend class Fl_Screen_Driver; + friend class Fl_Wayland_Graphics_Driver; + static int insertion_point_x; + static int insertion_point_y; + static int insertion_point_width; + static int insertion_point_height; + static bool insertion_point_location_is_valid; +public: + static void insertion_point_location(int x, int y, int height); + static bool insertion_point_location(int *px, int *py, int *pwidth, int *pheight); + int get_mouse_unscaled(int &xx, int &yy); + void screen_count(int count) {num_screens = count;} + + void reset_cursor(); + struct wl_cursor *xc_arrow; + struct wl_cursor *xc_ns; + struct wl_cursor *xc_wait; + struct wl_cursor *xc_insert; + struct wl_cursor *xc_hand; + struct wl_cursor *xc_help; + struct wl_cursor *xc_cross; + struct wl_cursor *xc_move; + struct wl_cursor *xc_north; + struct wl_cursor *xc_south; + struct wl_cursor *xc_west; + struct wl_cursor *xc_east; + struct wl_cursor *xc_we; + struct wl_cursor *xc_nesw; + struct wl_cursor *xc_nwse; + struct wl_cursor *xc_sw; + struct wl_cursor *xc_se; + struct wl_cursor *xc_ne; + struct wl_cursor *xc_nw; + static const struct wl_data_device_listener *p_data_device_listener; + +public: + struct wl_compositor *wl_compositor; + struct wl_subcompositor *wl_subcompositor; + struct wl_shm *wl_shm; + struct wl_list seats; + struct seat *seat; + struct wl_list outputs; // linked list of all screens in system + struct output { // one record for each screen + uint32_t id; + short x_org; + short y_org; + short width; // in pixels + short height; // in pixels + float dpi; + struct wl_output *wl_output; + int wld_scale; // Wayland scale factor + float gui_scale; // FLTK scale factor + struct wl_list link; + }; + struct libdecor *libdecor_context; + struct xdg_wm_base *xdg_wm_base; + struct zwp_text_input_manager_v3 *text_input_base; + + Fl_Wayland_Screen_Driver(); + virtual APP_SCALING_CAPABILITY rescalable() { return PER_SCREEN_APP_SCALING; } + virtual float scale(int n); + virtual void scale(int n, float f); + int screen_num_unscaled(int x, int y); + + void copy_image(const unsigned char* data, int W, int H); + // --- screen configuration + void init_workarea(); + virtual void init(); + virtual int x(); + virtual int y(); + virtual int w(); + virtual int h(); + virtual void screen_xywh(int &X, int &Y, int &W, int &H, int n); + virtual void screen_dpi(float &h, float &v, int n=0); + virtual void screen_work_area(int &X, int &Y, int &W, int &H, int n); + // --- audible output + virtual void beep(int type); + // --- global events + virtual void flush(); + virtual void grab(Fl_Window* win); + // --- global colors + virtual void get_system_colors(); + virtual const char *get_system_scheme(); + virtual int dnd(int unused); + virtual int compose(int &del); + virtual void compose_reset(); + virtual Fl_RGB_Image *read_win_rectangle(int X, int Y, int w, int h, Fl_Window *win, bool may_capture_subwins, bool *did_capture_subwins); + virtual int get_mouse(int &x, int &y); + virtual void open_display_platform(); + virtual void close_display(); + // --- compute dimensions of an Fl_Offscreen + virtual void offscreen_size(Fl_Offscreen o, int &width, int &height); + virtual int has_marked_text() const; + static int next_marked_length; // next length of marked text after current marked text will have been replaced + // --- clipboard operations + // this one is in Fl_wayland.cxx + virtual void copy(const char *stuff, int len, int clipboard, const char *type); + // this one is in Fl_wayland.cxx + virtual void paste(Fl_Widget &receiver, int clipboard, const char *type); + // this one is in Fl_wayland.cxx + virtual int clipboard_contains(const char *type); + // --- Wayland-special + void set_cursor(); + struct wl_cursor *default_cursor(); + void default_cursor(struct wl_cursor *cursor); + struct wl_cursor *cache_cursor(const char *cursor_name); + static Fl_Window *surface_to_window(struct wl_surface *); + uint32_t get_serial(); + struct wl_seat *get_wl_seat(); + char *get_seat_name(); + struct xkb_keymap *get_xkb_keymap(); + static bool own_output(struct wl_output *output); + typedef enum {unspecified, MUTTER, WESTON, KDE} compositor_name; + static compositor_name compositor; // identifies the used Wayland compositor +}; + + +#endif // FL_WAYLAND_SCREEN_DRIVER_H diff --git a/src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx b/src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx new file mode 100644 index 000000000..71a14ca22 --- /dev/null +++ b/src/drivers/Wayland/Fl_Wayland_Screen_Driver.cxx @@ -0,0 +1,1428 @@ +// +// Implementation of Wayland Screen interface +// +// 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_Wayland_Screen_Driver.H" +#include "Fl_Wayland_Window_Driver.H" +#include "Fl_Wayland_System_Driver.H" +#include "Fl_Wayland_Graphics_Driver.H" +#include <wayland-cursor.h> +#include "../../../libdecor/src/libdecor.h" +#include "xdg-shell-client-protocol.h" +#include "../Posix/Fl_Posix_System_Driver.H" +#include <FL/Fl.H> +#include <FL/platform.H> +#include <FL/fl_ask.H> +#include <FL/Fl_Box.H> +#include <FL/Fl_Tooltip.H> +#include <FL/filename.H> +#include "../../print_button.h" +#include <dlfcn.h> +#include <sys/time.h> +#include <linux/input.h> +#include <stdlib.h> +#include <xkbcommon/xkbcommon.h> +#include <xkbcommon/xkbcommon-compose.h> +#include "text-input-client-protocol.h" +#include <assert.h> +#include <sys/mman.h> +extern "C" { + bool libdecor_get_cursor_settings(char **theme, int *size); +} + + +#define fl_max(a,b) ((a) > (b) ? (a) : (b)) + +struct pointer_output { + Fl_Wayland_Screen_Driver::output* output; + struct wl_list link; +}; + +/* Implementation note about support of 3 Wayland compositors: Mutter, Weston, KDE. + +- About CSD and SSD : + * Mutter and Weston use CSD (client-side decoration) which means that libdecor.so draws all window + titlebars and responds to resize, minimization and maximization events. + * KDE uses SSD (server-side decoration) which means the OS draws titlebars according to its own rules + and triggers resize, minimization and maximization events. + +- Function registry_handle_global() runs within fl_open_display() and sets public static variable + Fl_Wayland_Screen_Driver::compositor to either Fl_Wayland_Screen_Driver::MUTTER, ::WESTON, or ::KDE. + +- Specific operations for WESTON: + * When a libdecor-framed window is minimized under Weston, the frame remains on display. To avoid + that, function libdecor_frame_set_minimized() is modified so it turns off the frame's visibility, with + function libdecor_frame_set_visibility(), when the window is minimized. That's implemented in file + libdecor/build/fl_libdecor.c. The modified libdecor_frame_set_minimized() function, part of libdecor.so, + needs access to variable Fl_Wayland_Screen_Driver::compositor, part of libfltk.a. This is achieved + calling FLTK function fl_libdecor_using_weston() which returns whether the running compositor + is Weston. This Weston bug has been corrected in Weston version 10. Thus, this special processing + is not performed when Weston version is ≥ 10. + +- Synchronization between drawing to buffer and committing buffer to screen. + Before committing a new graphics scene for display, Wayland requires to make sure the compositor is + ready for commit. FLTK uses frame callbacks for that. + A frame callback is created when an app calls Fl_Wayland_Window_Driver::make_current() + directly. This directs a callback listener function, called surface_frame_done, to be called by the + compositor when it's ready to commit a new graphics scene. This function schedules a new frame callback + and commits the buffer to the display. + A frame callback is also created by Fl_Wayland_Window_Driver::flush() when a window redraw operation + is needed. FLTK processes wayland events until the compositor is ready for commit and then commits + the new window content. + + - Support of Fl_Window::border(int) : + FLTK uses libdecor_frame_set_visibility() to show or hide a toplevel window's frame. This doesn't work + with KDE which uses Server-Side Decoration. In that case, FLTK hides and re-shows the window to toggle + between presence and absence of a window's frame. +*/ + + +/* Implementation note about screen-related information + + struct wl_output : Wayland-defined, contains info about a screen, one such record for each screen + + struct Fl_Wayland_Screen_Driver::output { // FLTK defined + uint32_t id; // screen identification + short x_org; + short y_org; + short width; // screen width in pixels + short height; // screen height in pixels + float dpi; + struct wl_output *wl_output; + int wld_scale; // Wayland scale + float gui_scale; // user-set scale + struct wl_list link; + }; + + struct Fl_Wayland_Window_Driver::window_output { // FLTK defined + Fl_Wayland_Screen_Driver::output* output; + struct wl_list link; + } + + The unique Fl_Wayland_Screen_Driver object contains a member + "outputs" of type struct wl_list = list of Fl_Wayland_Screen_Driver::output records + - this list is initialised by open-display + - registry_handle_global() feeds the list with 1 record for each screen + - registry_handle_global_remove() runs when a screen is removed. It removes + the output record that corresponds to that screen from the unique list of screens + (outputs member of the Fl_Wayland_Screen_Driver) and the list of struct output objects attached + to each window. + + Each Fl_Wayland_Window_Driver object contains a member + "outputs" of type struct wl_list = list of Fl_Wayland_Window_Driver::window_output records + - this list is fed by surface_enter() (when a surface is mapped) + - these records contain: + window_output->output = (Fl_Wayland_Screen_Driver::output*)wl_output_get_user_data(wl_output); + where wl_output is received from OS by surface_enter() + - surface_leave() removes the adequate record from the list + - hide() empties the list + - Fl_Wayland_Window_Driver::update_scale() sets the scale info of the records for a given window + */ + +Fl_Wayland_Screen_Driver::compositor_name Fl_Wayland_Screen_Driver::compositor = Fl_Wayland_Screen_Driver::unspecified; + +extern "C" { + FL_EXPORT bool fl_libdecor_using_weston(void) { + return Fl_Wayland_Screen_Driver::compositor == Fl_Wayland_Screen_Driver::WESTON; + }; +} + +static void xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial) +{ + xdg_wm_base_pong(xdg_wm_base, serial); +} + +static const struct xdg_wm_base_listener xdg_wm_base_listener = { + .ping = xdg_wm_base_ping, +}; + + +// these are set by Fl::args() and override any system colors: from Fl_get_system_colors.cxx +extern const char *fl_fg; +extern const char *fl_bg; +extern const char *fl_bg2; +// end of extern additions workaround + + +/** + Creates a driver that manages all screen and display related calls. + + This function must be implemented once for every platform. + */ +Fl_Screen_Driver *Fl_Screen_Driver::newScreenDriver() +{ + return new Fl_Wayland_Screen_Driver(); +} + +FL_EXPORT struct wl_display *fl_display = NULL; + +static bool has_xrgb = false; + + +static void shm_format(void *data, struct wl_shm *wl_shm, uint32_t format) +{ + if (format == Fl_Wayland_Graphics_Driver::wld_format) + has_xrgb = true; +} + +static struct wl_shm_listener shm_listener = { + shm_format +}; + +static void do_set_cursor(struct seat *seat, struct wl_cursor *wl_cursor = NULL) +{ + struct wl_cursor_image *image; + struct wl_buffer *buffer; + const int scale = seat->pointer_scale; + + if (!seat->cursor_theme) + return; + + if (!wl_cursor) wl_cursor = seat->default_cursor; + image = wl_cursor->images[0]; + buffer = wl_cursor_image_get_buffer(image); + wl_pointer_set_cursor(seat->wl_pointer, seat->serial, + seat->cursor_surface, + image->hotspot_x / scale, + image->hotspot_y / scale); + wl_surface_attach(seat->cursor_surface, buffer, 0, 0); + wl_surface_set_buffer_scale(seat->cursor_surface, scale); + wl_surface_damage_buffer(seat->cursor_surface, 0, 0, + image->width, image->height); + wl_surface_commit(seat->cursor_surface); +} + +static uint32_t ptime; +FL_EXPORT uint32_t fl_event_time; +static int px, py; + + +static void set_event_xy(Fl_Window *win) { + // turn off is_click if enough time or mouse movement has passed: + if (abs(Fl::e_x_root-px)+abs(Fl::e_y_root-py) > 3 || + fl_event_time >= ptime+1000) { + Fl::e_is_click = 0; +//fprintf(stderr, "Fl::e_is_click = 0\n"); + } +} + +// if this is same event as last && is_click, increment click count: +static inline void checkdouble() { + if (Fl::e_is_click == Fl::e_keysym) { + Fl::e_clicks++; +//fprintf(stderr, "Fl::e_clicks = %d\n", Fl::e_clicks); + } else { + Fl::e_clicks = 0; + Fl::e_is_click = Fl::e_keysym; +//fprintf(stderr, "Fl::e_is_click = %d\n", Fl::e_is_click); + } + px = Fl::e_x_root; + py = Fl::e_y_root; + ptime = fl_event_time; +} + + +Fl_Window *Fl_Wayland_Screen_Driver::surface_to_window(struct wl_surface *surface) { + Fl_X *xp = Fl_X::first; + while (xp) { + if (xp->xid->wl_surface == surface) return xp->w; + xp = xp->next; + } + return NULL; +} + + +static void pointer_enter(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + struct wl_surface *surface, + wl_fixed_t surface_x, + wl_fixed_t surface_y) +{ + struct seat *seat = (struct seat*)data; + Fl_Window *win = Fl_Wayland_Screen_Driver::surface_to_window(surface); + struct wl_cursor *cursor = NULL; + if (win) { // use custom cursor if present + Fl_Wayland_Window_Driver *driver = Fl_Wayland_Window_Driver::driver(win); + cursor = driver->cursor(); + if (win->parent() && !cursor) { + driver = Fl_Wayland_Window_Driver::driver(win->top_window()); + cursor = driver->cursor(); + } + } + do_set_cursor(seat, cursor); + seat->serial = serial; + if (win) { + float f = Fl::screen_scale(win->screen_num()); + Fl::e_x = wl_fixed_to_int(surface_x) / f; + Fl::e_x_root = Fl::e_x + win->x(); + Fl::e_y = wl_fixed_to_int(surface_y) / f; + Fl::e_y_root = Fl::e_y + win->y(); + set_event_xy(win); + Fl::handle(FL_ENTER, win); +//fprintf(stderr, "pointer_enter window=%p\n", win); + } + seat->pointer_focus = surface; +} + + +static void pointer_leave(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + struct wl_surface *surface) +{ + struct seat *seat = (struct seat*)data; + if (seat->pointer_focus == surface) seat->pointer_focus = NULL; + Fl_Window *win = Fl_Wayland_Screen_Driver::surface_to_window(surface); + if (win) { + Fl::belowmouse(0); + set_event_xy(win); + } +//fprintf(stderr, "pointer_leave surface=%p window=%p\n", surface, win); +} + + +static void pointer_motion(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + wl_fixed_t surface_x, + wl_fixed_t surface_y) +{ + struct seat *seat = (struct seat*)data; + Fl_Window *win = Fl_Wayland_Screen_Driver::surface_to_window(seat->pointer_focus); + if (!win) return; + float f = Fl::screen_scale(win->screen_num()); + Fl::e_x = wl_fixed_to_int(surface_x) / f; + Fl::e_x_root = Fl::e_x + win->x(); + // If there's an active grab() and the pointer is in a window other than the grab(), + // make e_x_root too large to be in any window + if (Fl::grab() && !Fl::grab()->menu_window() && Fl::grab() != win) { + Fl::e_x_root = 1000000; + } + Fl::e_y = wl_fixed_to_int(surface_y) / f; + Fl::e_y_root = Fl::e_y + win->y(); +//fprintf(stderr, "FL_MOVE on win=%p to x:%dx%d root:%dx%d\n", win, Fl::e_x, Fl::e_y, Fl::e_x_root, Fl::e_y_root); + fl_event_time = time; + set_event_xy(win); + Fl::handle(FL_MOVE, win); +} + + +//#include <FL/names.h> +static void pointer_button(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + uint32_t time, + uint32_t button, + uint32_t state) +{ + struct seat *seat = (struct seat*)data; + seat->serial = serial; + int event = 0; + Fl_Window *win = Fl_Wayland_Screen_Driver::surface_to_window(seat->pointer_focus); + if (!win) return; + fl_event_time = time; + if (button == BTN_LEFT && state == WL_POINTER_BUTTON_STATE_PRESSED && seat->pointer_focus == NULL && + fl_xid(win)->kind == Fl_Wayland_Window_Driver::DECORATED) { + // click on titlebar + libdecor_frame_move(fl_xid(win)->frame, seat->wl_seat, serial); + return; + } + int b = 0; + Fl::e_state = 0; + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (button == BTN_LEFT) {Fl::e_state = FL_BUTTON1; b = 1;} + else if (button == BTN_RIGHT) {Fl::e_state = FL_BUTTON3; b = 3;} + else if (button == BTN_MIDDLE) {Fl::e_state = FL_BUTTON2; b = 2;} + Fl::e_keysym = FL_Button + b; + } + Fl::e_dx = Fl::e_dy = 0; + + set_event_xy(win); + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + event = FL_PUSH; + checkdouble(); + } else if (state == WL_POINTER_BUTTON_STATE_RELEASED) event = FL_RELEASE; +//fprintf(stderr, "%s %s\n", fl_eventnames[event], win->label() ? win->label():"[]"); + Fl::handle(event, win); +} + +static void pointer_axis(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + uint32_t axis, + wl_fixed_t value) +{ + struct seat *seat = (struct seat*)data; + Fl_Window *win = Fl_Wayland_Screen_Driver::surface_to_window(seat->pointer_focus); + if (!win) return; + fl_event_time = time; + int delta = wl_fixed_to_int(value) / 10; +//fprintf(stderr, "FL_MOUSEWHEEL: %c delta=%d\n", axis==WL_POINTER_AXIS_HORIZONTAL_SCROLL?'H':'V', delta); + // allow both horizontal and vertical movements to be processed by the widget + if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL) { + Fl::e_dx = delta; + Fl::e_dy = 0; + Fl::handle(FL_MOUSEWHEEL, win->top_window()); + } + if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL) { + Fl::e_dx = 0; + Fl::e_dy = delta; + Fl::handle(FL_MOUSEWHEEL, win->top_window()); + } +} + +static struct wl_pointer_listener pointer_listener = { + pointer_enter, + pointer_leave, + pointer_motion, + pointer_button, + pointer_axis +}; + +static const char *proxy_tag = "FLTK for Wayland"; + +bool Fl_Wayland_Screen_Driver::own_output(struct wl_output *output) +{ + return wl_proxy_get_tag((struct wl_proxy *)output) == &proxy_tag; +} + +static void init_cursors(struct seat *seat); + +static void try_update_cursor(struct seat *seat) +{ + struct pointer_output *pointer_output; + int scale = 1; + + wl_list_for_each(pointer_output, &seat->pointer_outputs, link) { + scale = fl_max(scale, pointer_output->output->wld_scale); + } + + if (scale != seat->pointer_scale) { + seat->pointer_scale = scale; + init_cursors(seat); + do_set_cursor(seat); + } +} + + +static void cursor_surface_enter(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct seat *seat = (struct seat*)data; + struct pointer_output *pointer_output; + + if (!Fl_Wayland_Screen_Driver::own_output(wl_output)) + return; + + pointer_output = (struct pointer_output *)calloc(1, sizeof(struct pointer_output)); + pointer_output->output = (Fl_Wayland_Screen_Driver::output *)wl_output_get_user_data(wl_output); +//fprintf(stderr, "cursor_surface_enter: wl_output_get_user_data(%p)=%p\n", wl_output, pointer_output->output); + wl_list_insert(&seat->pointer_outputs, &pointer_output->link); + try_update_cursor(seat); + // maintain custom window cursor + Fl_Window *win = Fl::first_window(); + if (win) { + Fl_Wayland_Window_Driver *driver = Fl_Wayland_Window_Driver::driver(win); + struct wl_cursor *cursor = driver->cursor(); + if (cursor) do_set_cursor(seat, cursor); + } +} + +static void cursor_surface_leave(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct seat *seat = (struct seat*)data; + struct pointer_output *pointer_output, *tmp; + + wl_list_for_each_safe(pointer_output, tmp, &seat->pointer_outputs, link) { + if (pointer_output->output->wl_output == wl_output) { + wl_list_remove(&pointer_output->link); + free(pointer_output); + } + } +} + +static struct wl_surface_listener cursor_surface_listener = { + cursor_surface_enter, + cursor_surface_leave, +}; + + +static void init_cursors(struct seat *seat) +{ + char *name; + int size; + struct wl_cursor_theme *theme; + + if (!libdecor_get_cursor_settings(&name, &size)) { + name = NULL; + size = 24; + } + size *= seat->pointer_scale; + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + theme = wl_cursor_theme_load(name, size, scr_driver->wl_shm); + free(name); + //struct wl_cursor_theme *old_theme = seat->cursor_theme; + if (theme != NULL) { + if (seat->cursor_theme) { + // caution to destroy theme because Fl_Wayland_Window_Driver::set_cursor(Fl_Cursor) caches used cursors + scr_driver->reset_cursor(); + wl_cursor_theme_destroy(seat->cursor_theme); + } + seat->cursor_theme = theme; + } + if (seat->cursor_theme) + seat->default_cursor = scr_driver->xc_arrow = wl_cursor_theme_get_cursor(seat->cursor_theme, "left_ptr"); + if (!seat->cursor_surface) { + seat->cursor_surface = wl_compositor_create_surface(scr_driver->wl_compositor); + wl_surface_add_listener(seat->cursor_surface, &cursor_surface_listener, seat); + } +} + + +static void wl_keyboard_keymap(void *data, struct wl_keyboard *wl_keyboard, + uint32_t format, int32_t fd, uint32_t size) +{ + struct seat *seat = (struct seat*)data; + assert(format == WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1); + + char *map_shm = (char*)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0); + assert(map_shm != MAP_FAILED); + + struct xkb_keymap *xkb_keymap = xkb_keymap_new_from_string(seat->xkb_context, map_shm, + XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(map_shm, size); + close(fd); + + struct xkb_state *xkb_state = xkb_state_new(xkb_keymap); + xkb_keymap_unref(seat->xkb_keymap); + xkb_state_unref(seat->xkb_state); + seat->xkb_keymap = xkb_keymap; + seat->xkb_state = xkb_state; +} + +static void wl_keyboard_enter(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface, + struct wl_array *keys) +{ + struct seat *seat = (struct seat*)data; +//fprintf(stderr, "keyboard enter fl_win=%p; keys pressed are:\n", Fl_Wayland_Screen_Driver::surface_to_window(surface)); + seat->keyboard_surface = surface; + seat->keyboard_enter_serial = serial; +} + +struct key_repeat_data_t { + uint32_t time; + Fl_Window *window; +}; + +#define KEY_REPEAT_DELAY 0.5 // sec +#define KEY_REPEAT_INTERVAL 0.05 // sec + +static void key_repeat_timer_cb(key_repeat_data_t *key_repeat_data) { + if ((Fl::event() == FL_KEYDOWN || (Fl_Window_Driver::menu_parent() && Fl::event() == FL_ENTER)) && fl_event_time == key_repeat_data->time) { + Fl::handle(FL_KEYDOWN, key_repeat_data->window); + Fl::add_timeout(KEY_REPEAT_INTERVAL, (Fl_Timeout_Handler)key_repeat_timer_cb, key_repeat_data); + } + else delete key_repeat_data; +} + +int Fl_Wayland_Screen_Driver::next_marked_length = 0; + +int Fl_Wayland_Screen_Driver::has_marked_text() const { + return 1; +} + +int Fl_Wayland_Screen_Driver::insertion_point_x = 0; +int Fl_Wayland_Screen_Driver::insertion_point_y = 0; +int Fl_Wayland_Screen_Driver::insertion_point_width = 0; +int Fl_Wayland_Screen_Driver::insertion_point_height = 0; +bool Fl_Wayland_Screen_Driver::insertion_point_location_is_valid = false; + + +// inform TIM about location of the insertion point, and memorize this info. +void Fl_Wayland_Screen_Driver::insertion_point_location(int x, int y, int height) { +//printf("insertion_point_location %dx%d\n",x,y); + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + if (scr_driver->seat->text_input) { + if (Fl::focus()) { + Fl_Widget *focuswin = Fl::focus()->window(); + while (focuswin && focuswin->parent()) { + x += focuswin->x(); y += focuswin->y(); + focuswin = focuswin->window(); + } + } + float s = fl_graphics_driver->scale(); + insertion_point_location_is_valid = true; + insertion_point_x = s*x; + insertion_point_y = s*(y-height); + insertion_point_width = s*5; + insertion_point_height = s*height; + if (zwp_text_input_v3_get_user_data(scr_driver->seat->text_input) ) { + zwp_text_input_v3_set_cursor_rectangle(scr_driver->seat->text_input, insertion_point_x, + insertion_point_y, insertion_point_width, insertion_point_height); + zwp_text_input_v3_commit(scr_driver->seat->text_input); + } + } +} + + +// computes window coordinates & size of insertion point +bool Fl_Wayland_Screen_Driver::insertion_point_location(int *px, int *py, int *pwidth, int *pheight) +// return true if the current coordinates and size of the insertion point are available +{ + if ( ! insertion_point_location_is_valid ) return false; + *px = insertion_point_x; + *py = insertion_point_y; + *pwidth = insertion_point_width; + *pheight = insertion_point_height; + return true; +} + +int Fl_Wayland_Screen_Driver::compose(int& del) { + unsigned char ascii = (unsigned char)Fl::e_text[0]; + int condition = (Fl::e_state & (FL_ALT | FL_META | FL_CTRL)) && ascii < 128 ; // letter+modifier key + condition |= (Fl::e_keysym >= FL_Shift_L && Fl::e_keysym <= FL_Alt_R); // pressing modifier key + condition |= (Fl::e_keysym >= FL_Home && Fl::e_keysym <= FL_Help); +//fprintf(stderr, "compose: condition=%d e_state=%x ascii=%d\n", condition, Fl::e_state, ascii); + if (condition) { del = 0; return 0;} +//fprintf(stderr, "compose: del=%d compose_state=%d next_marked_length=%d \n", del, Fl::compose_state, next_marked_length); + del = Fl::compose_state; + Fl::compose_state = next_marked_length; + // no-underlined-text && (ascii non-printable || ascii == delete) + if (ascii && (!Fl::compose_state) && (ascii <= 31 || ascii == 127)) { del = 0; return 0; } + return 1; +} + +void Fl_Wayland_Screen_Driver::compose_reset() +{ + Fl::compose_state = 0; + next_marked_length = 0; + xkb_compose_state_reset(seat->xkb_compose_state); +} + +struct dead_key_struct { + xkb_keysym_t keysym; // the keysym obtained when hitting a dead key + const char *marked_text; // the temporary text to display for that dead key +}; + +static dead_key_struct dead_keys[] = { + {XKB_KEY_dead_grave, "`"}, + {XKB_KEY_dead_acute, "´"}, + {XKB_KEY_dead_circumflex, "^"}, + {XKB_KEY_dead_tilde, "~"}, + {XKB_KEY_dead_perispomeni, "~"}, // alias for dead_tilde + {XKB_KEY_dead_macron, "¯"}, + {XKB_KEY_dead_breve, "˘"}, + {XKB_KEY_dead_abovedot, "˙"}, + {XKB_KEY_dead_diaeresis, "¨"}, + {XKB_KEY_dead_abovering, "˚"}, + {XKB_KEY_dead_doubleacute, "˝"}, + {XKB_KEY_dead_caron, "ˇ"}, + {XKB_KEY_dead_cedilla, "¸"}, + {XKB_KEY_dead_ogonek, "˛"}, + {XKB_KEY_dead_iota, "ι"}, + {XKB_KEY_dead_doublegrave, " ̏"}, +}; + +const int dead_key_count = sizeof(dead_keys)/sizeof(struct dead_key_struct); + +static void wl_keyboard_key(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t time, uint32_t key, uint32_t state) +{ + struct seat *seat = (struct seat*)data; + seat->serial = serial; + static char buf[128]; + uint32_t keycode = key + 8; + xkb_keysym_t sym = xkb_state_key_get_one_sym(seat->xkb_state, keycode); +/*xkb_keysym_get_name(sym, buf, sizeof(buf)); +const char *action = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? "press" : "release"); +fprintf(stderr, "key %s: sym: %-12s(%d) code:%u fl_win=%p, ", action, buf, sym, keycode, Fl_Wayland_Screen_Driver::surface_to_window(seat->keyboard_surface));*/ + xkb_state_key_get_utf8(seat->xkb_state, keycode, buf, sizeof(buf)); +//fprintf(stderr, "utf8: '%s' e_length=%d [%d]\n", buf, (int)strlen(buf), *buf); + Fl::e_keysym = sym; + // special processing for number keys == keycodes 10-19 : + if (keycode >= 10 && keycode <= 18) Fl::e_keysym = keycode + 39; + else if (keycode == 19) Fl::e_keysym = 48; + Fl::e_text = buf; + Fl::e_length = strlen(buf); + // Process dead keys and compose sequences : + enum xkb_compose_status status = XKB_COMPOSE_NOTHING; + // This part is useful only if the compositor doesn't support protocol text-input-unstable-v3 + if (state == WL_KEYBOARD_KEY_STATE_PRESSED && !(sym >= FL_Shift_L && sym <= FL_Alt_R) && + sym != XKB_KEY_ISO_Level3_Shift) { + xkb_compose_state_feed(seat->xkb_compose_state, sym); + status = xkb_compose_state_get_status(seat->xkb_compose_state); + if (status == XKB_COMPOSE_COMPOSING) { + if (Fl::e_length == 0) { // dead keys produce e_length = 0 + int i; + for (i = 0; i < dead_key_count; i++) { + if (dead_keys[i].keysym == sym) break; + } + if (i < dead_key_count) strcpy(buf, dead_keys[i].marked_text); + else buf[0] = 0; + Fl::e_length = strlen(buf); + Fl::compose_state = 0; + } + Fl_Wayland_Screen_Driver::next_marked_length = Fl::e_length; + } else if (status == XKB_COMPOSE_COMPOSED) { + Fl::e_length = xkb_compose_state_get_utf8(seat->xkb_compose_state, buf, sizeof(buf)); + Fl::compose_state = Fl_Wayland_Screen_Driver::next_marked_length; + Fl_Wayland_Screen_Driver::next_marked_length = 0; + } else if (status == XKB_COMPOSE_CANCELLED) { + Fl::e_length = 0; + Fl::compose_state = Fl_Wayland_Screen_Driver::next_marked_length; + Fl_Wayland_Screen_Driver::next_marked_length = 0; + } +//fprintf(stderr, "xkb_compose_status=%d ctxt=%p state=%p l=%d[%s]\n", status, seat->xkb_context, seat->xkb_compose_state, Fl::e_length, buf); + } + // end of part used only without text-input-unstable-v3 + + fl_event_time = time; + int event = (state == WL_KEYBOARD_KEY_STATE_PRESSED ? FL_KEYDOWN : FL_KEYUP); + // Send event to focus-containing top window as defined by FLTK, + // otherwise send it to Wayland-defined focus window + Fl_Window *win = ( Fl::focus() ? Fl::focus()->top_window() : Fl_Wayland_Screen_Driver::surface_to_window(seat->keyboard_surface) ); + if (win) { + set_event_xy(win); + Fl::e_is_click = 0; + Fl::handle(event, win); + } + key_repeat_data_t *key_repeat_data = new key_repeat_data_t; + key_repeat_data->time = time; + key_repeat_data->window = win; + if (event == FL_KEYDOWN && status == XKB_COMPOSE_NOTHING && !(sym >= FL_Shift_L && sym <= FL_Alt_R)) + Fl::add_timeout(KEY_REPEAT_DELAY, (Fl_Timeout_Handler)key_repeat_timer_cb, key_repeat_data); +} + +static void wl_keyboard_leave(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, struct wl_surface *surface) +{ + struct seat *seat = (struct seat*)data; +//fprintf(stderr, "keyboard leave fl_win=%p\n", Fl_Wayland_Screen_Driver::surface_to_window(surface)); + seat->keyboard_surface = NULL; +} + +static void wl_keyboard_modifiers(void *data, struct wl_keyboard *wl_keyboard, + uint32_t serial, uint32_t mods_depressed, + uint32_t mods_latched, uint32_t mods_locked, + uint32_t group) +{ + struct seat *seat = (struct seat*)data; + xkb_state_update_mask(seat->xkb_state, mods_depressed, mods_latched, mods_locked, 0, 0, group); + Fl::e_state = 0; + if (xkb_state_mod_name_is_active(seat->xkb_state, XKB_MOD_NAME_SHIFT, XKB_STATE_MODS_DEPRESSED)) + Fl::e_state |= FL_SHIFT; + if (xkb_state_mod_name_is_active(seat->xkb_state, XKB_MOD_NAME_CTRL, XKB_STATE_MODS_DEPRESSED)) + Fl::e_state |= FL_CTRL; + if (xkb_state_mod_name_is_active(seat->xkb_state, XKB_MOD_NAME_ALT, XKB_STATE_MODS_DEPRESSED)) + Fl::e_state |= FL_ALT; + if (xkb_state_mod_name_is_active(seat->xkb_state, XKB_MOD_NAME_CAPS, XKB_STATE_MODS_LOCKED)) + Fl::e_state |= FL_CAPS_LOCK; +//fprintf(stderr, "mods_depressed=%u Fl::e_state=%X\n", mods_depressed, Fl::e_state); +} + +static void wl_keyboard_repeat_info(void *data, struct wl_keyboard *wl_keyboard, int32_t rate, int32_t delay) +{ + // wl_keyboard is version 3 under Debian, but that event isn't sent until version 4 +} + +static const struct wl_keyboard_listener wl_keyboard_listener = { + .keymap = wl_keyboard_keymap, + .enter = wl_keyboard_enter, + .leave = wl_keyboard_leave, + .key = wl_keyboard_key, + .modifiers = wl_keyboard_modifiers, + .repeat_info = wl_keyboard_repeat_info, +}; + + +void text_input_enter(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, + struct wl_surface *surface) { +//puts("text_input_enter"); + zwp_text_input_v3_set_user_data(zwp_text_input_v3, surface); + zwp_text_input_v3_enable(zwp_text_input_v3); + int x, y, width, height; + if (Fl_Wayland_Screen_Driver::insertion_point_location(&x, &y, &width, &height)) { + zwp_text_input_v3_set_cursor_rectangle(zwp_text_input_v3, x, y, width, height); + } + zwp_text_input_v3_commit(zwp_text_input_v3); + wl_display_roundtrip(fl_display); +} + +void text_input_leave(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, + struct wl_surface *surface) { +//puts("text_input_leave"); + zwp_text_input_v3_disable(zwp_text_input_v3); + zwp_text_input_v3_set_user_data(zwp_text_input_v3, NULL); + zwp_text_input_v3_commit(zwp_text_input_v3); +} + +void text_input_preedit_string(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, + const char *text, int32_t cursor_begin, int32_t cursor_end) { +//printf("text_input_preedit_string %s cursor_begin=%d cursor_end=%d\n",text, cursor_begin, cursor_end); + // goes to widget as marked text + Fl_Wayland_Screen_Driver::next_marked_length = text ? strlen(text) : 0; + Fl::e_text = text ? (char*)text : (char*)""; + Fl::e_length = text ? strlen(text) : 0; + Fl::e_keysym = 'a'; // fake a simple key + struct wl_surface *surface = (struct wl_surface*)data; + Fl_Window *win = Fl_Wayland_Screen_Driver::surface_to_window(surface); + set_event_xy(win); + Fl::e_is_click = 0; + Fl::handle(FL_KEYDOWN, win); +} + +void text_input_commit_string(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, + const char *text) { +//printf("text_input_commit_string %s\n",text); + Fl::e_text = (char*)text; + Fl::e_length = strlen(text); + struct wl_surface *surface = (struct wl_surface*)data; + Fl_Window *win = Fl_Wayland_Screen_Driver::surface_to_window(surface); + set_event_xy(win); + Fl::e_is_click = 0; + Fl::handle(FL_KEYDOWN, win); + zwp_text_input_v3_commit(zwp_text_input_v3); + Fl_Wayland_Screen_Driver::next_marked_length = 0; + Fl::compose_state = 0; +} + +void text_input_delete_surrounding_text(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, + uint32_t before_length, uint32_t after_length) { + fprintf(stderr, "delete_surrounding_text before=%d adfter=%d\n",before_length,after_length); +} + +void text_input_done(void *data, struct zwp_text_input_v3 *zwp_text_input_v3, + uint32_t serial) { +//puts("text_input_done"); +} + +static const struct zwp_text_input_v3_listener text_input_listener = { + .enter = text_input_enter, + .leave = text_input_leave, + .preedit_string = text_input_preedit_string, + .commit_string = text_input_commit_string, + .delete_surrounding_text = text_input_delete_surrounding_text, + .done = text_input_done, +}; + + +static void seat_capabilities(void *data, struct wl_seat *wl_seat, uint32_t capabilities) +{ + struct seat *seat = (struct seat*)data; + if ((capabilities & WL_SEAT_CAPABILITY_POINTER) && !seat->wl_pointer) { + seat->wl_pointer = wl_seat_get_pointer(wl_seat); + wl_pointer_add_listener(seat->wl_pointer, &pointer_listener, seat); + seat->pointer_scale = 1; + init_cursors(seat); + } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && seat->wl_pointer) { + wl_pointer_release(seat->wl_pointer); + seat->wl_pointer = NULL; + } + + bool have_keyboard = capabilities & WL_SEAT_CAPABILITY_KEYBOARD; + if (have_keyboard && seat->wl_keyboard == NULL) { + seat->wl_keyboard = wl_seat_get_keyboard(wl_seat); + wl_keyboard_add_listener(seat->wl_keyboard, + &wl_keyboard_listener, seat); +//fprintf(stderr, "wl_keyboard version=%d\n", wl_keyboard_get_version(seat->wl_keyboard)); + + } else if (!have_keyboard && seat->wl_keyboard != NULL) { + wl_keyboard_release(seat->wl_keyboard); + seat->wl_keyboard = NULL; + } + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + if (scr_driver->text_input_base) { + seat->text_input = zwp_text_input_manager_v3_get_text_input(scr_driver->text_input_base, seat->wl_seat); +//printf("seat->text_input=%p\n",seat->text_input); + zwp_text_input_v3_add_listener(seat->text_input, &text_input_listener, NULL); + } +} + +static void seat_name(void *data, struct wl_seat *wl_seat, const char *name) { + struct seat *seat = (struct seat*)data; + seat->name = strdup(name); +} + +static struct wl_seat_listener seat_listener = { + seat_capabilities, + seat_name +}; + +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 subpixel, + const char *make, + const char *model, + int32_t transform) +{ + //fprintf(stderr, "output_geometry: x=%d y=%d physical=%dx%d\n",x,y,physical_width,physical_height); + Fl_Wayland_Screen_Driver::output *output = (Fl_Wayland_Screen_Driver::output*)data; + output->dpi = 96; // to elaborate +} + +static void output_mode(void *data, struct wl_output *wl_output, uint32_t flags, + int32_t width, int32_t height, int32_t refresh) +{ + Fl_Wayland_Screen_Driver::output *output = (Fl_Wayland_Screen_Driver::output*)data; + output->x_org = 0; + output->y_org = 0; + output->width = width; + output->height = height; +//fprintf(stderr, "output_mode: [%p]=%dx%d\n",output->wl_output,width,height); +} + +static void output_done(void *data, struct wl_output *wl_output) +{ + Fl_Wayland_Screen_Driver::output *output = (Fl_Wayland_Screen_Driver::output*)data; + Fl_Wayland_Window_Driver::window_output *window_output; + struct seat *seat; +//fprintf(stderr, "output_done output=%p\n",output); + Fl_X *xp = Fl_X::first; + while (xp) { // all mapped windows + struct wld_window *win = xp->xid; + wl_list_for_each(window_output, &(win->outputs), link) { // all Fl_Wayland_Window_Driver::window_output for this window + if (window_output->output == output) { + Fl_Wayland_Window_Driver *win_driver = Fl_Wayland_Window_Driver::driver(win->fl_win); + if (output->wld_scale != win->scale) win_driver->update_scale(); + } + } + xp = xp->next; + } + + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + wl_list_for_each(seat, &(scr_driver->seats), link) { + try_update_cursor(seat); + } + scr_driver->init_workarea(); + Fl::handle(FL_SCREEN_CONFIGURATION_CHANGED, NULL); +} + + +static void output_scale(void *data, struct wl_output *wl_output, int32_t factor) { + Fl_Wayland_Screen_Driver::output *output = (Fl_Wayland_Screen_Driver::output*)data; + output->wld_scale = factor; +//fprintf(stderr,"output_scale: wl_output=%p factor=%d\n",wl_output, factor); +} + + +static struct wl_output_listener output_listener = { + output_geometry, + output_mode, + output_done, + output_scale +}; + + +static void registry_handle_global(void *user_data, struct wl_registry *wl_registry, + uint32_t id, const char *interface, uint32_t version) { +//fprintf(stderr, "interface=%s\n", interface); + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + if (strcmp(interface, "wl_compositor") == 0) { + if (version < 4) { + Fl::fatal("wl_compositor version >= 4 required"); + } + scr_driver->wl_compositor = (struct wl_compositor*)wl_registry_bind(wl_registry, + id, &wl_compositor_interface, 4); + + } else if (strcmp(interface, "wl_subcompositor") == 0) { + scr_driver->wl_subcompositor = (struct wl_subcompositor*)wl_registry_bind(wl_registry, + id, &wl_subcompositor_interface, 1); + + } else if (strcmp(interface, "wl_shm") == 0) { + scr_driver->wl_shm = (struct wl_shm*)wl_registry_bind(wl_registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(scr_driver->wl_shm, &shm_listener, NULL); + + } else if (strcmp(interface, "wl_seat") == 0) { + if (version < 3) { + Fl::fatal("%s version 3 required but only version %i is available\n", interface, version); + } + if (!scr_driver->seat) scr_driver->seat = (struct seat*)calloc(1, sizeof(struct seat)); +//fprintf(stderr, "registry_handle_global: seat=%p\n", scr_driver->seat); + wl_list_init(&scr_driver->seat->pointer_outputs); + scr_driver->seat->wl_seat = (wl_seat*)wl_registry_bind(wl_registry, id, &wl_seat_interface, 3); + scr_driver->seat->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + const char *locale = getenv("LC_ALL"); + if (!locale || !*locale) + locale = getenv("LC_CTYPE"); + if (!locale || !*locale) + locale = getenv("LANG"); + if (!locale || !*locale) + locale = "C"; + struct xkb_compose_table *table = xkb_compose_table_new_from_locale(scr_driver->seat->xkb_context, locale, XKB_COMPOSE_COMPILE_NO_FLAGS); + scr_driver->seat->xkb_compose_state = xkb_compose_state_new(table, XKB_COMPOSE_STATE_NO_FLAGS); + wl_seat_add_listener(scr_driver->seat->wl_seat, &seat_listener, scr_driver->seat); + if (scr_driver->seat->data_device_manager) { + scr_driver->seat->data_device = wl_data_device_manager_get_data_device(scr_driver->seat->data_device_manager, scr_driver->seat->wl_seat); + wl_data_device_add_listener(scr_driver->seat->data_device, Fl_Wayland_Screen_Driver::p_data_device_listener, NULL); + } + + } else if (strcmp(interface, wl_data_device_manager_interface.name) == 0) { + if (!scr_driver->seat) scr_driver->seat = (struct seat*)calloc(1, sizeof(struct seat)); + scr_driver->seat->data_device_manager = (struct wl_data_device_manager*)wl_registry_bind(wl_registry, id, &wl_data_device_manager_interface, 3); + if (scr_driver->seat->wl_seat) { + scr_driver->seat->data_device = wl_data_device_manager_get_data_device(scr_driver->seat->data_device_manager, scr_driver->seat->wl_seat); + wl_data_device_add_listener(scr_driver->seat->data_device, Fl_Wayland_Screen_Driver::p_data_device_listener, NULL); + } +//fprintf(stderr, "registry_handle_global: %s\n", interface); + + } else if (strcmp(interface, "wl_output") == 0) { + if (version < 2) { + Fl::fatal("%s version 3 required but only version %i is available\n", interface, version); + } + Fl_Wayland_Screen_Driver::output *output = (Fl_Wayland_Screen_Driver::output*)calloc(1, sizeof *output); + output->id = id; + output->wld_scale = 1; + output->wl_output = (struct wl_output*)wl_registry_bind(wl_registry, + id, &wl_output_interface, 2); + output->gui_scale = 1.f; + wl_proxy_set_tag((struct wl_proxy *) output->wl_output, &proxy_tag); + wl_output_add_listener(output->wl_output, &output_listener, output); + wl_list_insert(&(scr_driver->outputs), &output->link); + scr_driver->screen_count( wl_list_length(&(scr_driver->outputs)) ); +//fprintf(stderr, "wl_output: id=%d wl_output=%p screen_count()=%d\n", id, output->wl_output, Fl::screen_count()); + + } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) { +//fprintf(stderr, "registry_handle_global interface=%s\n", interface); + scr_driver->xdg_wm_base = (struct xdg_wm_base *)wl_registry_bind(wl_registry, id, &xdg_wm_base_interface, 1); + xdg_wm_base_add_listener(scr_driver->xdg_wm_base, &xdg_wm_base_listener, NULL); + } else if (strcmp(interface, "gtk_shell1") == 0) { + Fl_Wayland_Screen_Driver::compositor = Fl_Wayland_Screen_Driver::MUTTER; + //fprintf(stderr, "Running the Mutter compositor\n"); + } else if (strcmp(interface, "weston_desktop_shell") == 0) { + Fl_Wayland_Screen_Driver::compositor = Fl_Wayland_Screen_Driver::WESTON; + //fprintf(stderr, "Running the Weston compositor\n"); + } else if (strcmp(interface, "org_kde_plasma_shell") == 0) { + Fl_Wayland_Screen_Driver::compositor = Fl_Wayland_Screen_Driver::KDE; + //fprintf(stderr, "Running the KDE compositor\n"); + } + else if (strcmp(interface, zwp_text_input_manager_v3_interface.name) == 0) { + scr_driver->text_input_base = (struct zwp_text_input_manager_v3 *) wl_registry_bind(wl_registry, id, &zwp_text_input_manager_v3_interface, 1); + //printf("scr_driver->text_input_base=%p version=%d\n",scr_driver->text_input_base,version); + } +} + + +static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) +{//TODO to be tested + Fl_Wayland_Screen_Driver::output *output; + Fl_Wayland_Window_Driver::window_output *window_output, *tmp; +//fprintf(stderr, "registry_handle_global_remove data=%p id=%u\n", data, name); + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + wl_list_for_each(output, &(scr_driver->outputs), link) { // all screens of the system + if (output->id == name) { // the screen being removed + Fl_X *xp = Fl_X::first; + while (xp) { // all mapped windows + struct wld_window *win = xp->xid; + wl_list_for_each_safe(window_output, tmp, &(win->outputs), link) { // all Fl_Wayland_Window_Driver::window_output for this window + if (window_output->output == output) { + wl_list_remove(&window_output->link); + free(window_output); + } + } + xp = xp->next; + } + wl_list_remove(&output->link); + scr_driver->screen_count( wl_list_length(&(scr_driver->outputs)) ); + wl_output_destroy(output->wl_output); + free(output); + break; + } + } +} + + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + + +static void fd_callback(int unused, struct wl_display *display) { + wl_display_dispatch(display); +} + + +Fl_Wayland_Screen_Driver::Fl_Wayland_Screen_Driver() : Fl_Screen_Driver() { + libdecor_context = NULL; + seat = NULL; + text_input_base = NULL; + reset_cursor(); +} + +void Fl_Wayland_Screen_Driver::open_display_platform() { + struct wl_display *wl_display; + struct wl_registry *wl_registry; + + static bool beenHereDoneThat = false; + if (beenHereDoneThat) + return; + + beenHereDoneThat = true; + wl_display = wl_display_connect(NULL); + if (!wl_display) { + Fl::fatal("No Wayland connection\n"); + } + fl_display = wl_display; + wl_list_init(&seats); + wl_list_init(&outputs); + + wl_registry = wl_display_get_registry(wl_display); + wl_registry_add_listener(wl_registry, ®istry_listener, NULL); + wl_display_dispatch(wl_display); + wl_display_roundtrip(wl_display); + if (!has_xrgb) { + Fl::fatal("Error: no WL_SHM_FORMAT_ARGB8888 shm format\n"); + } + if (compositor == Fl_Wayland_Screen_Driver::unspecified) { + Fl::warning("FLTK could not identify the type of the running Wayland compositor"); + } + Fl::add_fd(wl_display_get_fd(wl_display), FL_READ, (Fl_FD_Handler)fd_callback, wl_display); + fl_create_print_window(); +} + +void Fl_Wayland_Screen_Driver::close_display() { + Fl::remove_fd(wl_display_get_fd(fl_display)); + wl_display_disconnect(fl_display); +} + + +static int workarea_xywh[4] = { -1, -1, -1, -1 }; + + +void Fl_Wayland_Screen_Driver::init_workarea() +{ + workarea_xywh[0] = 0; + workarea_xywh[1] = 0; + Fl_Wayland_Screen_Driver::output *output; + wl_list_for_each(output, &outputs, link) { + workarea_xywh[2] = output->width; // pixels + workarea_xywh[3] = output->height; // pixels + break; + } +} + + +int Fl_Wayland_Screen_Driver::x() { + if (!fl_display) open_display(); + Fl_Wayland_Screen_Driver::output *output; + wl_list_for_each(output, &outputs, link) { + break; + } + return workarea_xywh[0] / (output->gui_scale * output->wld_scale); +} + +int Fl_Wayland_Screen_Driver::y() { + if (!fl_display) open_display(); + Fl_Wayland_Screen_Driver::output *output; + wl_list_for_each(output, &outputs, link) { + break; + } + return workarea_xywh[1] / (output->gui_scale * output->wld_scale); +} + +int Fl_Wayland_Screen_Driver::w() { + if (!fl_display) open_display(); + Fl_Wayland_Screen_Driver::output *output; + wl_list_for_each(output, &outputs, link) { + break; + } + return workarea_xywh[2] / (output->gui_scale * output->wld_scale); +} + +int Fl_Wayland_Screen_Driver::h() { + if (!fl_display) open_display(); + Fl_Wayland_Screen_Driver::output *output; + wl_list_for_each(output, &outputs, link) { + break; + } + return workarea_xywh[3] / (output->gui_scale * output->wld_scale); +} + + +void Fl_Wayland_Screen_Driver::init() { + if (!fl_display) open_display(); +} + + +void Fl_Wayland_Screen_Driver::screen_work_area(int &X, int &Y, int &W, int &H, int n) +{ + if (num_screens < 0) init(); + if (n < 0 || n >= num_screens) n = 0; + if (n == 0) { // for the main screen, these return the work area + X = Fl::x(); + Y = Fl::y(); + W = Fl::w(); + H = Fl::h(); + } else { // for other screens, work area is full screen, + screen_xywh(X, Y, W, H, n); + } +} + + +void Fl_Wayland_Screen_Driver::screen_xywh(int &X, int &Y, int &W, int &H, int n) +{ + if (num_screens < 0) init(); + + if ((n < 0) || (n >= num_screens)) + n = 0; + + if (num_screens > 0) { + Fl_Wayland_Screen_Driver::output *output; + int i = 0; + wl_list_for_each(output, &outputs, link) { + if (i++ == n) { // n'th screen of the system + float s = output->gui_scale * output->wld_scale; + X = output->x_org / s; + Y = output->y_org / s; + W = output->width / s; + H = output->height / s; + break; + } + } + } +} + + +void Fl_Wayland_Screen_Driver::screen_dpi(float &h, float &v, int n) +{ + if (num_screens < 0) init(); + h = v = 0.0f; + + if (n >= 0 && n < num_screens) { + Fl_Wayland_Screen_Driver::output *output; + int i = 0; + wl_list_for_each(output, &outputs, link) { + if (i++ == n) { // n'th screen of the system + h = output->dpi; + v = output->dpi; + break; + } + } + } +} + + +void Fl_Wayland_Screen_Driver::beep(int type) +{ + fprintf(stderr, "\007"); +} + + +void Fl_Wayland_Screen_Driver::flush() +{ + if (fl_display) { + wl_display_flush(fl_display); + } +} + + +extern void fl_fix_focus(); // in Fl.cxx + + +void Fl_Wayland_Screen_Driver::grab(Fl_Window* win) +{ + Fl_Window *fullscreen_win = NULL; + for (Fl_Window *W = Fl::first_window(); W; W = Fl::next_window(W)) { + if (W->fullscreen_active()) { + fullscreen_win = W; + break; + } + } + if (win) { + if (!Fl::grab()) { + } + Fl::grab_ = win; // FIXME: Fl::grab_ "should be private", but we need + // a way to *set* the variable from the driver! + } else { + if (Fl::grab()) { + // We must keep the grab in the non-EWMH fullscreen case + if (!fullscreen_win ) { + //XUngrabKeyboard(fl_display, fl_event_time); + } + //XUngrabPointer(fl_display, fl_event_time); + // this flush is done in case the picked menu item goes into + // an infinite loop, so we don't leave the X server locked up: + //XFlush(fl_display); + Fl::grab_ = 0; // FIXME: Fl::grab_ "should be private", but we need + // a way to *set* the variable from the driver! + fl_fix_focus(); + } + } +} + + +static void set_selection_color(uchar r, uchar g, uchar b) +{ + Fl::set_color(FL_SELECTION_COLOR,r,g,b); +} + +static void getsyscolor(const char *key1, const char* key2, const char *arg, const char *defarg, void (*func)(uchar,uchar,uchar)) +{ + uchar r, g, b; + if (!arg) arg = defarg; + if (!Fl::screen_driver()->parse_color(arg, r, g, b)) + Fl::error("Unknown color: %s", arg); + else + func(r, g, b); +} + + +void Fl_Wayland_Screen_Driver::get_system_colors() +{ + open_display(); + const char* key1 = 0; + if (Fl::first_window()) key1 = Fl::first_window()->xclass(); + if (!key1) key1 = "fltk"; + if (!bg2_set) + getsyscolor("Text","background", fl_bg2, "#ffffff", Fl::background2); + if (!fg_set) + getsyscolor(key1, "foreground", fl_fg, "#000000", Fl::foreground); + if (!bg_set) + getsyscolor(key1, "background", fl_bg, "#c0c0c0", Fl::background); + getsyscolor("Text", "selectBackground", 0, "#000080", set_selection_color); +} + + +const char *Fl_Wayland_Screen_Driver::get_system_scheme() +{ + return getenv("FLTK_SCHEME"); +} + + +Fl_RGB_Image *Fl_Wayland_Screen_Driver::read_win_rectangle(int X, int Y, int w, int h, Fl_Window *win, + bool ignore, bool *p_ignore) { + Window xid = win ? fl_xid(win) : NULL; + struct fl_wld_buffer *buffer = win ? xid->buffer : (Fl_Offscreen)Fl_Surface_Device::surface()->driver()->gc(); + float s = win ? xid->scale * scale(win->screen_num()) : + Fl_Surface_Device::surface()->driver()->scale(); + int Xs, Ys, ws, hs; + if (s == 1) { + Xs = X; Ys = Y; ws = w; hs = h; + } else { + Xs = Fl_Scalable_Graphics_Driver::floor(X, s); + Ys = Fl_Scalable_Graphics_Driver::floor(Y, s); + ws = Fl_Scalable_Graphics_Driver::floor(X+w, s) - Xs; + hs = Fl_Scalable_Graphics_Driver::floor(Y+h, s) - Ys; + } + if (ws == 0 || hs == 0) return NULL; + uchar *data = new uchar[ws * hs * 3]; + uchar *p = data, *q; + for (int j = 0; j < hs; j++) { + q = buffer->draw_buffer + (j+Ys) * buffer->stride + 4 * Xs; + for (int i = 0; i < ws; i++) { + *p++ = *(q+2); // R + *p++ = *(q+1); // G + *p++ = *q; // B + q += 4; + } + } + Fl_RGB_Image *rgb = new Fl_RGB_Image(data, ws, hs, 3); + rgb->alloc_array = 1; + return rgb; +} + + +void Fl_Wayland_Screen_Driver::offscreen_size(Fl_Offscreen off, int &width, int &height) +{ + width = off->width; + height = off->data_size / off->stride; +} + +//NOTICE: returns -1 if x,y is not in any screen +int Fl_Wayland_Screen_Driver::screen_num_unscaled(int x, int y) +{ + if (num_screens < 0) init(); + + Fl_Wayland_Screen_Driver::output *output; + int screen = 0; + wl_list_for_each(output, &outputs, link) { + int s = output->wld_scale; + int sx = output->x_org/s, sy = output->y_org/s, sw = output->width/s, sh = output->height/s; + if ((x >= sx) && (x < (sx+sw)) && (y >= sy) && (y < (sy+sh))) { + return screen; + } + screen++; + } + return -1; +} + +float Fl_Wayland_Screen_Driver::scale(int n) { + Fl_Wayland_Screen_Driver::output *output; + int i = 0; + wl_list_for_each(output, &outputs, link) { + if (i++ == n) break; + } + return output->gui_scale; +} + + +void Fl_Wayland_Screen_Driver::scale(int n, float f) { + Fl_Wayland_Screen_Driver::output *output; + int i = 0; + wl_list_for_each(output, &outputs, link) { + if (i++ == n) { + output->gui_scale = f; + return; + } + } +} + + +void Fl_Wayland_Screen_Driver::set_cursor() { + do_set_cursor(seat); +} + +struct wl_cursor *Fl_Wayland_Screen_Driver::default_cursor() { + return seat->default_cursor; +} + +void Fl_Wayland_Screen_Driver::default_cursor(struct wl_cursor *cursor) { + seat->default_cursor = cursor; +} + +struct wl_cursor *Fl_Wayland_Screen_Driver::cache_cursor(const char *cursor_name) { + return wl_cursor_theme_get_cursor(seat->cursor_theme, cursor_name); +} + +void Fl_Wayland_Screen_Driver::reset_cursor() { + xc_arrow = xc_ns = xc_wait = xc_insert = xc_hand = xc_help = xc_cross = xc_move = xc_north = xc_south = xc_west = xc_east = xc_we = xc_nesw = xc_nwse = xc_sw = xc_se = xc_ne = xc_nw = NULL; +} + +uint32_t Fl_Wayland_Screen_Driver::get_serial() { + return seat->serial; +} + +struct wl_seat*Fl_Wayland_Screen_Driver::get_wl_seat() { + return seat->wl_seat; +} + +char *Fl_Wayland_Screen_Driver::get_seat_name() { + return seat->name; +} + +struct xkb_keymap *Fl_Wayland_Screen_Driver::get_xkb_keymap() { + return seat->xkb_keymap; +} diff --git a/src/drivers/Wayland/Fl_Wayland_System_Driver.H b/src/drivers/Wayland/Fl_Wayland_System_Driver.H new file mode 100644 index 000000000..d117f3823 --- /dev/null +++ b/src/drivers/Wayland/Fl_Wayland_System_Driver.H @@ -0,0 +1,34 @@ +// +// Definition of Wayland system driver +// for the Fast Light Tool Kit (FLTK). +// +// Copyright 2010-2021 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 +// + +#ifndef FL_WAYLAND_SYSTEM_DRIVER_H +#define FL_WAYLAND_SYSTEM_DRIVER_H + +#include "../Unix/Fl_Unix_System_Driver.H" + +class FL_EXPORT Fl_Wayland_System_Driver : public Fl_Unix_System_Driver { +public: + Fl_Wayland_System_Driver() : Fl_Unix_System_Driver() { + // Wayland system driver uses the default key table + } + virtual int need_menu_handle_part2() {return 0;} + int event_key(int k); + int get_key(int k); + virtual void *control_maximize_button(void *data); +}; + +#endif /* FL_WAYLAND_SYSTEM_DRIVER_H */ diff --git a/src/drivers/Wayland/Fl_Wayland_System_Driver.cxx b/src/drivers/Wayland/Fl_Wayland_System_Driver.cxx new file mode 100644 index 000000000..96a4c16cd --- /dev/null +++ b/src/drivers/Wayland/Fl_Wayland_System_Driver.cxx @@ -0,0 +1,98 @@ +// +// Definition of Wayland system driver +// for the Fast Light Tool Kit (FLTK). +// +// Copyright 2010-2021 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 "Fl_Wayland_System_Driver.H" +#include <FL/Fl.H> +#include "Fl_Wayland_Window_Driver.H" +#include <FL/platform.H> +#include "../../../libdecor/src/libdecor.h" + +/** + Creates a driver that manages all system related calls. + + This function must be implemented once for every platform. + */ +Fl_System_Driver *Fl_System_Driver::newSystemDriver() +{ + return new Fl_Wayland_System_Driver(); +} + + +int Fl_Wayland_System_Driver::event_key(int k) { + if (k > FL_Button && k <= FL_Button+8) + return Fl::event_state(8<<(k-FL_Button)); + int sym = Fl::event_key(); + if (sym >= 'a' && sym <= 'z' ) sym -= 32; + return (Fl::event() == FL_KEYDOWN || Fl::event() == FL_SHORTCUT) && sym == k; +} + + +int Fl_Wayland_System_Driver::get_key(int k) { + return event_key(k); +} + + +void *Fl_Wayland_System_Driver::control_maximize_button(void *data) { + // The code below aims at removing the calling window's fullscreen button + // while dialog runs. Unfortunately, it doesn't work with some X11 window managers + // (e.g., KDE, xfce) because the button goes away but doesn't come back, + // so we move this code to a virtual member function. + // Noticeably, this code works OK under Wayland. + struct win_dims { + Fl_Widget_Tracker *tracker; + int minw, minh, maxw, maxh; + struct win_dims *next; + }; + + if (!data) { // this call turns each decorated window's maximize button off + struct win_dims *first_dim = NULL; + // consider all bordered, top-level FLTK windows + Fl_Window *win = Fl::first_window(); + while (win) { + if (!win->parent() && win->border() && + !(Fl_X::i(win)->xid->state & LIBDECOR_WINDOW_STATE_MAXIMIZED) ) { + win_dims *dim = new win_dims; + dim->tracker = new Fl_Widget_Tracker(win); + Fl_Window_Driver *dr = Fl_Window_Driver::driver(win); + dim->minw = dr->minw(); + dim->minh = dr->minh(); + dim->maxw = dr->maxw(); + dim->maxh = dr->maxh(); + //make win un-resizable + win->size_range(win->w(), win->h(), win->w(), win->h()); + dim->next = first_dim; + first_dim = dim; + } + win = Fl::next_window(win); + } + return first_dim; + } else { // this call returns each decorated window's maximize button to its previous state + win_dims *first_dim = (win_dims *)data; + while (first_dim) { + win_dims *dim = first_dim; + //give back win its resizing parameters + if (dim->tracker->exists()) { + Fl_Window *win = (Fl_Window*)dim->tracker->widget(); + win->size_range(dim->minw, dim->minh, dim->maxw, dim->maxh); + } + first_dim = dim->next; + delete dim->tracker; + delete dim; + } + return NULL; + } +} diff --git a/src/drivers/Wayland/Fl_Wayland_Window_Driver.H b/src/drivers/Wayland/Fl_Wayland_Window_Driver.H new file mode 100644 index 000000000..99a67dbba --- /dev/null +++ b/src/drivers/Wayland/Fl_Wayland_Window_Driver.H @@ -0,0 +1,172 @@ +// +// Definition of Wayland window driver for the Fast Light Tool Kit (FLTK). +// +// Copyright 2010-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 +// + +/** + \file Fl_Wayland_Window_Driver.H + \brief Definition of Wayland window driver. + */ + +#ifndef FL_WAYLAND_WINDOW_DRIVER_H +#define FL_WAYLAND_WINDOW_DRIVER_H + +#include "../../Fl_Window_Driver.H" +#include <FL/Fl_Plugin.H> +#include "Fl_Wayland_Screen_Driver.H" + + +/* + Move everything here that manages the native window interface. + + There is one window driver for each Fl_Window. Window drivers manage window + actions such as resizing, events, decoration, fullscreen modes, etc. . All + drawing and rendering is managed by the Surface device and the associated + graphics driver. + + - window specific event handling + - window types and styles, depth, etc. + - decorations + */ + +typedef struct _cairo_pattern cairo_pattern_t; + +struct Fl_Window_Driver::shape_data_type { + int lw_; ///< width of shape image + int lh_; ///< height of shape image + Fl_Image* shape_; ///< shape image + cairo_pattern_t *mask_pattern_; +}; + + +class FL_EXPORT Fl_Wayland_Window_Driver : public Fl_Window_Driver +{ + friend class Fl_X; +private: + static bool in_flush; // useful for progressive window drawing + struct wl_cursor *cursor_; + void delete_cursor_(); +public: + struct wl_cursor *cursor() { return cursor_; }; + bool in_handle_configure; // distinguish OS and user window resize + struct window_output { + Fl_Wayland_Screen_Driver::output* output; + struct wl_list link; + }; + + struct icon_data { + const void *legacy_icon; + Fl_RGB_Image **icons; + int count; + } *icon_; + // --- support for screen-specific scaling factors + struct type_for_resize_window_between_screens { + int screen; + bool busy; + }; + static type_for_resize_window_between_screens data_for_resize_window_between_screens_; + int screen_num_; + void screen_num(int n) { screen_num_ = n; } + void decorated_win_size(int &w, int &h); + void shape_bitmap_(Fl_Image* b); + void shape_alpha_(Fl_Image* img, int offset); + void update_scale(); + +public: + enum kind {DECORATED, SUBWINDOW, POPUP, UNFRAMED}; + struct xdg_toplevel *xdg_toplevel(); + Fl_Wayland_Window_Driver(Fl_Window*); + virtual ~Fl_Wayland_Window_Driver(); + static void redraw(struct wld_window *window); + + static inline Fl_Wayland_Window_Driver* driver(const Fl_Window *w) {return (Fl_Wayland_Window_Driver*)Fl_Window_Driver::driver(w);} + virtual int screen_num(); + static void resize_after_screen_change(void *data); + + // --- window data + virtual int decorated_w(); + virtual int decorated_h(); + virtual const Fl_Image* shape(); + + // --- window management + virtual Fl_X *makeWindow(); + virtual void take_focus(); + virtual void flush(); + virtual void flush_overlay(); + virtual void draw_end(); + virtual void make_current(); + virtual void show(); + virtual void resize(int X,int Y,int W,int H); + virtual void label(const char *name, const char *mininame); + virtual void destroy_double_buffer(); + virtual void hide(); + virtual void map(); + virtual void unmap(); + virtual void fullscreen_on(); + virtual void fullscreen_off(int X, int Y, int W, int H); + virtual void use_border(); + virtual void size_range(); + virtual void iconize(); + virtual void decoration_sizes(int *top, int *left, int *right, int *bottom); + virtual void show_with_args_begin(); + virtual void show_with_args_end(int argc, char **argv); + // --- window cursor stuff + virtual int set_cursor(Fl_Cursor); + virtual int set_cursor(const Fl_RGB_Image*, int, int); + + virtual void shape(const Fl_Image* img); + virtual void icons(const Fl_RGB_Image *icons[], int count); + virtual const void *icon() const; + virtual void icon(const void * ic); + virtual void free_icons(); + virtual void capture_titlebar_and_borders(Fl_RGB_Image*& top, Fl_RGB_Image*& left, Fl_RGB_Image*& bottom, Fl_RGB_Image*& right); + virtual int 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); + virtual void wait_for_expose(); + virtual void reposition_menu_window(int x, int y); + virtual void menu_window_area(int &X, int &Y, int &W, int &H, int nscreen = -1); +}; + + +struct wld_window { + struct wl_list outputs; + struct wl_surface *wl_surface; + struct fl_wld_buffer *buffer; + struct xdg_surface *xdg_surface; + union { + struct libdecor_frame *frame; + struct wl_subsurface *subsurface; + struct xdg_popup *xdg_popup; + struct xdg_toplevel *xdg_toplevel; + }; + Fl_Window *fl_win; + enum Fl_Wayland_Window_Driver::kind kind; + int configured_width; + int configured_height; + int floating_width; + int floating_height; + int scale; + int state; +}; + + +class Fl_Wayland_Plugin : public Fl_Plugin { +public: + Fl_Wayland_Plugin(const char *pluginName) : Fl_Plugin(klass(), pluginName) { } + virtual const char *klass() { return "wayland.fltk.org"; } + virtual const char *name() = 0; + virtual void do_swap(Fl_Window*) = 0; + virtual void invalidate(Fl_Window*) = 0; +}; + +#endif // FL_WAYLAND_WINDOW_DRIVER_H 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); +} diff --git a/src/drivers/Wayland/Fl_wayland.cxx b/src/drivers/Wayland/Fl_wayland.cxx new file mode 100644 index 000000000..f00a7d909 --- /dev/null +++ b/src/drivers/Wayland/Fl_wayland.cxx @@ -0,0 +1,672 @@ +// +// Wayland specific code for the Fast Light Tool Kit (FLTK). +// +// Copyright 1998-2021 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 +// + +#if !defined(FL_DOXYGEN) + +# include <config.h> +# include <FL/Fl.H> +# include <FL/platform.H> +# include <FL/Fl_Window.H> +# include <FL/Fl_Shared_Image.H> +# include <FL/Fl_Image_Surface.H> +# include <stdio.h> +# include <stdlib.h> +# include "../../flstring.h" +# include "Fl_Wayland_Screen_Driver.H" +# include "Fl_Wayland_Window_Driver.H" +# include "Fl_Wayland_System_Driver.H" +# include "Fl_Wayland_Graphics_Driver.H" +# include <errno.h> + + +//////////////////////////////////////////////////////////////// + +Window fl_message_window = 0; +int fl_screen; +Window fl_xim_win = 0; +char fl_is_over_the_spot = 0; + + +int Fl_Wayland_Screen_Driver::get_mouse_unscaled(int &mx, int &my) { + open_display(); + mx = Fl::e_x_root; my = Fl::e_y_root; + int screen = screen_num_unscaled(mx, my); + return screen >= 0 ? screen : 0; +} + + +int Fl_Wayland_Screen_Driver::get_mouse(int &xx, int &yy) { + int snum = get_mouse_unscaled(xx, yy); + float s = scale(snum); + xx = xx/s; + yy = yy/s; + return snum; +} + +//////////////////////////////////////////////////////////////// +// Code used for copy and paste and DnD into the program: +//static Window fl_dnd_source_window; + +static char *fl_selection_buffer[2]; +static int fl_selection_length[2]; +static const char * fl_selection_type[2]; +static int fl_selection_buffer_length[2]; +static char fl_i_own_selection[2] = {0,0}; +static struct wl_data_offer *fl_selection_offer = NULL; +static const char *fl_selection_offer_type = NULL; +// The MIME type Wayland uses for text-containing clipboard: +static const char wld_plain_text_clipboard[] = "text/plain;charset=utf-8"; + + +int Fl_Wayland_Screen_Driver::clipboard_contains(const char *type) +{ + return fl_selection_type[1] == type; +} + + +struct data_source_write_struct { + size_t rest; + char *from; +}; + +void write_data_source_cb(FL_SOCKET fd, data_source_write_struct *data) { + while (data->rest) { + ssize_t n = write(fd, data->from, data->rest); + if (n == -1) { + if (errno == EAGAIN) return; + Fl::error("write_data_source_cb: error while writing clipboard data\n"); + break; + } + data->from += n; + data->rest -= n; + } + Fl::remove_fd(fd, FL_WRITE); + delete data; + close(fd); +} + +static void data_source_handle_send(void *data, struct wl_data_source *source, const char *mime_type, int fd) { + fl_intptr_t rank = (fl_intptr_t)data; +//fprintf(stderr, "data_source_handle_send: %s fd=%d l=%d\n", mime_type, fd, fl_selection_length[1]); + if (strcmp(mime_type, wld_plain_text_clipboard) == 0 || strcmp(mime_type, "text/plain") == 0 || strcmp(mime_type, "image/bmp") == 0) { + data_source_write_struct *write_data = new data_source_write_struct; + write_data->rest = fl_selection_length[rank]; + write_data->from = fl_selection_buffer[rank]; + Fl::add_fd(fd, FL_WRITE, (Fl_FD_Handler)write_data_source_cb, write_data); + } else { + Fl::error("Destination client requested unsupported MIME type: %s\n", mime_type); + close(fd); + } +} + +static Fl_Window *fl_dnd_target_window = 0; +static bool doing_dnd = false; // true when DnD is in action +static wl_surface *dnd_icon = NULL; // non null when DnD uses text as cursor +static wl_cursor* save_cursor = NULL; // non null when DnD uses "dnd-copy" cursor + +static void data_source_handle_cancelled(void *data, struct wl_data_source *source) { + // An application has replaced the clipboard contents or DnD finished +//fprintf(stderr, "data_source_handle_cancelled: %p\n", source); + wl_data_source_destroy(source); + doing_dnd = false; + if (dnd_icon) { + Fl_Offscreen off = (Fl_Offscreen)wl_surface_get_user_data(dnd_icon); + struct wld_window fake_window; + fake_window.buffer = off; + Fl_Wayland_Graphics_Driver::buffer_release(&fake_window); + wl_surface_destroy(dnd_icon); + dnd_icon = NULL; + } + fl_i_own_selection[1] = 0; + if (data == 0) { // at end of DnD + if (save_cursor) { + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + scr_driver->default_cursor(save_cursor); + scr_driver->set_cursor(); + save_cursor = NULL; + } + if (fl_dnd_target_window) { + Fl::handle(FL_DND_LEAVE, fl_dnd_target_window); + fl_dnd_target_window = 0; + } + Fl::pushed(0); + } +} + + +static void data_source_handle_target(void *data, struct wl_data_source *source, const char *mime_type) { + if (mime_type != NULL) { + //printf("Destination would accept MIME type if dropped: %s\n", mime_type); + } else { + //printf("Destination would reject if dropped\n"); + } +} + +static uint32_t last_dnd_action = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + +static void data_source_handle_action(void *data, struct wl_data_source *source, uint32_t dnd_action) { + last_dnd_action = dnd_action; + switch (dnd_action) { + case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: + //printf("Destination would perform a copy action if dropped\n"); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: + //printf("Destination would reject the drag if dropped\n"); + break; + } +} + +static void data_source_handle_dnd_drop_performed(void *data, struct wl_data_source *source) { + //printf("Drop performed\n"); +} + +static void data_source_handle_dnd_finished(void *data, struct wl_data_source *source) { + switch (last_dnd_action) { + case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: + //printf("Destination has accepted the drop with a move action\n"); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: + //printf("Destination has accepted the drop with a copy action\n"); + break; + } +} + +static const struct wl_data_source_listener data_source_listener = { + .target = data_source_handle_target, + .send = data_source_handle_send, + .cancelled = data_source_handle_cancelled, + .dnd_drop_performed = data_source_handle_dnd_drop_performed, + .dnd_finished = data_source_handle_dnd_finished, + .action = data_source_handle_action, +}; + + +static Fl_Offscreen offscreen_from_text(const char *text, int scale) { + const char *p, *q; + int width = 0, height, w2, ltext = strlen(text); + fl_font(FL_HELVETICA, 10 * scale); + p = text; + int nl = 0; + while(nl < 20 && (q=strchr(p, '\n')) != NULL) { + nl++; + w2 = int(fl_width(p, q - p)); + if (w2 > width) width = w2; + p = q + 1; + } + if (nl < 20 && text[ ltext - 1] != '\n') { + nl++; + w2 = int(fl_width(p)); + if (w2 > width) width = w2; + } + if (width > 300*scale) width = 300*scale; + height = nl * fl_height() + 3; + width += 6; + Fl_Offscreen off = Fl_Wayland_Graphics_Driver::create_shm_buffer(width, height); + memset(off->draw_buffer, 0, off->data_size); + Fl_Image_Surface *surf = new Fl_Image_Surface(width, height, 0, off); + Fl_Surface_Device::push_current(surf); + p = text; + fl_font(FL_HELVETICA, 10 * scale); + int y = fl_height(); + while (nl > 0) { + q = strchr(p, '\n'); + if (q) { + fl_draw(p, q - p, 3, y); + } else { + fl_draw(p, 3, y); + break; + } + y += fl_height(); + p = q + 1; + nl--; + } + Fl_Surface_Device::pop_current(); + delete surf; + cairo_surface_flush( cairo_get_target(off->cairo_) ); + memcpy(off->data, off->draw_buffer, off->data_size); + return off; +} + + +int Fl_Wayland_Screen_Driver::dnd(int use_selection) { + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + + struct wl_data_source *source = + wl_data_device_manager_create_data_source(scr_driver->seat->data_device_manager); + // we transmit the adequate value of index in fl_selection_buffer[index] + wl_data_source_add_listener(source, &data_source_listener, (void*)0); + wl_data_source_offer(source, wld_plain_text_clipboard); + wl_data_source_set_actions(source, WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY); + Fl_Offscreen off = NULL; + int s = 1; + if (use_selection) { + // use the text as dragging icon + Fl_Widget *current = Fl::pushed() ? Fl::pushed() : Fl::first_window(); + s = fl_xid(current->top_window())->scale; + off = offscreen_from_text(fl_selection_buffer[0], s); + dnd_icon = wl_compositor_create_surface(scr_driver->wl_compositor); + } else dnd_icon = NULL; + doing_dnd = true; + wl_data_device_start_drag(scr_driver->seat->data_device, source, + scr_driver->seat->pointer_focus, dnd_icon, scr_driver->seat->serial); + if (use_selection) { + wl_surface_attach(dnd_icon, off->wl_buffer, 0, 0); + wl_surface_set_buffer_scale(dnd_icon, s); + wl_surface_damage(dnd_icon, 0, 0, 10000, 10000); + wl_surface_commit(dnd_icon); + wl_surface_set_user_data(dnd_icon, off); + } else { + static struct wl_cursor *dnd_cursor = scr_driver->cache_cursor("dnd-copy"); + if (dnd_cursor) { + save_cursor = scr_driver->default_cursor(); + scr_driver->default_cursor(dnd_cursor); + scr_driver->set_cursor(); + } else save_cursor = NULL; + } + return 1; +} + + +static void data_offer_handle_offer(void *data, struct wl_data_offer *offer, const char *mime_type) { + // runs when app becomes active and lists possible clipboard types +//fprintf(stderr, "Clipboard offer=%p supports MIME type: %s\n", offer, mime_type); + if (strcmp(mime_type, "image/png") == 0) { + fl_selection_type[1] = Fl::clipboard_image; + fl_selection_offer_type = "image/png"; + } else if (strcmp(mime_type, "image/bmp") == 0 && (!fl_selection_offer_type || strcmp(fl_selection_offer_type, "image/png"))) { + fl_selection_type[1] = Fl::clipboard_image; + fl_selection_offer_type = "image/bmp"; + } else if (strcmp(mime_type, wld_plain_text_clipboard) == 0 && !fl_selection_type[1]) { + fl_selection_type[1] = Fl::clipboard_plain_text; + } +} + + +static void data_offer_handle_source_actions(void *data, struct wl_data_offer *offer, uint32_t actions) { + if (actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY) { + //printf("Drag supports the copy action\n"); + } +} + +static void data_offer_handle_action(void *data, struct wl_data_offer *offer, uint32_t dnd_action) { + switch (dnd_action) { + case WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE: + //printf("A move action would be performed if dropped\n"); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY: + //printf("A copy action would be performed if dropped\n"); + break; + case WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE: + //printf("The drag would be rejected if dropped\n"); + break; + } +} + +static const struct wl_data_offer_listener data_offer_listener = { + .offer = data_offer_handle_offer, + .source_actions = data_offer_handle_source_actions, + .action = data_offer_handle_action, +}; + +static void data_device_handle_data_offer(void *data, struct wl_data_device *data_device, struct wl_data_offer *offer) { + // An application has created a new data source +//fprintf(stderr, "data_device_handle_data_offer offer=%p\n", offer); + fl_selection_type[1] = NULL; + fl_selection_offer_type = NULL; + wl_data_offer_add_listener(offer, &data_offer_listener, NULL); +} + + +static void data_device_handle_selection(void *data, struct wl_data_device *data_device, struct wl_data_offer *offer) { + // An application has set the clipboard contents. W +//fprintf(stderr, "data_device_handle_selection\n"); + if (fl_selection_offer) wl_data_offer_destroy(fl_selection_offer); + fl_selection_offer = offer; +//if (offer == NULL) fprintf(stderr, "Clipboard is empty\n"); +} + + +static size_t convert_crlf(char *s, size_t len) +{ // turn \r characters into \n and "\r\n" sequences into \n: + char *p; + size_t l = len; + while ((p = strchr(s, '\r'))) { + if (*(p+1) == '\n') { + memmove(p, p+1, l-(p-s)); + len--; l--; + } else *p = '\n'; + l -= p-s; + s = p + 1; + } + return len; +} + + +// Gets from the system the clipboard or dnd text and puts it in fl_selection_buffer[1] +// which is enlarged if necessary. +static void get_clipboard_or_dragged_text(struct wl_data_offer *offer) { + int fds[2]; + if (pipe(fds)) return; + wl_data_offer_receive(offer, wld_plain_text_clipboard, fds[1]); + close(fds[1]); + wl_display_flush(fl_display); + // read in fl_selection_buffer + char *to = fl_selection_buffer[1]; + ssize_t rest = fl_selection_buffer_length[1]; + while (rest) { + ssize_t n = read(fds[0], to, rest); + if (n <= 0) { + close(fds[0]); + fl_selection_length[1] = to - fl_selection_buffer[1]; + fl_selection_buffer[1][ fl_selection_length[1] ] = 0; + return; + } + n = convert_crlf(to, n); + to += n; + rest -= n; + } + // compute size of unread clipboard data + rest = fl_selection_buffer_length[1]; + while (true) { + char buf[1000]; + ssize_t n = read(fds[0], buf, sizeof(buf)); + if (n <= 0) { + close(fds[0]); + break; + } + rest += n; + } +//fprintf(stderr, "get_clipboard_or_dragged_text: size=%ld\n", rest); + // read full clipboard data + if (pipe(fds)) return; + wl_data_offer_receive(offer, wld_plain_text_clipboard, fds[1]); + close(fds[1]); + wl_display_flush(fl_display); + if (rest+1 > fl_selection_buffer_length[1]) { + delete[] fl_selection_buffer[1]; + fl_selection_buffer[1] = new char[rest+1000+1]; + fl_selection_buffer_length[1] = rest+1000; + } + char *from = fl_selection_buffer[1]; + while (true) { + ssize_t n = read(fds[0], from, rest); + if (n <= 0) { + close(fds[0]); + break; + } + n = convert_crlf(from, n); + from += n; + } + fl_selection_length[1] = from - fl_selection_buffer[1];; + fl_selection_buffer[1][fl_selection_length[1]] = 0; + Fl::e_clipboard_type = Fl::clipboard_plain_text; +} + +static struct wl_data_offer *current_drag_offer = NULL; +static uint32_t fl_dnd_serial; + + +static void data_device_handle_enter(void *data, struct wl_data_device *data_device, uint32_t serial, + struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y, struct wl_data_offer *offer) { + Fl_Window *win = Fl_Wayland_Screen_Driver::surface_to_window(surface); +//printf("Drag entered our surface %p(win=%p) at %dx%d\n", surface, win, wl_fixed_to_int(x), wl_fixed_to_int(y)); + if (win) { + float f = Fl::screen_scale(win->screen_num()); + fl_dnd_target_window = win; + Fl::e_x = wl_fixed_to_int(x) / f; + Fl::e_x_root = Fl::e_x + fl_dnd_target_window->x(); + Fl::e_y = wl_fixed_to_int(y) / f; + Fl::e_y_root = Fl::e_y + fl_dnd_target_window->y(); + Fl::handle(FL_DND_ENTER, fl_dnd_target_window); + current_drag_offer = offer; + fl_dnd_serial = serial; + } + uint32_t supported_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY; + uint32_t preferred_action = supported_actions; + wl_data_offer_set_actions(offer, supported_actions, preferred_action); +} + +static void data_device_handle_motion(void *data, struct wl_data_device *data_device, uint32_t time, + wl_fixed_t x, wl_fixed_t y) { + if (!current_drag_offer) return; +//printf("data_device_handle_motion fl_dnd_target_window=%p\n", fl_dnd_target_window); + int ret = 0; + if (fl_dnd_target_window) { + float f = Fl::screen_scale(fl_dnd_target_window->screen_num()); + Fl::e_x = wl_fixed_to_int(x) / f; + Fl::e_x_root = Fl::e_x + fl_dnd_target_window->x(); + Fl::e_y = wl_fixed_to_int(y) / f; + Fl::e_y_root = Fl::e_y + fl_dnd_target_window->y(); + ret = Fl::handle(FL_DND_DRAG, fl_dnd_target_window); + } + uint32_t supported_actions = ret ? WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY : WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE; + uint32_t preferred_action = supported_actions; + wl_data_offer_set_actions(current_drag_offer, supported_actions, preferred_action); + wl_display_roundtrip(fl_display); + if (ret && current_drag_offer) wl_data_offer_accept(current_drag_offer, fl_dnd_serial, "text/plain"); +} + +static void data_device_handle_leave(void *data, struct wl_data_device *data_device) { +//printf("Drag left our surface\n"); +} + + +static void data_device_handle_drop(void *data, struct wl_data_device *data_device) { + if (!current_drag_offer) return; + int ret = Fl::handle(FL_DND_RELEASE, fl_dnd_target_window); +//printf("data_device_handle_drop ret=%d doing_dnd=%d\n", ret, doing_dnd); + + if (!ret) { + wl_data_offer_destroy(current_drag_offer); + current_drag_offer = NULL; + return; + } + + if (doing_dnd) { + Fl::e_text = fl_selection_buffer[0]; + Fl::e_length = fl_selection_length[0]; + } else { + get_clipboard_or_dragged_text(current_drag_offer); + Fl::e_text = fl_selection_buffer[1]; + Fl::e_length = fl_selection_length[1]; + } + int old_event = Fl::e_number; + Fl::belowmouse()->handle(Fl::e_number = FL_PASTE); + Fl::e_number = old_event; + + wl_data_offer_finish(current_drag_offer); + wl_data_offer_destroy(current_drag_offer); + current_drag_offer = NULL; +} + +static const struct wl_data_device_listener data_device_listener = { + .data_offer = data_device_handle_data_offer, + .enter = data_device_handle_enter, + .leave = data_device_handle_leave, + .motion = data_device_handle_motion, + .drop = data_device_handle_drop, + .selection = data_device_handle_selection, +}; + + +const struct wl_data_device_listener *Fl_Wayland_Screen_Driver::p_data_device_listener = &data_device_listener; + + +static void read_int(uchar *c, int& i) { + i = *c; + i |= (*(++c))<<8; + i |= (*(++c))<<16; + i |= (*(++c))<<24; +} + + +// Reads from the clipboard an image which can be in image/bmp or image/png MIME type. +// Returns 0 if OK, != 0 if error. +static int get_clipboard_image() { + int fds[2]; + if (pipe(fds)) return 1; + wl_data_offer_receive(fl_selection_offer, fl_selection_offer_type, fds[1]); + close(fds[1]); + wl_display_roundtrip(fl_display); + if (strcmp(fl_selection_offer_type, "image/png") == 0) { + char tmp_fname[21]; + Fl_Shared_Image *shared = 0; + strcpy(tmp_fname, "/tmp/clipboardXXXXXX"); + int fd = mkstemp(tmp_fname); + if (fd == -1) return 1; + while (true) { + char buf[10000]; + ssize_t n = read(fds[0], buf, sizeof(buf)); + if (n <= 0) { + close(fds[0]); + close(fd); + break; + } + n = write(fd, buf, n); + } + shared = Fl_Shared_Image::get(tmp_fname); + fl_unlink(tmp_fname); + if (!shared) return 1; + int ld = shared->ld() ? shared->ld() : shared->w() * shared->d(); + uchar *rgb = new uchar[shared->w() * shared->h() * shared->d()]; + memcpy(rgb, shared->data()[0], ld * shared->h() ); + Fl_RGB_Image *image = new Fl_RGB_Image(rgb, shared->w(), shared->h(), shared->d(), shared->ld()); + shared->release(); + image->alloc_array = 1; + Fl::e_clipboard_data = (void*)image; + } else { // process image/bmp + uchar buf[54]; + size_t rest = 1; + char *bmp = NULL; + ssize_t n = read(fds[0], buf, sizeof(buf)); // read size info of the BMP image + if (n == sizeof(buf)) { + int w, h; // size of the BMP image + read_int(buf + 18, w); + read_int(buf + 22, h); + int R = ((3*w+3)/4) * 4; // the number of bytes per row of BMP image, rounded up to multiple of 4 + bmp = new char[R * h + 54]; + memcpy(bmp, buf, 54); + char *from = bmp + 54; + rest = R * h; + while (rest) { + ssize_t n = read(fds[0], from, rest); + if (n <= 0) break; + from += n; + rest -= n; + } +//fprintf(stderr, "get_clipboard_image: image/bmp %dx%d rest=%lu\n", w,h,rest); + } + close(fds[0]); + if (!rest) Fl::e_clipboard_data = Fl_Unix_System_Driver::own_bmp_to_RGB(bmp); + delete[] bmp; + if (rest) return 1; + } + Fl::e_clipboard_type = Fl::clipboard_image; + return 0; +} + + +void Fl_Wayland_Screen_Driver::paste(Fl_Widget &receiver, int clipboard, const char *type) { + if (clipboard != 1) return; + if (fl_i_own_selection[1]) { + // We already have it, do it quickly without compositor. + if (type == Fl::clipboard_plain_text && fl_selection_type[1] == type) { + Fl::e_text = fl_selection_buffer[1]; + Fl::e_length = fl_selection_length[1]; + if (!Fl::e_text) Fl::e_text = (char *)""; + } else if (type == Fl::clipboard_image && fl_selection_type[1] == type) { + Fl::e_clipboard_data = Fl_Unix_System_Driver::own_bmp_to_RGB(fl_selection_buffer[1]); + Fl::e_clipboard_type = Fl::clipboard_image; + } else return; + receiver.handle(FL_PASTE); + return; + } + // otherwise get the compositor to return it: + if (!fl_selection_offer) return; + if (type == Fl::clipboard_plain_text && clipboard_contains(Fl::clipboard_plain_text)) { + get_clipboard_or_dragged_text(fl_selection_offer); + Fl::e_text = fl_selection_buffer[1]; + Fl::e_length = fl_selection_length[1]; + receiver.handle(FL_PASTE); + } else if (type == Fl::clipboard_image && clipboard_contains(Fl::clipboard_image)) { + if (get_clipboard_image()) return; + Window xid = fl_xid(receiver.top_window()); + if (xid && xid->scale > 1) { + Fl_RGB_Image *rgb = (Fl_RGB_Image*)Fl::e_clipboard_data; + rgb->scale(rgb->data_w() / xid->scale, rgb->data_h() / xid->scale); + } + int done = receiver.handle(FL_PASTE); + Fl::e_clipboard_type = ""; + if (done == 0) { + delete (Fl_RGB_Image*)Fl::e_clipboard_data; + Fl::e_clipboard_data = NULL; + } + } +} + + +void Fl_Wayland_Screen_Driver::copy(const char *stuff, int len, int clipboard, const char *type) { + if (!stuff || len < 0) return; + + if (clipboard >= 2) + clipboard = 1; // Only on X11 do multiple clipboards make sense. + + if (len+1 > fl_selection_buffer_length[clipboard]) { + delete[] fl_selection_buffer[clipboard]; + fl_selection_buffer[clipboard] = new char[len+100]; + fl_selection_buffer_length[clipboard] = len+100; + } + memcpy(fl_selection_buffer[clipboard], stuff, len); + fl_selection_buffer[clipboard][len] = 0; // needed for direct paste + fl_selection_length[clipboard] = len; + fl_i_own_selection[clipboard] = 1; + fl_selection_type[clipboard] = Fl::clipboard_plain_text; + if (clipboard == 1) { + Fl_Wayland_Screen_Driver *scr_driver = (Fl_Wayland_Screen_Driver*)Fl::screen_driver(); + scr_driver->seat->data_source = wl_data_device_manager_create_data_source(scr_driver->seat->data_device_manager); + // we transmit the adequate value of index in fl_selection_buffer[index] + wl_data_source_add_listener(scr_driver->seat->data_source, &data_source_listener, (void*)1); + wl_data_source_offer(scr_driver->seat->data_source, wld_plain_text_clipboard); + wl_data_device_set_selection(scr_driver->seat->data_device, scr_driver->seat->data_source, scr_driver->seat->keyboard_enter_serial); +//fprintf(stderr, "wl_data_device_set_selection len=%d to %d\n", len, clipboard); + } +} + + +// takes a raw RGB image and puts it in the copy/paste buffer +void Fl_Wayland_Screen_Driver::copy_image(const unsigned char *data, int W, int H){ + if (!data || W <= 0 || H <= 0) return; + delete[] fl_selection_buffer[1]; + fl_selection_buffer[1] = (char *)Fl_Unix_System_Driver::create_bmp(data,W,H,&fl_selection_length[1]); + fl_selection_buffer_length[1] = fl_selection_length[1]; + fl_i_own_selection[1] = 1; + fl_selection_type[1] = Fl::clipboard_image; + seat->data_source = wl_data_device_manager_create_data_source(seat->data_device_manager); + // we transmit the adequate value of index in fl_selection_buffer[index] + wl_data_source_add_listener(seat->data_source, &data_source_listener, (void*)1); + wl_data_source_offer(seat->data_source, "image/bmp"); + wl_data_device_set_selection(seat->data_device, seat->data_source, seat->keyboard_enter_serial); +//fprintf(stderr, "copy_image: len=%d\n", fl_selection_length[1]); +} + +//////////////////////////////////////////////////////////////// +// Code for tracking clipboard changes: + +// is that possible with Wayland ? + +//////////////////////////////////////////////////////////////// + +#endif // !defined(FL_DOXYGEN) |
