diff options
61 files changed, 18947 insertions, 893 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index 979878a3e..4cc6c249a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,4 +1,4 @@ -Changes in FLTK 1.4.0 Released: ??? ?? 2021 +Changes in FLTK 1.4.0 Released: ??? ?? 2022 General Information about this Release @@ -17,6 +17,8 @@ Changes in FLTK 1.4.0 Released: ??? ?? 2021 New Features and Extensions + - FLTK 1.4 introduces a new platform, Wayland, available for recent Unix + distributions. More information in README.Wayland.txt - Windows platform: added support for using a manifest to set the application's level of DPI awareness (issue #309). - X11 platform: class Fl_Native_File_Chooser will run the KDE file dialog @@ -121,6 +123,8 @@ Changes in FLTK 1.4.0 Released: ??? ?? 2021 New Configuration Options (ABI Version) + - Configure option --enable-wayland allows to build the FLTK library for + the new Wayland platform. The corresponding CMake option is OPTION_USE_WAYLAND. - The new configure option --disable-gdiplus removes the possibility to draw antialiased lines and curves on the Windows platform. The corresponding CMake option is OPTION_USE_GDIPLUS. diff --git a/CMake/options.cmake b/CMake/options.cmake index eb8f1fe92..602663fe6 100644 --- a/CMake/options.cmake +++ b/CMake/options.cmake @@ -50,6 +50,24 @@ set (FL_ABI_VERSION ${OPTION_ABI_VERSION}) if (UNIX) option (OPTION_CREATE_LINKS "create backwards compatibility links" OFF) list (APPEND FLTK_LDLIBS -lm) + option (OPTION_USE_WAYLAND "use Wayland" OFF) + if (OPTION_USE_WAYLAND) + set (FLTK_USE_WAYLAND 1) + option (OPTION_USE_SYSTEM_LIBDECOR "use libdecor from the system" OFF) + unset (OPTION_USE_XRENDER CACHE) + unset (OPTION_USE_XINERAMA CACHE) + unset (OPTION_USE_XFT CACHE) + unset (OPTION_USE_XCURSOR CACHE) + unset (OPTION_USE_XFIXES CACHE) + unset (OPTION_USE_PANGO CACHE) + set (OPTION_USE_PANGO TRUE CACHE BOOL "use lib Pango") + if (OPTION_USE_SYSTEM_LIBDECOR) + pkg_check_modules(SYSTEM_LIBDECOR libdecor-0) + if (NOT SYSTEM_LIBDECOR_FOUND) + set (OPTION_USE_SYSTEM_LIBDECOR OFF) + endif (NOT SYSTEM_LIBDECOR_FOUND) + endif (OPTION_USE_SYSTEM_LIBDECOR) + endif (OPTION_USE_WAYLAND) endif (UNIX) if (WIN32) @@ -72,7 +90,7 @@ endif (APPLE) # find X11 libraries and headers set (PATH_TO_XLIBS) -if ((NOT APPLE OR OPTION_APPLE_X11) AND NOT WIN32) +if ((NOT APPLE OR OPTION_APPLE_X11) AND NOT WIN32 AND NOT OPTION_USE_WAYLAND) include (FindX11) if (X11_FOUND) set (FLTK_USE_X11 1) @@ -82,7 +100,7 @@ if ((NOT APPLE OR OPTION_APPLE_X11) AND NOT WIN32) endif (X11_Xext_FOUND) get_filename_component (PATH_TO_XLIBS ${X11_X11_LIB} PATH) endif (X11_FOUND) -endif ((NOT APPLE OR OPTION_APPLE_X11) AND NOT WIN32) +endif ((NOT APPLE OR OPTION_APPLE_X11) AND NOT WIN32 AND NOT OPTION_USE_WAYLAND) if (OPTION_APPLE_X11) if (NOT(${CMAKE_SYSTEM_VERSION} VERSION_LESS 17.0.0)) # a.k.a. macOS version ≥ 10.13 @@ -252,6 +270,8 @@ if (OPENGL_FOUND) set (GLLIBS "-lglu32 -lopengl32") elseif (APPLE AND NOT OPTION_APPLE_X11) set (GLLIBS "-framework OpenGL") + elseif (OPTION_USE_WAYLAND) + set (GLLIBS "-lwayland-egl -lEGL -lGLU -lGL") else () set (GLLIBS "-lGLU -lGL") endif (WIN32) @@ -481,7 +501,7 @@ if (X11_Xft_FOUND) endif (X11_Xft_FOUND) # test option compatibility: Pango requires Xft -if (OPTION_USE_PANGO) +if (OPTION_USE_PANGO AND NOT OPTION_USE_WAYLAND) if (NOT X11_Xft_FOUND) message (STATUS "Pango requires Xft but Xft library or headers could not be found.") message (STATUS "Please install Xft development files and try again or disable OPTION_USE_PANGO.") @@ -493,10 +513,10 @@ if (OPTION_USE_PANGO) message (FATAL_ERROR "*** Aborting ***") endif (NOT OPTION_USE_XFT) endif (NOT X11_Xft_FOUND) -endif (OPTION_USE_PANGO) +endif (OPTION_USE_PANGO AND NOT OPTION_USE_WAYLAND) ####################################################################### -if (X11_Xft_FOUND AND OPTION_USE_PANGO) +if ((X11_Xft_FOUND OR OPTION_USE_WAYLAND) AND OPTION_USE_PANGO) pkg_check_modules(PANGOXFT pangoxft) pkg_check_modules(PANGOCAIRO pangocairo) pkg_check_modules(CAIRO cairo) @@ -553,7 +573,15 @@ if (X11_Xft_FOUND AND OPTION_USE_PANGO) list (APPEND FLTK_LDLIBS -lpango-1.0 -lpangoxft-1.0 -lgobject-2.0) endif (HAVE_LIB_PANGO AND HAVE_LIB_PANGOXFT AND HAVE_LIB_GOBJECT) endif (PANGOXFT_FOUND AND PANGOCAIRO_FOUND AND CAIRO_FOUND) -endif (X11_Xft_FOUND AND OPTION_USE_PANGO) +endif ((X11_Xft_FOUND OR OPTION_USE_WAYLAND) AND OPTION_USE_PANGO) + +if (OPTION_USE_WAYLAND AND NOT OPTION_USE_SYSTEM_LIBDECOR) + pkg_check_modules(GTK gtk+-3.0) + #set (GTK_FOUND 0) #use this to get cairo titlebars rather than GTK + if (GTK_FOUND) + include_directories (${GTK_INCLUDE_DIRS}) + endif (GTK_FOUND) +endif (OPTION_USE_WAYLAND AND NOT OPTION_USE_SYSTEM_LIBDECOR) if (OPTION_USE_XFT) set (USE_XFT X11_Xft_FOUND) diff --git a/CMake/variables.cmake b/CMake/variables.cmake index 27672c7d5..8908bd513 100644 --- a/CMake/variables.cmake +++ b/CMake/variables.cmake @@ -43,6 +43,11 @@ if (WIN32) list (APPEND FLTK_LDLIBS -lole32 -luuid -lcomctl32 -lws2_32) elseif (APPLE AND NOT OPTION_APPLE_X11) list (APPEND FLTK_LDLIBS "-framework Cocoa") +elseif (OPTION_USE_WAYLAND) + list (APPEND FLTK_LDLIBS "-lwayland-cursor -lwayland-client -lxkbcommon -ldbus-1") + if (OPTION_USE_SYSTEM_LIBDECOR) + list (APPEND FLTK_LDLIBS "-ldecor-0") + endif (OPTION_USE_SYSTEM_LIBDECOR) else () list (APPEND FLTK_LDLIBS -lm) endif (WIN32) diff --git a/FL/platform.H b/FL/platform.H index 4e068c2e0..4b9d6d687 100644 --- a/FL/platform.H +++ b/FL/platform.H @@ -37,6 +37,8 @@ class Fl_Window; # include "win32.H" # elif defined(__APPLE__) # include "mac.H" +# elif defined(FLTK_USE_WAYLAND) +# include "wayland.H" # elif defined(FLTK_USE_X11) # include "x11.H" # endif // _WIN32 diff --git a/FL/platform_types.h b/FL/platform_types.h index 51963d6e4..5ea11f7f4 100644 --- a/FL/platform_types.h +++ b/FL/platform_types.h @@ -1,5 +1,5 @@ /* - * Copyright 2016-2018 by Bill Spitzak and others. + * Copyright 2016-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 @@ -116,6 +116,17 @@ typedef struct HGLRC__ *GLContext; struct dirent {char d_name[1];}; #endif +#elif defined(FLTK_USE_WAYLAND) +typedef struct fl_wld_buffer *Fl_Offscreen; /**< an offscreen drawing buffer */ +typedef struct _cairo_pattern* Fl_Bitmask; +typedef struct flWaylandRegion* Fl_Region; +typedef int FL_SOCKET; /**< socket or file descriptor */ +typedef void *EGLContext; +typedef EGLContext GLContext; +#include <sys/stat.h> +#include <sys/types.h> +#include <dirent.h> + #elif defined(FLTK_USE_X11) typedef unsigned long Fl_Offscreen; diff --git a/FL/wayland.H b/FL/wayland.H new file mode 100755 index 000000000..af0851da7 --- /dev/null +++ b/FL/wayland.H @@ -0,0 +1,31 @@ +// +// Wayland platform header file 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_PLATFORM_H) +# error "Never use <FL/wayland.H> directly; include <FL/platform.H> instead." +#endif // !FL_PLATFORM_H + +typedef struct wld_window *Window; + +extern struct wl_display *fl_display; + +struct flWaylandRegion { + int count; + struct _cairo_rectangle *rects; +}; // a region is the union of a series of rectangles + +#include <stdint.h> +extern FL_EXPORT uint32_t fl_event_time; diff --git a/README.Wayland.txt b/README.Wayland.txt new file mode 100644 index 000000000..4e90d8f45 --- /dev/null +++ b/README.Wayland.txt @@ -0,0 +1,141 @@ +README.Wayland.txt - Wayland platform support for FLTK +------------------------------------------------------ + + +CONTENTS +======== + + 1 INTRODUCTION + + 2 WAYLAND SUPPORT FOR FLTK + 2.1 Configuration + 2.2 Known limitations + + 3 PLATFORM SPECIFIC NOTES + 3.1 Debian and Derivatives (like Ubuntu) + + +1 INTRODUCTION +============== + +Version 1.4 of the FLTK library introduces support of the public FLTK API on +the Wayland platform. It requires a Wayland-equipped OS which means Linux. +Pre-existing platform-independent source code for FLTK 1.3.x should build and +run unchanged with FLTK 1.4 and the Wayland platform. +The code has been tested on Debian and Ubuntu with 3 distinct Wayland compositors: +mutter (Debian's compositor), weston, and KDE. +CJK text-input methods, as well as dead and compose keys are supported. + + +2 WAYLAND SUPPORT FOR FLTK +========================== + +It is possible to have your FLTK application do all its windowing and drawing +through the Wayland protocol on Linux systems. All graphics is done via Cairo or EGL. +All text-drawing is done via Pango. + + 2.1 Configuration +--------------- + +* Configure-based build can be performed as follows: +Once after "git clone", create the configure file : + autoconf -f + +Prepare build with : + ./configure --enable-wayland [--enable-shared] + +Build with : + make + +* CMake-based build can be performed as follows: +cmake -S <path-to-source> -B <path-to-build> -DCMAKE_BUILD_TYPE=Release -DOPTION_USE_WAYLAND=1 + +cd <path-to-build>; make + +The FLTK wayland platform uses a library called libdecor which handles window decorations +(i.e., titlebars, shade). Libdecor is bundled in the FLTK source code and FLTK uses by default +this form of libdecor. Optionally, OPTION_USE_SYSTEM_LIBDECOR can be turned on to have FLTK +use the system's version of libdecor which is available on recent Linux distributions (e.g., +Debian bookworm or more recent in packages libdecor-0-0 and libdecor-0-plugin-1-cairo). + + 2.2 Known limitations +------------------------------- + +* A deliberate design trait of Wayland makes application windows ignorant of their exact +placement on screen. It's possible, though, to position a popup window relatively to another +window. This allows FLTK to properly position menu and tooltip windows. But Fl_Window::position() +has no effect on other top-level windows. + +* With Wayland, there is no way to know if a window is currently minimized, nor is there any +way to programmatically unset minimization of a window. Consequently, Fl_Window::show() of +a minimized window has no effect. + +* With GTK-style window titlebars, the minimum width of a window is currently set at 134 pixels. + +* The library should support multi-display configurations in principle, but has not been +tested in that situation. + +* Text input methods have been tested without any understanding of the writing systems, +so feedback on this subject would be helpful. + +* While platform-independent source code prepared for FLTK 1.3 is expected to be compatible +with FLTK 1.4 and the Wayland platform, X11-specific code will not compile. In particular, +the common FLTK 1.3 construct : + #ifdef __APPLE__ + *** macOS-specific code *** + #elif defined(_WIN32) + *** Windows-specific code *** + #else + *** X11-specific code *** + #endif +will choke at compile time because it exposes X11-specific code to the non-X11, Wayland +environment. This should be written instead : + #include <FL/fl_config.h> + + #ifdef __APPLE__ + *** macOS-specific code *** + #elif defined(_WIN32) + *** Windows-specific code *** + #elif defined(FLTK_USE_X11) + *** X11-specific code *** + #endif +Moreover, the new FLTK_USE_WAYLAND preprocessor variable is available to bracket +Wayland-specific source code. + + +3 PLATFORM SPECIFIC NOTES +========================= + +The following are notes about building FLTK for the Wayland platform +on the various supported Linux distributions. + + 3.1 Debian and Derivatives (like Ubuntu) + ---------------------------------------- +Under Debian, the Wayland platform requires version 11 (a.k.a. Bullseye) or more recent. +Under Ubuntu, the Wayland platform is known to work with version 20.04 (focal fossa) or more recent. + +These packages are necessary to build the FLTK library, in addition to those present +in a basic Debian/Ubuntu distribution : +- g++ +- gdb +- make +- git +- autoconf +- libglu1-mesa-dev +- libpango1.0-dev +- libwayland-dev +- wayland-protocols +- libdbus-1-dev +- libxkbcommon-dev +- libgtk-3-dev <== with this, windows get a GTK-style titlebar +- libglew-dev <== necessary to use OpenGL version 3 or above +- cmake <== if you plan to build with CMake +- cmake-qt-gui <== if you plan to use the GUI of CMake + +These further packages are necessary to run FLTK apps under the Gnome-Wayland desktop: +- gnome +- gnome-session-wayland + +These packages allow to run FLTK apps under the KDE/Plasma-Wayland desktop: +- kde-plasma-desktop +- plasma-workspace-wayland diff --git a/cairo/Fl_Cairo.cxx b/cairo/Fl_Cairo.cxx index 3a5b59f73..a6b2043ae 100644 --- a/cairo/Fl_Cairo.cxx +++ b/cairo/Fl_Cairo.cxx @@ -1,7 +1,7 @@ // // Main header file for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2021 by Bill Spitzak and others. +// 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 @@ -30,6 +30,9 @@ # include <cairo-win32.h> #elif defined(__APPLE_QUARTZ__) // macOS # include <cairo-quartz.h> +#elif defined(FLTK_USE_WAYLAND) +# include "../src/drivers/Wayland/Fl_Wayland_Graphics_Driver.H" +# include "../src/drivers/Wayland/Fl_Wayland_Window_Driver.H" #else # error Cairo is not supported on this platform. #endif @@ -69,7 +72,13 @@ void Fl_Cairo_State::autolink(bool b) { */ cairo_t * Fl::cairo_make_current(Fl_Window* wi) { if (!wi) return NULL; // Precondition - + cairo_t * cairo_ctxt; +#if defined(FLTK_USE_WAYLAND) + Window xid = fl_xid(wi); + if (!xid->buffer) return NULL; // this may happen with GL windows + cairo_ctxt = xid->buffer->cairo_; + cairo_state_.cc(cairo_ctxt, false); +#else // FLTK_USE_WAYLAND if (fl_gc==0) { // means remove current cc Fl::cairo_cc(0); // destroy any previous cc cairo_state_.window(0); @@ -82,7 +91,6 @@ cairo_t * Fl::cairo_make_current(Fl_Window* wi) { cairo_state_.window(wi); - cairo_t * cairo_ctxt; #ifndef __APPLE__ float scale = Fl::screen_scale(wi->screen_num()); // get the screen scaling factor #endif @@ -95,9 +103,11 @@ cairo_t * Fl::cairo_make_current(Fl_Window* wi) { #ifndef __APPLE__ cairo_scale(cairo_ctxt, scale, scale); #endif +#endif // FLTK_USE_WAYLAND return cairo_ctxt; } +#if !defined(FLTK_USE_WAYLAND) /* Creates transparently a cairo_surface_t object. gc is an HDC context in Windows, a CGContext* in Quartz, and @@ -176,6 +186,9 @@ cairo_t * Fl::cairo_make_current(void *gc, int W, int H) { cairo_surface_destroy(s); return c; } + +#endif // !FLTK_USE_WAYLAND + #else // just don't leave the libfltk_cairo lib empty to avoid warnings #include <FL/Fl_Export.H> diff --git a/configure.ac b/configure.ac index 311108b36..e4c4eb2a4 100644 --- a/configure.ac +++ b/configure.ac @@ -3,7 +3,7 @@ dnl the "configure" script is made from this by running GNU "autoconf" dnl dnl Configuration script for the Fast Light Tool Kit (FLTK). dnl -dnl Copyright 1998-2021 by Bill Spitzak and others. +dnl Copyright 1998-2022 by Bill Spitzak and others. dnl dnl This library is free software. Distribution and use rights are outlined in dnl the file "COPYING" which should have been included with this file. If this @@ -110,6 +110,8 @@ AC_ARG_ENABLE([localzlib], AS_HELP_STRING([--enable-localzlib], [use local ZLIB AC_ARG_ENABLE([pango], AS_HELP_STRING([--enable-pango], [turn on Pango support])) +AC_ARG_ENABLE([wayland], AS_HELP_STRING([--enable-wayland], [turn on Wayland support])) + AC_ARG_ENABLE([print], AS_HELP_STRING([--disable-print], [turn off print support (X11)])) AS_IF([test x$enable_print = xno], [ AC_DEFINE([FL_NO_PRINT_SUPPORT], [Disable X11 print support?]) @@ -897,6 +899,7 @@ dnl Define OS-specific stuff... HLINKS= OSX_ONLY=: THREADS= +LIBDECORDIR="" AC_ARG_WITH([links], AS_HELP_STRING([--with-links], [make header links for common misspellings (default=no)])) @@ -995,6 +998,64 @@ AS_CASE([$host_os_gui], [cygwin* | mingw*], [ THREADS="threads$EXEEXT" ]) + AS_IF([test x$enable_wayland = xyes], [ + dnl Prepare for Wayland... + + AS_IF([test x$PKGCONFIG = x], [ + dnl pkg-config is not available, issue warning and abort... + AC_MSG_WARN([--enable-wayland: please install pkg-config.]) + AC_MSG_ERROR([Aborting.]) + ]) + + BUILD="WAYLAND" + AC_DEFINE([FLTK_USE_WAYLAND]) + CFLAGS="$CFLAGS -DUSE_SYSTEM_LIBDECOR=0" + CXXFLAGS="$CXXFLAGS -DUSE_SYSTEM_LIBDECOR=0" + graphics="Wayland" + LIBS="$LIBS $($PKGCONFIG --libs wayland-cursor) $($PKGCONFIG --libs wayland-client) $($PKGCONFIG --libs xkbcommon)" + LIBS="$LIBS $($PKGCONFIG --libs dbus-1) -ldl" + CXXFLAGS="$CXXFLAGS -I../libdecor/src" + DSOFLAGS="$LIBS $DSOFLAGS" + enable_pango=yes + LIBDECORDIR="libdecor/build" + LDFLAGS="$LDFLAGS -rdynamic -no-pie" + + AC_SEARCH_LIBS([dlopen], [dl]) + AC_CHECK_HEADER([GL/gl.h], [AC_DEFINE([HAVE_GL])]) + AC_CHECK_HEADER([GL/glu.h], [ + AC_DEFINE([HAVE_GL_GLU_H]) + GLLIBS="$($PKGCONFIG --libs wayland-egl) $($PKGCONFIG --libs egl) $($PKGCONFIG --libs glu) $GLLIBS" + ]) + + dnl Check for GTK-3 ... + gtk_found=no + CFLAGS="$($PKGCONFIG --cflags gtk+-3.0) $CFLAGS" + AC_CHECK_HEADERS([gtk/gtk.h], [ + CFLAGS="$CFLAGS -DHAVE_GTK" + LIBS="$LIBS $($PKGCONFIG --libs gtk+-3.0)" + gtk_found=yes + ]) + + dnl Check for the Pango library ... + pango_found=no + CFLAGS="$($PKGCONFIG --cflags pangocairo) $CFLAGS" + CXXFLAGS="$($PKGCONFIG --cflags pangocairo) $CXXFLAGS" + LIBS="$LIBS $($PKGCONFIG --libs pangocairo)" + + AC_CHECK_HEADERS([pango/pangocairo.h], [ + AC_DEFINE([USE_PANGO]) + AC_DEFINE([USE_XFT]) + pango_found=yes + ]) + + dnl Early abort if Pango could not be found + AS_IF([test x$pango_found != xyes], [ + AC_MSG_NOTICE([--enable-wayland: Pango libs and/or headers could not be found.]) + AC_MSG_ERROR([Aborting.]) + ]) + + ], [ + dnl Check for X11... AC_PATH_XTRA @@ -1016,7 +1077,7 @@ AS_CASE([$host_os_gui], [cygwin* | mingw*], [ AS_IF([test "x$x_includes" != x], [ ac_cpp="$ac_cpp -I$x_includes" ]) - +]) dnl Check for OpenGL unless disabled... GLLIBS= @@ -1217,6 +1278,8 @@ AC_SUBST([HLINKS]) AC_SUBST([OSX_ONLY]) AC_SUBST([THREADS]) +AC_SUBST([LIBDECORDIR]) + AC_SUBST([INSTALL_DESKTOP]) AC_SUBST([UNINSTALL_DESKTOP]) @@ -1344,17 +1407,21 @@ AS_IF([test -n "$GCC"], [ [AC_MSG_RESULT(no)]) CFLAGS="$OLDCFLAGS" - dnl Make sure that shared libraries don't have undefined references - # See if ld supports -no-undefined... - AC_MSG_CHECKING([if ld supports -no-undefined]) - OLDLDFLAGS="$LDFLAGS" - LDFLAGS="$LDFLAGS -Wl,-no-undefined" - AC_LINK_IFELSE( - [AC_LANG_PROGRAM([[]], [[]])], - [DSOFLAGS="$DSOFLAGS -Wl,-no-undefined" - AC_MSG_RESULT(yes)], - [AC_MSG_RESULT(no)]) - LDFLAGS="$OLDLDFLAGS" + AS_IF([test x$enable_wayland = xyes],[ + DSOFLAGS="$DSOFLAGS -Wl,--allow-shlib-undefined" + ] , [ + dnl Make sure that shared libraries don't have undefined references + # See if ld supports -no-undefined... + AC_MSG_CHECKING([if ld supports -no-undefined]) + OLDLDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS -Wl,-no-undefined" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[]], [[]])], + [DSOFLAGS="$DSOFLAGS -Wl,-no-undefined" + AC_MSG_RESULT(yes)], + [AC_MSG_RESULT(no)]) + LDFLAGS="$OLDLDFLAGS" + ]) # See if ld supports -Bsymbolic-functions... AC_MSG_CHECKING([if ld supports -Bsymbolic-functions]) @@ -1529,7 +1596,9 @@ AS_CASE([$host_os_gui], [cygwin* | mingw*], [ ], [darwin*], [ graphics="Quartz" ], [*], [ - graphics="X11" + AS_IF([test x$enable_wayland != xyes], [ + graphics="X11" + ]) AS_IF([test x$xft_found = xyes], [ graphics="$graphics + Xft" ]) diff --git a/examples/OpenGL3-glut-test.cxx b/examples/OpenGL3-glut-test.cxx index 75ff3b0d1..31639b545 100644 --- a/examples/OpenGL3-glut-test.cxx +++ b/examples/OpenGL3-glut-test.cxx @@ -1,7 +1,7 @@ // // Tiny OpenGL v3 + glut demo program for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2021 by Bill Spitzak and others. +// 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 @@ -14,6 +14,7 @@ // https://www.fltk.org/bugs.php // +#include <FL/Fl.H> // includes <FL/fl_config.h> #if defined(__APPLE__) # define GL_DO_NOT_WARN_IF_MULTI_GL_VERSION_HEADERS_INCLUDED 1 # include <OpenGL/gl3.h> // defines OpenGL 3.0+ functions @@ -198,7 +199,10 @@ int main (int argc, char* argv[]) glutCreateWindow("Triangle Test"); #ifndef __APPLE__ GLenum err = glewInit(); // defines pters to functions of OpenGL V 1.2 and above - if (err) Fl::error("glewInit() failed returning %u", err); +#ifdef FLTK_USE_WAYLAND + if (err == GLEW_ERROR_NO_GLX_DISPLAY) err = GLEW_OK; +#endif + if (err != GLEW_OK) Fl::error("glewInit() failed returning %u", err); fprintf(stderr, "Status: Using GLEW %s\n", glewGetString(GLEW_VERSION)); #endif int gl_version_major; diff --git a/examples/OpenGL3test.cxx b/examples/OpenGL3test.cxx index 1b7983529..08c1711a5 100644 --- a/examples/OpenGL3test.cxx +++ b/examples/OpenGL3test.cxx @@ -1,7 +1,7 @@ // // Tiny OpenGL v3 demo program for the Fast Light Tool Kit (FLTK). // -// Copyright 1998-2018 by Bill Spitzak and others. +// 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 @@ -146,6 +146,11 @@ public: make_current(); #ifndef __APPLE__ GLenum err = glewInit(); // defines pters to functions of OpenGL V 1.2 and above +# ifdef FLTK_USE_WAYLAND + // glewInit returns GLEW_ERROR_NO_GLX_DISPLAY with Wayland + // see https://github.com/nigels-com/glew/issues/273 + if (err == GLEW_ERROR_NO_GLX_DISPLAY) err = GLEW_OK; +# endif if (err) Fl::warning("glewInit() failed returning %u", err); else add_output("Using GLEW %s\n", glewGetString(GLEW_VERSION)); #endif diff --git a/fl_config.cmake.in b/fl_config.cmake.in index 1a83890dc..c0505e03a 100644 --- a/fl_config.cmake.in +++ b/fl_config.cmake.in @@ -55,4 +55,14 @@ #cmakedefine FLTK_USE_X11 1 + +/* + * FLTK_USE_WAYLAND + * + * Do we use Wayland for the current platform? + * + */ + +#cmakedefine FLTK_USE_WAYLAND 1 + #endif /* _FL_fl_config_h_ */ diff --git a/fl_config.in b/fl_config.in index 477f91f28..af4fd3530 100644 --- a/fl_config.in +++ b/fl_config.in @@ -54,4 +54,14 @@ #undef FLTK_USE_X11 + +/* + * FLTK_USE_WAYLAND + * + * Do we use Wayland for the current platform? + * + */ + +#undef FLTK_USE_WAYLAND + #endif /* _FL_fl_config_h_ */ diff --git a/libdecor/LICENSE b/libdecor/LICENSE new file mode 100644 index 000000000..9cf106272 --- /dev/null +++ b/libdecor/LICENSE @@ -0,0 +1,19 @@ +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/libdecor/README.md b/libdecor/README.md new file mode 100644 index 000000000..83234c7ee --- /dev/null +++ b/libdecor/README.md @@ -0,0 +1,86 @@ +# libdecor - A client-side decorations library for Wayland client + +libdecor is a library that can help Wayland clients draw window +decorations for them. It aims to provide multiple backends that implements the +decoration drawing. + + +## Dependencies + +Required: +- `meson` >= 0.47 +- `ninja` +- `wayland-client` >= 1.18 +- `wayland-protocols` >= 1.15 +- `wayland-cursor` +- `cairo` +- `pangocairo` + +Recommended: +- `dbus-1` (to query current cursor theme) + +Optional +- `egl` (to build EGL example) +- `opengl` +- `xkbcommon` (to build cairo demo) + +Install via apt: +`sudo apt install meson libwayland-dev wayland-protocols libpango1.0-dev libdbus-1-dev libegl-dev libopengl-dev libxkbcommon-dev` + +Install via dnf: +`sudo dnf install meson wayland-devel wayland-protocols-devel pango-devel dbus-devel mesa-libEGL-devel libglvnd-devel libxkbcommon-devel` + +Newer meson versions can be installed via pip: `pip3 install -U meson`. + +## Build & Install + +### Quick Start + +To build and run the example program: +1. `meson build -Dinstall_demo=true && meson compile -C build` +2. `meson devenv -C build libdecor-demo` + +### Release Builds + +The library and default plugins can be built and installed via: +1. `meson build --buildtype release` +2. `meson install -C build` + +where `build` is the build directory that will be created during this process. + +This will install by default to `/usr/local/`. To change this set the `prefix` during built, e.g. `meson build --buildtype release -Dprefix=$HOME/.local/`. + +Plugins will be installed into the same directory and from thereon will be selected automatically depending on their precedence. This behaviour can be overridden at runtime by setting the environment variable `LIBDECOR_PLUGIN_DIR` and pointing it to a directory with a valid plugin. + +### Debug and Development Builds + +During development and when debugging, it is recommended to enable the AddressSanitizer and increase the warning level: +1. `meson build -Dinstall_demo=true -Db_sanitize=address -Dwarning_level=3` +2. `meson compile -C build` + +You may have to install `libasan6` (apt) or `libasan` (dnf). Otherwise linking will fail. + +By default `libdecor` will look for plugins in the target directory of the installation. Therefore, when running the demos directly from the `build` directory, no plugins will be found and the fallback plugin without any decorations will be used. + +On Meson 0.58.0 and above, this can be corrected using `devenv`, i.e., to run the demo: + +`meson devenv -C build libdecor-demo` + +On older Meson versions, the search path for plugins can be overridden by the environment variable `LIBDECOR_PLUGIN_DIR`. To use the `cairo` plugin, point to the plugin directory: + +`export LIBDECOR_PLUGIN_DIR=build/src/plugins/cairo/` + +and run the demo: + +`./build/demo/libdecor-demo`. + + +### Code of Conduct + +libdecor follows the Contributor Covenant, found at: +https://www.freedesktop.org/wiki/CodeOfConduct + +Please conduct yourself in a respectful and civilised manner when interacting +with community members on mailing lists, IRC, or bug trackers. The community +represents the project as a whole, and abusive or bullying behaviour is not +tolerated by the project. diff --git a/libdecor/build/Makefile b/libdecor/build/Makefile new file mode 100644 index 000000000..b64ac1561 --- /dev/null +++ b/libdecor/build/Makefile @@ -0,0 +1,80 @@ +# +# Library Makefile for the Fast Light Tool Kit (FLTK). +# +# Copyright 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 ../../makeinclude + +CFLAGS_DECOR = -I. -I../.. -I../../src -I../src -fPIC -D_GNU_SOURCE -DUSE_SYSTEM_LIBDECOR=0 +OBJECTS = fl_libdecor.o libdecor-cairo-blur.o fl_libdecor-plugins.o \ + ../../src/xdg-decoration-protocol.o ../../src/xdg-shell-protocol.o \ + ../../src/text-input-protocol.o cursor-settings.o os-compatibility.o +PROTOCOLS = /usr/share/wayland-protocols + +all : demo egl + +depend: + : echo "libdecor/build: make depend..." + +fl_libdecor.o : fl_libdecor.c ../src/libdecor.c ../../src/xdg-shell-protocol.c ../../src/xdg-decoration-protocol.c ../../src/text-input-protocol.c + $(CC) $(CFLAGS) $(CFLAGS_DECOR) -c fl_libdecor.c -DLIBDECOR_PLUGIN_API_VERSION=1 -DLIBDECOR_PLUGIN_DIR=\"/usr/local/lib\" + +fl_libdecor-plugins.o : fl_libdecor-plugins.c ../src/plugins/cairo/libdecor-cairo.c + $(CC) $(CFLAGS) $(CFLAGS_DECOR) -c fl_libdecor-plugins.c -DLIBDECOR_PLUGIN_API_VERSION=1 -DLIBDECOR_PLUGIN_DIR=\"/usr/local/lib\" + +libdecor-cairo-blur.o : ../src/plugins/cairo/libdecor-cairo-blur.c + $(CC) $(CFLAGS_DECOR) -c ../src/plugins/cairo/libdecor-cairo-blur.c + +os-compatibility.o : ../src/os-compatibility.c + $(CC) $(CFLAGS_DECOR) -c ../src/os-compatibility.c + +cursor-settings.o : ../src/cursor-settings.c + $(CC) $(CFLAGS_DECOR) -c ../src/cursor-settings.c -DHAS_DBUS `pkg-config --cflags dbus-1` + +../../src/xdg-shell-protocol.c : + wayland-scanner private-code $(PROTOCOLS)/stable/xdg-shell/xdg-shell.xml \ + ../../src/xdg-shell-protocol.c + wayland-scanner client-header $(PROTOCOLS)/stable/xdg-shell/xdg-shell.xml \ + ../../src/xdg-shell-client-protocol.h + +../../src/xdg-decoration-protocol.c : + wayland-scanner private-code \ + $(PROTOCOLS)/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml \ + ../../src/xdg-decoration-protocol.c + wayland-scanner client-header \ + $(PROTOCOLS)/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml \ + ../../src/xdg-decoration-client-protocol.h + +../../src/text-input-protocol.c : + wayland-scanner private-code \ + $(PROTOCOLS)/unstable/text-input/text-input-unstable-v3.xml \ + ../../src/text-input-protocol.c + wayland-scanner client-header \ + $(PROTOCOLS)/unstable/text-input/text-input-unstable-v3.xml \ + ../../src/text-input-client-protocol.h + +demo : ../demo/demo.c $(OBJECTS) + $(CC) -o demo ../demo/demo.c -D_GNU_SOURCE -I../.. -I../src -I. -I../../src $(OBJECTS) $(LDLIBS) -lm -rdynamic -no-pie -Wl,--defsym=fl_libdecor_using_weston=0 + +egl : ../demo/egl.c $(OBJECTS) + $(CC) -o egl ../demo/egl.c -D_GNU_SOURCE -I../.. -I../src -I. -I../../src $(OBJECTS) $(GLDLIBS) -lm -rdynamic -no-pie -Wl,--defsym=fl_libdecor_using_weston=0 + + +install: + echo "Nothing to install" + +uninstall: + +clean: + $(RM) *.o ../../src/xdg-*.c ../../src/xdg-*.h ../../src/xdg-*.o ../../src/text-input-* demo egl diff --git a/libdecor/build/fl_libdecor-plugins.c b/libdecor/build/fl_libdecor-plugins.c new file mode 100644 index 000000000..ff8d5a101 --- /dev/null +++ b/libdecor/build/fl_libdecor-plugins.c @@ -0,0 +1,348 @@ +// +// Interface with the libdecor library for the Fast Light Tool Kit (FLTK). +// +// Copyright 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 +// + +/* Support of interactions between FLTK and libdecor plugins, either dynamically + loaded by dlopen() or built-in FLTK. + + Under USE_SYSTEM_LIBDECOR, the plugin can only be dynamically loaded. + Under ! USE_SYSTEM_LIBDECOR, it can be dynamically loaded from a directory + given in environment variable LIBDECOR_PLUGIN_DIR, or the built-in one is used. + */ + +#include <dlfcn.h> +#include <string.h> +#include "../src/libdecor.h" +#include "xdg-decoration-client-protocol.h" +#include <pango/pangocairo.h> + +#if USE_SYSTEM_LIBDECOR +#include "../src/libdecor-plugin.h" + +enum component {NONE}; +enum decoration_type {DECORATION_TYPE_NONE}; + +struct buffer { // identical in libdecor-cairo.c and libdecor-gtk.c + struct wl_buffer *wl_buffer; + bool in_use; + bool is_detached; + + void *data; + size_t data_size; + int width; + int height; + int scale; + int buffer_width; + int buffer_height; +}; + +#else // !USE_SYSTEM_LIBDECOR + +const struct libdecor_plugin_description *fl_libdecor_plugin_description = NULL; + +# ifdef HAVE_GTK +# include <gtk/gtk.h> +# include "../src/plugins/gtk/libdecor-gtk.c" +# else +# include "../src/plugins/cairo/libdecor-cairo.c" +# undef libdecor_frame_set_min_content_size +# endif // HAVE_GTK + +#endif // USE_SYSTEM_LIBDECOR + + +#if USE_SYSTEM_LIBDECOR || HAVE_GTK +/* these definitions derive from libdecor/src/plugins/cairo/libdecor-cairo.c */ + +struct libdecor_plugin_cairo { + struct libdecor_plugin plugin; + + struct wl_callback *globals_callback; + struct wl_callback *globals_callback_shm; + + struct libdecor *context; + + struct wl_registry *wl_registry; + struct wl_subcompositor *wl_subcompositor; + struct wl_compositor *wl_compositor; + + struct wl_shm *wl_shm; + struct wl_callback *shm_callback; + bool has_argb; + + struct wl_list visible_frame_list; + struct wl_list seat_list; + struct wl_list output_list; + + char *cursor_theme_name; + int cursor_size; + + PangoFontDescription *font; +}; + +enum composite_mode { + COMPOSITE_SERVER, + COMPOSITE_CLIENT, +}; + +struct border_component_cairo { + enum component type; + + bool is_hidden; + bool opaque; + + enum composite_mode composite_mode; + struct { + struct wl_surface *wl_surface; + struct wl_subsurface *wl_subsurface; + struct buffer *buffer; + struct wl_list output_list; + int scale; + } server; + struct { + cairo_surface_t *image; + struct border_component_cairo *parent_component; + } client; + + struct wl_list child_components; /* border_component::link */ + struct wl_list link; /* border_component::child_components */ +}; + +struct libdecor_frame_cairo { + struct libdecor_frame frame; + + struct libdecor_plugin_cairo *plugin_cairo; + + int content_width; + int content_height; + + enum decoration_type decoration_type; + + enum libdecor_window_state window_state; + + char *title; + + enum libdecor_capabilities capabilities; + + struct border_component_cairo *focus; + struct border_component_cairo *active; + struct border_component_cairo *grab; + + bool shadow_showing; + struct border_component_cairo shadow; + + struct { + bool is_showing; + struct border_component_cairo title; + struct border_component_cairo min; + struct border_component_cairo max; + struct border_component_cairo close; + } title_bar; + + /* store pre-processed shadow tile */ + cairo_surface_t *shadow_blur; + + struct wl_list link; +}; +#endif + + +#if USE_SYSTEM_LIBDECOR || !HAVE_GTK + +/* Definitions derived from libdecor-gtk.c */ + +typedef struct _GtkWidget GtkWidget; +enum header_element { HDR_NONE }; +typedef enum { GTK_STATE_FLAG_NORMAL = 0 } GtkStateFlags; + +struct border_component_gtk { + enum component type; + struct wl_surface *wl_surface; + struct wl_subsurface *wl_subsurface; + struct buffer *buffer; + bool opaque; + struct wl_list output_list; + int scale; + struct wl_list child_components; /* border_component::link */ + struct wl_list link; /* border_component::child_components */ +}; + +struct header_element_data { + const char* name; + enum header_element type; + GtkWidget *widget; + GtkStateFlags state; +}; + +struct libdecor_frame_gtk { + struct libdecor_frame frame; + struct libdecor_plugin_gtk *plugin_gtk; + int content_width; + int content_height; + enum libdecor_window_state window_state; + enum decoration_type decoration_type; + char *title; + enum libdecor_capabilities capabilities; + struct border_component_gtk *active; + struct border_component_gtk *focus; + struct border_component_gtk *grab; + bool shadow_showing; + struct border_component_gtk shadow; + GtkWidget *window; /* offscreen window for rendering */ + GtkWidget *header; /* header bar with widgets */ + struct border_component_gtk headerbar; + struct header_element_data hdr_focus; + cairo_surface_t *shadow_blur; + struct wl_list link; +}; + +#endif // USE_SYSTEM_LIBDECOR || !HAVE_GTK + +/* these definitions are copied from libdecor/src/libdecor.c */ + +struct libdecor_limits { + int min_width; + int min_height; + int max_width; + int max_height; +}; + +struct libdecor_frame_private { + int ref_count; + struct libdecor *context; + struct wl_surface *wl_surface; + struct libdecor_frame_interface *iface; + void *user_data; + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct zxdg_toplevel_decoration_v1 *toplevel_decoration; + bool pending_map; + struct { + char *app_id; + char *title; + struct libdecor_limits content_limits; + struct xdg_toplevel *parent; + } state; + struct libdecor_configuration *pending_configuration; + int content_width; + int content_height; + enum libdecor_window_state window_state; + enum zxdg_toplevel_decoration_v1_mode decoration_mode; + enum libdecor_capabilities capabilities; + struct libdecor_limits interactive_limits; + bool visible; +}; + + +static unsigned char *gtk_titlebar_buffer(struct libdecor_frame *frame, + int *width, int *height, int *stride) +{ + struct libdecor_frame_gtk *lfg = (struct libdecor_frame_gtk *)frame; +#if USE_SYSTEM_LIBDECOR || !HAVE_GTK + struct border_component_gtk *bc; +#else + struct border_component *bc; +#endif + bc = &lfg->headerbar; + struct buffer *buffer = bc->buffer; + *width = buffer->buffer_width; + *height = buffer->buffer_height; + *stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, buffer->buffer_width); + return (unsigned char*)buffer->data; +} + + +static unsigned char *cairo_titlebar_buffer(struct libdecor_frame *frame, + int *width, int *height, int *stride) +{ + struct libdecor_frame_cairo *lfc = (struct libdecor_frame_cairo *)frame; +#if USE_SYSTEM_LIBDECOR || HAVE_GTK + struct border_component_cairo *bc = &lfc->title_bar.title; +#else + struct border_component *bc = &lfc->title_bar.title; +#endif + struct buffer *buffer = bc->server.buffer; + *width = buffer->buffer_width; + *height = buffer->buffer_height; + *stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, buffer->buffer_width); + return (unsigned char*)buffer->data; +} + + +/* + Although each plugin declares an exported global variable + LIBDECOR_EXPORT const struct libdecor_plugin_description libdecor_plugin_description; + these plugins are dlopen()'ed in libdecor.c without the RTLD_GLOBAL flag. + Consequently their symbols are not discovered by dlsym(RTLD_DEFAULT, "symbol-name"). + + Under USE_SYSTEM_LIBDECOR, we repeat the dlopen() for the same plugin + then dlsym() will report the address of libdecor_plugin_description. + + Under !USE_SYSTEM_LIBDECOR, we compile fl_libdecor.c which modifies the dlopen() + to call dlsym(ld, "libdecor_plugin_description") just after the dlopen and memorizes + this address. + + A plugin is loaded also if SSD. + KDE has its own size limit, similar to that of GDK plugin + */ +static const char *get_libdecor_plugin_description(struct libdecor_frame *frame) { + static const struct libdecor_plugin_description *plugin_description = NULL; + if (frame->priv->decoration_mode == ZXDG_TOPLEVEL_DECORATION_V1_MODE_SERVER_SIDE) { + return "Server-Side Decoration"; + } + if (!plugin_description) { + #if USE_SYSTEM_LIBDECOR + char fname[PATH_MAX]; + const char *dir = getenv("LIBDECOR_PLUGIN_DIR"); + if (!dir) dir = LIBDECOR_PLUGIN_DIR; + sprintf(fname, "%s/libdecor-gtk.so", dir); + void *dl = dlopen(fname, RTLD_LAZY | RTLD_LOCAL); + if (!dl) { + sprintf(fname, "%s/libdecor-cairo.so", dir); + dl = dlopen(fname, RTLD_LAZY | RTLD_LOCAL); + } + if (dl) plugin_description = (const struct libdecor_plugin_description*)dlsym(dl, "libdecor_plugin_description"); +#else + plugin_description = fl_libdecor_plugin_description; + extern const struct libdecor_plugin_description libdecor_plugin_description; + if (!plugin_description) plugin_description = &libdecor_plugin_description; +#endif + //if (plugin_description) puts(plugin_description->description); + } + return plugin_description ? plugin_description->description : NULL; +} + + +/* + FLTK-added utility function to give access to the pixel array representing + the titlebar of a window decorated by the cairo plugin of libdecor. + frame: a libdecor-defined pointer given by fl_xid(win)->frame (with Fl_Window *win); + *width, *height: returned assigned to the width and height in pixels of the titlebar; + *stride: returned assigned to the number of bytes per line of the pixel array; + return value: start of the pixel array, which is in BGRA order, or NULL. + */ +unsigned char *fl_libdecor_titlebar_buffer(struct libdecor_frame *frame, + int *width, int *height, int *stride) +{ + static const char *my_plugin = NULL; + if (!my_plugin) my_plugin = get_libdecor_plugin_description(frame); + if (my_plugin && !strcmp(my_plugin, "GTK plugin")) { + return gtk_titlebar_buffer(frame, width, height, stride); + } + else if (my_plugin && !strcmp(my_plugin, "libdecor plugin using Cairo")) { + return cairo_titlebar_buffer(frame, width, height, stride); + } + return NULL; +} diff --git a/libdecor/build/fl_libdecor.c b/libdecor/build/fl_libdecor.c new file mode 100644 index 000000000..a09155566 --- /dev/null +++ b/libdecor/build/fl_libdecor.c @@ -0,0 +1,127 @@ +// +// Interface with the libdecor library for the Fast Light Tool Kit (FLTK). +// +// Copyright 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 +// + +/* Improvements to libdecor.c without modifying libdecor.c itself */ + +#define libdecor_frame_set_minimized libdecor_frame_set_minimized_orig +#define libdecor_new libdecor_new_orig +#include <dlfcn.h> +static void *dlopen_corrected(const char *, int); +#define dlopen(A, B) dlopen_corrected(A, B) +#include "../src/libdecor.c" +#undef dlopen +#undef libdecor_frame_set_minimized +#undef libdecor_new + +extern bool fl_libdecor_using_weston(void); +extern const struct libdecor_plugin_description *fl_libdecor_plugin_description; +//#include <stdio.h> + +// we have a built-in plugin so don't need a fallback one +struct libdecor_plugin *libdecor_fallback_plugin_new(struct libdecor *context) { + return NULL; +} + +// see get_libdecor_plugin_description() explaining why this is useful +static void *dlopen_corrected(const char *filename, int flags) { + static int best_priority = -1; + void *retval = dlopen(filename, flags); + if (retval) { + const struct libdecor_plugin_description *description = + (const struct libdecor_plugin_description*)dlsym(retval, "libdecor_plugin_description"); + if (description && description->priorities->priority > best_priority) { + fl_libdecor_plugin_description = description; + best_priority = description->priorities->priority; + } + } + return retval; +} + + +LIBDECOR_EXPORT void libdecor_frame_set_minimized(struct libdecor_frame *frame) +{ + static bool done = false; + static bool using_weston = false; + if (!done) { + typedef bool (*ext_f)(void); + volatile ext_f ext = fl_libdecor_using_weston; + done = true; + if (ext) using_weston = fl_libdecor_using_weston(); +//fprintf(stderr, "fl_libdecor_using_weston=%p using_weston=%d\n", fl_libdecor_using_weston, using_weston); + if (using_weston) { // determine the version of the running Weston compositor + FILE *pipe = popen("weston --version", "r"); + if (pipe) { + char line[50], *p; + int version = 0; + p = fgets(line, sizeof(line), pipe); + pclose(pipe); + if (p) p = strchr(line, ' '); + if (p) { + sscanf(p, "%d", &version); + // Weston version 10 has fixed the bug handled here + if (version >= 10) using_weston = false; + } + } + } + } + if (using_weston) libdecor_frame_set_visibility(frame, false); + libdecor_frame_set_minimized_orig(frame); +} + + +/* + By default, FLTK modifies libdecor's libdecor_new() function to determine the plugin as follows : + 1) the directory pointed by environment variable LIBDECOR_PLUGIN_DIR or, in absence of this variable, + by -DLIBDECOR_PLUGIN_DIR=xxx at build time is searched for a libdecor plugin; + 2) if this directory does not exist or contains no plugin, the built-in plugin is used. + * if FLTK was built with package libgtk-3-dev, the GTK plugin is used + * if FLTK was built without package libgtk-3-dev, the Cairo plugin is used + + If FLTK was built with OPTION_USE_SYSTEM_LIBDECOR turned ON, the present modification + isn't compiled, so the plugin-searching algorithm of libdecor_new() in libdecor-0.so is used. + This corresponds to step 1) above and to use no titlebar is no plugin is found. + + N.B.: only the system package is built with a meaningful value of -DLIBDECOR_PLUGIN_DIR= + so a plugin may be loaded that way only if FLTK was built with OPTION_USE_SYSTEM_LIBDECOR turned ON. + + */ +LIBDECOR_EXPORT struct libdecor *libdecor_new(struct wl_display *wl_display, struct libdecor_interface *iface) +{ + struct libdecor *context; + context = zalloc(sizeof *context); + context->ref_count = 1; + context->iface = iface; + context->wl_display = wl_display; + context->wl_registry = wl_display_get_registry(wl_display); + wl_registry_add_listener(context->wl_registry, ®istry_listener, context); + context->init_callback = wl_display_sync(context->wl_display); + wl_callback_add_listener(context->init_callback, &init_wl_display_callback_listener, context); + wl_list_init(&context->frames); + // attempt to dynamically load a libdecor plugin with dlopen() + FILE *old_stderr = stderr; + stderr = fopen("/dev/null", "w+"); // avoid "Couldn't open plugin directory" messages + if (init_plugins(context) != 0) { // attempt to load plugin by dlopen() + // no plug-in was found by dlopen(), use built-in plugin instead + // defined in the source code of the built-in plugin: libdecor-cairo.c or libdecor-gtk.c + extern const struct libdecor_plugin_description libdecor_plugin_description; + context->plugin = libdecor_plugin_description.constructor(context); + } + fclose(stderr); // restore stderr as it was before + stderr = old_stderr; + + wl_display_flush(wl_display); + return context; +} diff --git a/libdecor/demo/demo.c b/libdecor/demo/demo.c new file mode 100644 index 000000000..041e35d6c --- /dev/null +++ b/libdecor/demo/demo.c @@ -0,0 +1,1304 @@ +/* + * Copyright © 2011 Benjamin Franzke + * Copyright © 2010 Intel Corporation + * Copyright © 2018 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include <errno.h> +#include <fcntl.h> +#include <linux/input.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> +#include <wayland-cursor.h> +#include <xkbcommon/xkbcommon.h> + +#include "libdecor.h" +#include "utils.h" +#include "cursor-settings.h" +#include "os-compatibility.h" + +#include "xdg-shell-client-protocol.h" + +struct window; + +static const size_t chk = 16; +static const int DEFAULT_WIDTH = 30*chk; +static const int DEFAULT_HEIGHT = 20*chk; + +static const int POPUP_WIDTH = 100; +static const int POPUP_HEIGHT = 300; + +static const char *proxy_tag = "libdecor-demo"; + +static const char *titles[] = { + "Hello!", + "Hallå!", + "Привет!", + "Γειά σου!", + "שלום!", + "你好!", + "สวัสดี!", + "こんにちは!", + "👻❤️🤖➕🍰", +}; + +static const size_t N_TITLES = ARRAY_SIZE(titles); + + +static bool +own_proxy(struct wl_proxy *proxy) +{ + return (wl_proxy_get_tag(proxy) == &proxy_tag); +} + +static bool +own_output(struct wl_output *output) +{ + return own_proxy((struct wl_proxy *) output); +} + +struct buffer { + struct wl_buffer *wl_buffer; + void *data; + size_t data_size; +}; + +struct popup { + struct wl_surface *wl_surface; + struct xdg_surface *xdg_surface; + struct xdg_popup *xdg_popup; + struct xdg_surface *parent; + struct seat *seat; + struct window *window; +}; + +struct window { + struct wl_surface *wl_surface; + struct buffer *buffer; + struct libdecor_frame *frame; + int content_width; + int content_height; + int configured_width; + int configured_height; + int floating_width; + int floating_height; + enum libdecor_window_state window_state; + struct wl_list outputs; + int scale; + struct popup *popup; + size_t title_index; +}; + +struct seat { + struct wl_seat *wl_seat; + struct wl_keyboard *wl_keyboard; + struct wl_pointer *wl_pointer; + struct wl_list link; + struct wl_list pointer_outputs; + struct wl_cursor_theme *cursor_theme; + struct wl_cursor *left_ptr_cursor; + struct wl_surface *cursor_surface; + struct wl_surface *pointer_focus; + int pointer_scale; + uint32_t serial; + wl_fixed_t pointer_sx; + wl_fixed_t pointer_sy; + char *name; + + struct xkb_context *xkb_context; + struct xkb_state *xkb_state; +}; + +struct output { + uint32_t id; + struct wl_output *wl_output; + int scale; + struct wl_list link; +}; + +struct window_output { + struct output* output; + struct wl_list link; +}; + +struct pointer_output { + struct output* output; + struct wl_list link; +}; + +static struct wl_compositor *wl_compositor; +static struct wl_shm *wl_shm; +static struct xdg_wm_base *xdg_wm_base; +static struct wl_list seats; +static struct wl_list outputs; + +static bool has_xrgb = false; + +static struct window *window; + +static void +redraw(struct window *window); + +static void +resize(struct window *window, int width, int height) +{ + struct libdecor_state *state; + + if (!libdecor_frame_is_floating(window->frame)) { + printf("... ignoring in non-floating mode\n"); + return; + } + + /* commit changes to decorations */ + state = libdecor_state_new( width, height); + libdecor_frame_commit(window->frame, state, NULL); + libdecor_state_free(state); + /* force redraw of content and commit */ + window->configured_width = width; + window->configured_height = height; + /* store floating dimensions */ + window->floating_width = width; + window->floating_height = height; + redraw(window); +} + +static struct buffer * +create_shm_buffer(int width, + int height, + uint32_t format); + +static void +update_scale(struct window *window) +{ + int scale = 1; + struct window_output *window_output; + + wl_list_for_each(window_output, &window->outputs, link) { + scale = MAX(scale, window_output->output->scale); + } + if (scale != window->scale) { + window->scale = scale; + redraw(window); + } +} + +static void +shm_format(void *data, + struct wl_shm *wl_shm, + uint32_t format) +{ + if (format == WL_SHM_FORMAT_XRGB8888) + has_xrgb = true; +} + +static struct wl_shm_listener shm_listener = { + shm_format +}; + +static void +try_update_cursor(struct seat *seat); + +static void +cursor_surface_enter(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct seat *seat = data; + struct pointer_output *pointer_output; + + if (!own_output(wl_output)) + return; + + pointer_output = zalloc(sizeof *pointer_output); + pointer_output->output = wl_output_get_user_data(wl_output); + wl_list_insert(&seat->pointer_outputs, &pointer_output->link); + try_update_cursor(seat); +} + +static void +cursor_surface_leave(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct seat *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; + + theme = wl_cursor_theme_load(name, size, wl_shm); + free(name); + if (theme != NULL) { + if (seat->cursor_theme) + wl_cursor_theme_destroy(seat->cursor_theme); + seat->cursor_theme = theme; + } + if (seat->cursor_theme) + seat->left_ptr_cursor + = wl_cursor_theme_get_cursor(seat->cursor_theme, "left_ptr"); + if (!seat->cursor_surface) { + seat->cursor_surface = wl_compositor_create_surface( + wl_compositor); + wl_surface_add_listener(seat->cursor_surface, + &cursor_surface_listener, seat); + } +} + +static void +set_cursor(struct seat *seat) +{ + struct wl_cursor *wl_cursor; + struct wl_cursor_image *image; + struct wl_buffer *buffer; + const int scale = seat->pointer_scale; + + if (!seat->cursor_theme) + return; + + wl_cursor = seat->left_ptr_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 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 = MAX(scale, pointer_output->output->scale); + } + + if (scale != seat->pointer_scale) { + seat->pointer_scale = scale; + init_cursors(seat); + set_cursor(seat); + } +} + +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 = data; + + seat->pointer_focus = surface; + seat->serial = serial; + + if (surface != window->wl_surface) + return; + + set_cursor(seat); + + seat->pointer_sx = surface_x; + seat->pointer_sy = surface_y; +} + +static void +pointer_leave(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + struct wl_surface *surface) +{ + struct seat *seat = data; + if (seat->pointer_focus == surface) + seat->pointer_focus = NULL; +} + +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 = data; + + seat->pointer_sx = surface_x; + seat->pointer_sy = surface_y; +} + +static struct xdg_positioner * +create_positioner(struct seat *seat) +{ + struct xdg_positioner *positioner; + enum xdg_positioner_constraint_adjustment constraint_adjustment; + int x, y; + + positioner = xdg_wm_base_create_positioner(xdg_wm_base); + xdg_positioner_set_size(positioner, POPUP_WIDTH, POPUP_HEIGHT); + + libdecor_frame_translate_coordinate(window->frame, + wl_fixed_to_int(seat->pointer_sx), + wl_fixed_to_int(seat->pointer_sy), + &x, &y); + + xdg_positioner_set_anchor_rect(positioner, x, y, 1, 1); + + constraint_adjustment = (XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y | + XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X); + xdg_positioner_set_constraint_adjustment (positioner, + constraint_adjustment); + + xdg_positioner_set_anchor (positioner, + XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT); + xdg_positioner_set_gravity (positioner, + XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT); + + return positioner; +} + +static void +xdg_popup_configure(void *data, + struct xdg_popup *xdg_popup, + int32_t x, + int32_t y, + int32_t width, + int32_t height) +{ +} + +static void +popup_destroy(struct popup *popup) +{ + libdecor_frame_popup_ungrab(popup->window->frame, + popup->seat->name); + xdg_popup_destroy(popup->xdg_popup); + xdg_surface_destroy(popup->xdg_surface); + wl_surface_destroy(popup->wl_surface); + popup->window->popup = NULL; + free(popup); +} + +static void +xdg_popup_done(void *data, + struct xdg_popup *xdg_popup) +{ + struct popup *popup = data; + + popup_destroy(popup); +} + +static const struct xdg_popup_listener xdg_popup_listener = { + xdg_popup_configure, + xdg_popup_done, +}; + +static void +xdg_surface_configure(void *data, + struct xdg_surface *xdg_surface, + uint32_t serial) +{ + struct popup *popup = data; + uint32_t *pixels; + struct buffer *buffer; + int y; + + buffer = create_shm_buffer(POPUP_WIDTH, POPUP_HEIGHT, + WL_SHM_FORMAT_XRGB8888); + pixels = buffer->data; + for (y = 0; y < POPUP_HEIGHT; y++) { + int x; + + for (x = 0; x < POPUP_WIDTH; x++) + pixels[y * POPUP_WIDTH + x] = 0xff4455ff; + } + + wl_surface_attach(popup->wl_surface, buffer->wl_buffer, 0, 0); + wl_surface_set_buffer_scale(window->wl_surface, window->scale); + wl_surface_damage(window->wl_surface, 0, 0, + POPUP_WIDTH, POPUP_HEIGHT); + xdg_surface_ack_configure(popup->xdg_surface, serial); + wl_surface_commit(popup->wl_surface); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + xdg_surface_configure, +}; + +static void +open_popup(struct seat *seat) +{ + struct popup *popup; + struct xdg_positioner *positioner; + + popup = zalloc(sizeof *popup); + + popup->wl_surface = wl_compositor_create_surface(wl_compositor); + popup->xdg_surface = xdg_wm_base_get_xdg_surface (xdg_wm_base, + popup->wl_surface); + popup->parent = libdecor_frame_get_xdg_surface(window->frame); + popup->window = window; + popup->seat = seat; + positioner = create_positioner(seat); + popup->xdg_popup = xdg_surface_get_popup(popup->xdg_surface, + popup->parent, + positioner); + xdg_positioner_destroy(positioner); + + xdg_surface_add_listener (popup->xdg_surface, + &xdg_surface_listener, + popup); + xdg_popup_add_listener (popup->xdg_popup, + &xdg_popup_listener, + popup); + + window->popup = popup; + + xdg_popup_grab(popup->xdg_popup, seat->wl_seat, seat->serial); + wl_surface_commit(popup->wl_surface); + + libdecor_frame_popup_grab(window->frame, seat->name); +} + +static void +close_popup(struct window *window) +{ + struct popup *popup = window->popup; + + popup_destroy(popup); +} + +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 = data; + + if (seat->pointer_focus != window->wl_surface) + return; + + seat->serial = serial; + + if (window->popup && + state == WL_POINTER_BUTTON_STATE_PRESSED) + close_popup(window); + + if (button == BTN_LEFT && + state == WL_POINTER_BUTTON_STATE_PRESSED) { + libdecor_frame_move(window->frame, seat->wl_seat, serial); + } else if (button == BTN_MIDDLE && + state == WL_POINTER_BUTTON_STATE_PRESSED) { + libdecor_frame_show_window_menu(window->frame, + seat->wl_seat, + serial, + wl_fixed_to_int(seat->pointer_sx), + wl_fixed_to_int(seat->pointer_sy)); + } else if (button == BTN_RIGHT && + state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (!window->popup) + open_popup(seat); + } +} + +static void +pointer_axis(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + uint32_t axis, + wl_fixed_t value) +{ +} + +static struct wl_pointer_listener pointer_listener = { + pointer_enter, + pointer_leave, + pointer_motion, + pointer_button, + pointer_axis +}; + +static void +keyboard_keymap(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t format, + int32_t fd, + uint32_t size) +{ + struct seat *seat = data; + + if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) { + close(fd); + return; + } + + char *map_str = (char *)(mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0)); + if (map_str == MAP_FAILED) { + close(fd); + fprintf(stderr, "keymap mmap failed: %s", strerror(errno)); + return; + } + + struct xkb_keymap *keymap = xkb_keymap_new_from_string( + seat->xkb_context, map_str, + XKB_KEYMAP_FORMAT_TEXT_V1, + XKB_KEYMAP_COMPILE_NO_FLAGS); + munmap(map_str, size); + close(fd); + + if (!keymap) + return; + + seat->xkb_state = xkb_state_new(keymap); + + xkb_keymap_unref(keymap); +} + +static void +keyboard_enter(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *surface, + struct wl_array *keys) +{ +} + +static void +keyboard_leave(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + struct wl_surface *surface) +{ +} + +static void +keyboard_key(void *data, + struct wl_keyboard *wl_keyboard, + uint32_t serial, + uint32_t time, + uint32_t key, + uint32_t state) +{ + struct seat *seat = data; + + if (state & WL_KEYBOARD_KEY_STATE_PRESSED) { + const xkb_keysym_t *syms; + + if (xkb_state_key_get_syms(seat->xkb_state, key + 8, &syms) != 1) + return; + + switch (syms[0]) { + case XKB_KEY_Escape: + printf("close\n"); + libdecor_frame_close(window->frame); + break; + case XKB_KEY_1: /* toggle resizability */ + if (libdecor_frame_has_capability( + window->frame, LIBDECOR_ACTION_RESIZE)) { + printf("set fixed-size\n"); + libdecor_frame_unset_capabilities(window->frame, + LIBDECOR_ACTION_RESIZE); + } + else { + printf("set resizeable\n"); + libdecor_frame_set_capabilities(window->frame, + LIBDECOR_ACTION_RESIZE); + } + break; + case XKB_KEY_2: /* maximize */ + printf("maximize\n"); + libdecor_frame_set_maximized(window->frame); + break; + case XKB_KEY_3: /* un-maximize / restore */ + printf("un-maximize\n"); + libdecor_frame_unset_maximized(window->frame); + break; + case XKB_KEY_4: /* fullscreen */ + printf("fullscreen\n"); + libdecor_frame_set_fullscreen(window->frame, NULL); + break; + case XKB_KEY_5: /* un-fullscreen / restore */ + printf("un-fullscreen\n"); + libdecor_frame_unset_fullscreen(window->frame); + break; + case XKB_KEY_minus: + case XKB_KEY_plus: + { + const int dd = (syms[0] == XKB_KEY_minus ? -1 : +1) * chk/2; + printf("resize to: %i x %i\n", + window->configured_width + dd, + window->configured_height + dd); + resize(window, + window->configured_width + dd, + window->configured_height + dd); + } + break; + case XKB_KEY_v: /* VGA: 640x480 */ + printf("set VGA resolution: 640x480\n"); + resize(window, 640, 480); + break; + case XKB_KEY_s: /* SVGA: 800x600 */ + printf("set SVGA resolution: 800x600\n"); + resize(window, 800, 600); + break; + case XKB_KEY_x: /* XVGA: 1024x768 */ + printf("set XVGA resolution: 1024x768\n"); + resize(window, 1024, 768); + break; + case XKB_KEY_t: + libdecor_frame_set_title(window->frame, titles[window->title_index]); + window->title_index = (window->title_index + 1) % N_TITLES; + break; + case XKB_KEY_h: /* toggle decorations */ + libdecor_frame_set_visibility( + window->frame, + !libdecor_frame_is_visible(window->frame)); + printf("decorations %s\n", + libdecor_frame_is_visible(window->frame) ? + "visible" : "hidden"); + break; + } + } +} + +static void +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 = data; + + xkb_state_update_mask(seat->xkb_state, + mods_depressed, mods_latched, mods_locked, + 0, 0, group); +} + +static void +keyboard_repeat_info(void *data, + struct wl_keyboard *wl_keyboard, + int32_t rate, + int32_t delay) +{ +} + +static struct wl_keyboard_listener keyboard_listener = { + keyboard_keymap, + keyboard_enter, + keyboard_leave, + keyboard_key, + keyboard_modifiers, + keyboard_repeat_info, +}; + +static void +seat_capabilities(void *data, + struct wl_seat *wl_seat, + uint32_t capabilities) +{ + struct seat *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; + } + + if (capabilities & WL_SEAT_CAPABILITY_KEYBOARD && + !seat->wl_keyboard) { + seat->wl_keyboard = wl_seat_get_keyboard(wl_seat); + seat->xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS); + wl_keyboard_add_listener(seat->wl_keyboard, &keyboard_listener, + seat); + } else if (!(capabilities & WL_SEAT_CAPABILITY_KEYBOARD) && + seat->wl_keyboard) { + xkb_context_unref(seat->xkb_context); + wl_keyboard_release(seat->wl_keyboard); + seat->wl_keyboard = NULL; + } +} + +static void +seat_name(void *data, + struct wl_seat *wl_seat, + const char *name) +{ + struct seat *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) +{ +} + +static void +output_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int32_t width, + int32_t height, + int32_t refresh) +{ +} + +static void +output_done(void *data, + struct wl_output *wl_output) +{ + struct output *output = data; + struct seat *seat; + + if (window) { + if (output->scale != window->scale) + update_scale(window); + } + + wl_list_for_each(seat, &seats, link) { + try_update_cursor(seat); + } +} + +static void +output_scale(void *data, + struct wl_output *wl_output, + int32_t factor) +{ + struct output *output = data; + + output->scale = factor; +} + +static struct wl_output_listener output_listener = { + output_geometry, + output_mode, + output_done, + output_scale +}; + +static void +xdg_wm_base_ping(void *user_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 = { + xdg_wm_base_ping, +}; + +static void +registry_handle_global(void *user_data, + struct wl_registry *wl_registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + struct seat *seat; + struct output *output; + + if (strcmp(interface, "wl_compositor") == 0) { + if (version < 4) { + fprintf(stderr, "wl_compositor version >= 4 required"); + exit(EXIT_FAILURE); + } + wl_compositor = + wl_registry_bind(wl_registry, + id, &wl_compositor_interface, 4); + } else if (strcmp(interface, "wl_shm") == 0) { + wl_shm = wl_registry_bind(wl_registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(wl_shm, &shm_listener, NULL); + } else if (strcmp(interface, "wl_seat") == 0) { + if (version < 3) { + fprintf(stderr, "%s version 3 required but only version " + "%i is available\n", interface, version); + exit(EXIT_FAILURE); + } + seat = zalloc(sizeof *seat); + wl_list_init(&seat->pointer_outputs); + seat->wl_seat = wl_registry_bind(wl_registry, + id, &wl_seat_interface, 3); + wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); + } else if (strcmp(interface, "wl_output") == 0) { + if (version < 2) { + fprintf(stderr, "%s version 3 required but only version " + "%i is available\n", interface, version); + exit(EXIT_FAILURE); + } + output = zalloc(sizeof *output); + output->id = id; + output->scale = 1; + output->wl_output = wl_registry_bind(wl_registry, + id, &wl_output_interface, + 2); + 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(&outputs, &output->link); + } else if (strcmp(interface, "xdg_wm_base") == 0) { + xdg_wm_base = wl_registry_bind(wl_registry, id, + &xdg_wm_base_interface, + 1); + xdg_wm_base_add_listener(xdg_wm_base, + &xdg_wm_base_listener, + NULL); + } +} + +static void +registry_handle_global_remove(void *data, struct wl_registry *registry, + uint32_t name) +{ + struct output *output; + struct window_output *window_output; + + wl_list_for_each(output, &outputs, link) { + if (output->id == name) { + wl_list_for_each(window_output, &window->outputs, + link) { + if (window_output->output == output) { + wl_list_remove(&window_output->link); + free(window_output); + } + } + wl_list_remove(&output->link); + 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 +handle_error(struct libdecor *context, + enum libdecor_error error, + const char *message) +{ + fprintf(stderr, "Caught error (%d): %s\n", error, message); + exit(EXIT_FAILURE); +} + +static struct libdecor_interface libdecor_iface = { + .error = handle_error, +}; + +static void +buffer_release(void *user_data, + struct wl_buffer *wl_buffer) +{ + struct buffer *buffer = user_data; + + wl_buffer_destroy(buffer->wl_buffer); + munmap(buffer->data, buffer->data_size); + free(buffer); +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static struct buffer * +create_shm_buffer(int width, + int height, + uint32_t format) +{ + struct wl_shm_pool *pool; + int fd, size, stride; + void *data; + struct buffer *buffer; + + stride = width * 4; + size = stride * height; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); + return NULL; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + return NULL; + } + + buffer = zalloc(sizeof *buffer); + + pool = wl_shm_create_pool(wl_shm, fd, size); + buffer->wl_buffer = wl_shm_pool_create_buffer(pool, 0, + width, height, + stride, format); + wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer); + wl_shm_pool_destroy(pool); + close(fd); + + buffer->data = data; + buffer->data_size = size; + + return buffer; +} + +static void +paint_buffer(struct buffer *buffer, + int width, + int height, + int scale, + enum libdecor_window_state window_state) +{ + uint32_t *pixels = buffer->data; + uint32_t bg, fg, color; + int y, x, sx, sy; + size_t off; + int stride = width * scale; + + if (window_state & LIBDECOR_WINDOW_STATE_ACTIVE) { + fg = 0xffbcbcbc; + bg = 0xff8e8e8e; + } else { + fg = 0xff8e8e8e; + bg = 0xff484848; + } + + for (y = 0; y < height; y++) { + for (x = 0; x < width; x++) { + color = (x & chk) ^ (y & chk) ? fg : bg; + for (sx = 0; sx < scale; sx++) { + for (sy = 0; sy < scale; sy++) { + off = x * scale + sx + + (y * scale + sy) * stride; + pixels[off] = color; + } + } + } + } +} + +static void +redraw(struct window *window) +{ + struct buffer *buffer; + + buffer = create_shm_buffer(window->configured_width * window->scale, + window->configured_height * window->scale, + WL_SHM_FORMAT_XRGB8888); + paint_buffer(buffer, window->configured_width, + window->configured_height, window->scale, + window->window_state); + + wl_surface_attach(window->wl_surface, buffer->wl_buffer, 0, 0); + wl_surface_set_buffer_scale(window->wl_surface, window->scale); + wl_surface_damage_buffer(window->wl_surface, 0, 0, + window->configured_width * window->scale, + window->configured_height * window->scale); + wl_surface_commit(window->wl_surface); +} + +static void +handle_configure(struct libdecor_frame *frame, + struct libdecor_configuration *configuration, + void *user_data) +{ + struct window *window = user_data; + int width, height; + enum libdecor_window_state window_state; + struct libdecor_state *state; + + if (!libdecor_configuration_get_content_size(configuration, frame, + &width, &height)) { + width = window->content_width; + height = window->content_height; + } + + width = (width == 0) ? window->floating_width : width; + height = (height == 0) ? window->floating_height : height; + + window->configured_width = width; + window->configured_height = height; + + if (!libdecor_configuration_get_window_state(configuration, + &window_state)) + window_state = LIBDECOR_WINDOW_STATE_NONE; + + window->window_state = window_state; + + state = libdecor_state_new(width, height); + libdecor_frame_commit(frame, state, configuration); + libdecor_state_free(state); + + /* store floating dimensions */ + if (libdecor_frame_is_floating(window->frame)) { + window->floating_width = width; + window->floating_height = height; + } + + redraw(window); +} + +static void +handle_close(struct libdecor_frame *frame, + void *user_data) +{ + exit(EXIT_SUCCESS); +} + +static void +handle_commit(struct libdecor_frame *frame, + void *user_data) +{ + wl_surface_commit(window->wl_surface); +} + +static void +handle_dismiss_popup(struct libdecor_frame *frame, + const char *seat_name, + void *user_data) +{ + popup_destroy(window->popup); +} + +static struct libdecor_frame_interface libdecor_frame_iface = { + handle_configure, + handle_close, + handle_commit, + handle_dismiss_popup, +}; + +static void +surface_enter(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct window *window = data; + struct output *output; + struct window_output *window_output; + + if (!own_output(wl_output)) + return; + + output = wl_output_get_user_data(wl_output); + + if (output == NULL) + return; + + window_output = zalloc(sizeof *window_output); + window_output->output = output; + wl_list_insert(&window->outputs, &window_output->link); + update_scale(window); +} + +static void +surface_leave(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct window *window = data; + struct 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); + update_scale(window); + break; + } + } +} + +static struct wl_surface_listener surface_listener = { + surface_enter, + surface_leave, +}; + +static void +free_outputs() +{ + struct output *output; + struct window_output *window_output, *window_output_tmp; + + wl_list_for_each(output, &outputs, link) { + wl_list_for_each_safe(window_output, window_output_tmp, &window->outputs, link) { + if (window_output->output == output) { + wl_list_remove(&window_output->link); + free(window_output); + } + } + wl_list_remove(&output->link); + wl_output_destroy(output->wl_output); + free(output); + break; + } +} + +int +main(int argc, + char **argv) +{ + struct timeval start; + struct timeval now; + struct wl_display *wl_display; + struct wl_registry *wl_registry; + struct libdecor *context; + struct output *output; + int timeout = -1; + bool timed_quit = false; + + if (argc > 1 && strcmp(argv[1], "--timed-quit") == 0) { + timed_quit = true; + timeout = 500; /* ms */ + } + + /* write all output to stdout immediately */ + setbuf(stdout, NULL); + + wl_display = wl_display_connect(NULL); + if (!wl_display) { + fprintf(stderr, "No Wayland connection\n"); + return EXIT_FAILURE; + } + + 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_roundtrip(wl_display); + wl_display_roundtrip(wl_display); + if (!has_xrgb) { + fprintf(stderr, "No XRGB shm format\n"); + return EXIT_FAILURE; + } + + window = zalloc(sizeof *window); + window->scale = 1; + window->title_index = 0; + wl_list_for_each(output, &outputs, link) { + window->scale = MAX(window->scale, output->scale); + } + wl_list_init(&window->outputs); + window->wl_surface = wl_compositor_create_surface(wl_compositor); + wl_surface_add_listener(window->wl_surface, &surface_listener, window); + + /* initialise content dimensions */ + window->floating_width = DEFAULT_WIDTH; + window->floating_height = DEFAULT_HEIGHT; + + context = libdecor_new(wl_display, &libdecor_iface); + window->frame = libdecor_decorate(context, window->wl_surface, + &libdecor_frame_iface, window); + libdecor_frame_set_app_id(window->frame, "libdecor-demo"); + libdecor_frame_set_title(window->frame, "libdecor demo"); + libdecor_frame_map(window->frame); + + gettimeofday(&start, NULL); + libdecor_frame_set_min_content_size(window->frame, 15 * chk, 10 * chk); + + while (libdecor_dispatch(context, timeout) >= 0) { + if (timed_quit) { + gettimeofday(&now, NULL); + if (now.tv_sec >= start.tv_sec + 2) { + fprintf(stderr, "Exiting due to --timed-quit\n"); + libdecor_frame_close(window->frame); + } + } + } + + free_outputs(); + + free(window); + + return EXIT_SUCCESS; +} diff --git a/libdecor/demo/egl.c b/libdecor/demo/egl.c new file mode 100644 index 000000000..3d7331645 --- /dev/null +++ b/libdecor/demo/egl.c @@ -0,0 +1,355 @@ +/* + * Copyright © 2011 Benjamin Franzke + * Copyright © 2010 Intel Corporation + * Copyright © 2018 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <math.h> +#include <EGL/egl.h> +#include <wayland-client.h> +#include <wayland-egl.h> +#include <libdecor.h> +#include <GL/gl.h> +#include <utils.h> + +static const size_t default_size = 200; + +struct client { + struct wl_display *display; + struct wl_compositor *compositor; + EGLDisplay egl_display; + EGLContext egl_context; +}; + +struct window { + struct client *client; + struct wl_surface *surface; + struct libdecor_frame *frame; + struct wl_egl_window *egl_window; + EGLSurface egl_surface; + int content_width; + int content_height; + int floating_width; + int floating_height; + bool open; + bool configured; +}; + +static void +frame_configure(struct libdecor_frame *frame, + struct libdecor_configuration *configuration, + void *user_data) +{ + struct window *window = user_data; + struct libdecor_state *state; + int width, height; + + if (!libdecor_configuration_get_content_size(configuration, frame, + &width, &height)) { + width = window->floating_width; + height = window->floating_height; + } + + window->content_width = width; + window->content_height = height; + + wl_egl_window_resize(window->egl_window, + window->content_width, window->content_height, + 0, 0); + + state = libdecor_state_new(width, height); + libdecor_frame_commit(frame, state, configuration); + libdecor_state_free(state); + + /* store floating dimensions */ + if (libdecor_frame_is_floating(window->frame)) { + window->floating_width = width; + window->floating_height = height; + } + + window->configured = true; +} + +static void +frame_close(struct libdecor_frame *frame, + void *user_data) +{ + struct window *window = user_data; + + window->open = false; +} + +static void +frame_commit(struct libdecor_frame *frame, + void *user_data) +{ + struct window *window = user_data; + + eglSwapBuffers(window->client->display, window->egl_surface); +} + +static struct libdecor_frame_interface frame_interface = { + frame_configure, + frame_close, + frame_commit, +}; + +static void +libdecor_error(struct libdecor *context, + enum libdecor_error error, + const char *message) +{ + fprintf(stderr, "Caught error (%d): %s\n", error, message); + exit(EXIT_FAILURE); +} + +static struct libdecor_interface libdecor_interface = { + libdecor_error, +}; + +static void +registry_global(void *data, + struct wl_registry *wl_registry, + uint32_t name, + const char *interface, + uint32_t version) +{ + struct client *client = data; + + if (strcmp(interface, wl_compositor_interface.name) == 0) { + client->compositor = wl_registry_bind(wl_registry, name, + &wl_compositor_interface, 1); + } +} + +static void +registry_global_remove(void *data, + struct wl_registry *wl_registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_global, + registry_global_remove +}; + +static bool +setup(struct window *window) +{ + static const 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_NONE + }; + + EGLint major, minor; + EGLint n; + EGLConfig config; + + window->client->egl_display = + eglGetDisplay((EGLNativeDisplayType)window->client->display); + + if (eglInitialize(window->client->egl_display, &major, &minor) == EGL_FALSE) { + fprintf(stderr, "Cannot initialise EGL!\n"); + return false; + } + + if (eglBindAPI(EGL_OPENGL_API) == EGL_FALSE) { + fprintf(stderr, "Cannot bind EGL API!\n"); + return false; + } + + if (eglChooseConfig(window->client->egl_display, + config_attribs, + &config, 1, &n) == EGL_FALSE) { + fprintf(stderr, "No matching EGL configurations!\n"); + return false; + } + + window->client->egl_context = eglCreateContext(window->client->egl_display, + config, EGL_NO_CONTEXT, NULL); + + if (window->client->egl_context == EGL_NO_CONTEXT) { + fprintf(stderr, "No EGL context!\n"); + return false; + } + + window->surface = wl_compositor_create_surface(window->client->compositor); + + window->egl_window = wl_egl_window_create(window->surface, + default_size, default_size); + + window->egl_surface = eglCreateWindowSurface( + window->client->egl_display, config, + (EGLNativeWindowType)window->egl_window, + NULL); + + eglMakeCurrent(window->client->egl_display, window->egl_surface, + window->egl_surface, window->client->egl_context); + + return true; +} + +static void +cleanup(struct window *window) +{ + if (window->client->egl_display) { + eglMakeCurrent(window->client->egl_display, EGL_NO_SURFACE, + EGL_NO_SURFACE, EGL_NO_CONTEXT); + } + if (window->egl_surface) { + eglDestroySurface(window->client->egl_display, window->egl_surface); + } + if (window->egl_window) { + wl_egl_window_destroy(window->egl_window); + } + if (window->surface) { + wl_surface_destroy(window->surface); + } + if (window->client->egl_context) { + eglDestroyContext(window->client->egl_display, window->client->egl_context); + } + if (window->client->egl_display) { + eglTerminate(window->client->egl_display); + } +} + +static float +hue_to_channel(const float *const hue, const int n) +{ + /* convert hue to rgb channels with saturation and value equal to 1 + * https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB_alternative + */ + const float k = fmod(n + ((*hue) * 3 / M_PI), 6); + return 1 - MAX(0, MIN(MIN(k, 4 - k), 1)); +} + +static void +hue_to_rgb(const float *const hue, float (*rgb)[3]) +{ + (*rgb)[0] = hue_to_channel(hue, 5); + (*rgb)[1] = hue_to_channel(hue, 3); + (*rgb)[2] = hue_to_channel(hue, 1); +} + +static void +draw(struct window *window) +{ + struct timespec tv; + double time; + + /* change of colour hue (HSV space) in rad/sec */ + static const float hue_change = (2 * M_PI) / 10; + float hue; + float rgb[3] = {0,0,0}; + + clock_gettime(CLOCK_REALTIME, &tv); + time = tv.tv_sec + tv.tv_nsec * 1e-9; + + hue = fmod(time * hue_change, 2 * M_PI); + + hue_to_rgb(&hue, &rgb); + + glClearColor(rgb[0], rgb[1], rgb[2], 1); + glClear(GL_COLOR_BUFFER_BIT); + + eglSwapBuffers(window->client->egl_display, window->egl_surface); +} + +int +main(int argc, char *argv[]) +{ + struct wl_registry *wl_registry; + struct libdecor *context = NULL; + struct window *window; + struct client *client; + int ret = EXIT_SUCCESS; + + client = calloc(1, sizeof(struct client)); + + client->display = wl_display_connect(NULL); + if (!client->display) { + fprintf(stderr, "No Wayland connection\n"); + free(client); + return EXIT_FAILURE; + } + + wl_registry = wl_display_get_registry(client->display); + wl_registry_add_listener(wl_registry, ®istry_listener, client); + wl_display_roundtrip(client->display); + + window = calloc(1, sizeof(struct window)); + window->client = client; + window->open = true; + window->configured = false; + window->floating_width = window->floating_height = default_size; + + if (!setup(window)) { + goto out; + } + + context = libdecor_new(client->display, &libdecor_interface); + window->frame = libdecor_decorate(context, window->surface, + &frame_interface, window); + libdecor_frame_set_app_id(window->frame, "egl-demo"); + libdecor_frame_set_title(window->frame, "EGL demo"); + libdecor_frame_map(window->frame); + + wl_display_roundtrip(client->display); + wl_display_roundtrip(client->display); + + /* wait for the first configure event */ + while (!window->configured) { + if (libdecor_dispatch(context, 0) < 0) { + ret = EXIT_FAILURE; + goto out; + } + } + + while (window->open) { + if (libdecor_dispatch(context, 0) < 0) { + ret = EXIT_FAILURE; + goto out; + } + draw(window); + } + +out: + if (context) { + libdecor_unref(context); + } + cleanup(window); + free(window); + free(client); + + return ret; +} diff --git a/libdecor/src/cursor-settings.c b/libdecor/src/cursor-settings.c new file mode 100644 index 000000000..b94623fd6 --- /dev/null +++ b/libdecor/src/cursor-settings.c @@ -0,0 +1,136 @@ +#include "cursor-settings.h" +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include "config.h" + +#ifdef HAS_DBUS +#include <dbus/dbus.h> + +static DBusMessage * +get_setting_sync(DBusConnection *const connection, + const char *key, + const char *value) +{ + DBusError error; + dbus_bool_t success; + DBusMessage *message; + DBusMessage *reply; + + dbus_error_init(&error); + + message = dbus_message_new_method_call( + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Settings", + "Read"); + + success = dbus_message_append_args(message, + DBUS_TYPE_STRING, &key, + DBUS_TYPE_STRING, &value, + DBUS_TYPE_INVALID); + + if (!success) + return NULL; + + reply = dbus_connection_send_with_reply_and_block( + connection, + message, + DBUS_TIMEOUT_USE_DEFAULT, + &error); + + dbus_message_unref(message); + + if (dbus_error_is_set(&error)) + return NULL; + + return reply; +} + +static bool +parse_type(DBusMessage *const reply, + const int type, + void *value) +{ + DBusMessageIter iter[3]; + + dbus_message_iter_init(reply, &iter[0]); + if (dbus_message_iter_get_arg_type(&iter[0]) != DBUS_TYPE_VARIANT) + return false; + + dbus_message_iter_recurse(&iter[0], &iter[1]); + if (dbus_message_iter_get_arg_type(&iter[1]) != DBUS_TYPE_VARIANT) + return false; + + dbus_message_iter_recurse(&iter[1], &iter[2]); + if (dbus_message_iter_get_arg_type(&iter[2]) != type) + return false; + + dbus_message_iter_get_basic(&iter[2], value); + + return true; +} + +bool +libdecor_get_cursor_settings(char **theme, int *size) +{ + static const char name[] = "org.gnome.desktop.interface"; + static const char key_theme[] = "cursor-theme"; + static const char key_size[] = "cursor-size"; + + DBusError error; + DBusConnection *connection; + DBusMessage *reply; + const char *value_theme = NULL; + + dbus_error_init(&error); + + connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + + if (dbus_error_is_set(&error)) + return false; + + reply = get_setting_sync(connection, name, key_theme); + if (!reply) + return false; + + if (!parse_type(reply, DBUS_TYPE_STRING, &value_theme)) { + dbus_message_unref(reply); + return false; + } + + *theme = strdup(value_theme); + + dbus_message_unref(reply); + + reply = get_setting_sync(connection, name, key_size); + if (!reply) + return false; + + if (!parse_type(reply, DBUS_TYPE_INT32, size)) { + dbus_message_unref(reply); + return false; + } + + dbus_message_unref(reply); + + return true; +} +#else +bool +libdecor_get_cursor_settings(char **theme, int *size) +{ + char *env_xtheme; + char *env_xsize; + + env_xtheme = getenv("XCURSOR_THEME"); + if (env_xtheme != NULL) + *theme = strdup(env_xtheme); + + env_xsize = getenv("XCURSOR_SIZE"); + if (env_xsize != NULL) + *size = atoi(env_xsize); + + return env_xtheme != NULL && env_xsize != NULL; +} +#endif diff --git a/libdecor/src/cursor-settings.h b/libdecor/src/cursor-settings.h new file mode 100644 index 000000000..700bb4f30 --- /dev/null +++ b/libdecor/src/cursor-settings.h @@ -0,0 +1,6 @@ +#pragma once + +#include <stdbool.h> + +bool +libdecor_get_cursor_settings(char **theme, int *size); diff --git a/libdecor/src/libdecor-fallback.c b/libdecor/src/libdecor-fallback.c new file mode 100644 index 000000000..c281197c2 --- /dev/null +++ b/libdecor/src/libdecor-fallback.c @@ -0,0 +1,209 @@ +/* + * Copyright © 2019 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include "libdecor-fallback.h" + +#include <poll.h> +#include <errno.h> + +#include "utils.h" + +struct libdecor_plugin_fallback { + struct libdecor_plugin plugin; + struct libdecor *context; +}; + +static void +libdecor_plugin_fallback_destroy(struct libdecor_plugin *plugin) +{ + libdecor_plugin_release(plugin); + free(plugin); +} + +static int +libdecor_plugin_fallback_get_fd(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_fallback *plugin_fallback = + (struct libdecor_plugin_fallback *) plugin; + struct wl_display *wl_display = + libdecor_get_wl_display(plugin_fallback->context); + + return wl_display_get_fd(wl_display); +} + +static int +libdecor_plugin_fallback_dispatch(struct libdecor_plugin *plugin, + int timeout) +{ + struct libdecor_plugin_fallback *plugin_fallback = + (struct libdecor_plugin_fallback *) plugin; + struct wl_display *wl_display = + libdecor_get_wl_display(plugin_fallback->context); + struct pollfd fds[1]; + int ret; + int dispatch_count = 0; + + while (wl_display_prepare_read(wl_display) != 0) + dispatch_count += wl_display_dispatch_pending(wl_display); + + if (wl_display_flush(wl_display) < 0 && + errno != EAGAIN) { + wl_display_cancel_read(wl_display); + return -errno; + } + + fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN }; + + ret = poll(fds, ARRAY_SIZE (fds), timeout); + if (ret > 0) { + if (fds[0].revents & POLLIN) { + wl_display_read_events(wl_display); + dispatch_count += wl_display_dispatch_pending(wl_display); + return dispatch_count; + } else { + wl_display_cancel_read(wl_display); + return dispatch_count; + } + } else if (ret == 0) { + wl_display_cancel_read(wl_display); + return dispatch_count; + } else { + wl_display_cancel_read(wl_display); + return -errno; + } +} + +static struct libdecor_frame * +libdecor_plugin_fallback_frame_new(struct libdecor_plugin *plugin) +{ + struct libdecor_frame *frame; + + frame = zalloc(sizeof *frame); + + return frame; +} + +static void +libdecor_plugin_fallback_frame_free(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ +} + +static void +libdecor_plugin_fallback_frame_commit(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration) +{ +} + +static void +libdecor_plugin_fallback_frame_property_changed(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ +} + +static void +libdecor_plugin_fallback_frame_translate_coordinate(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + int content_x, + int content_y, + int *frame_x, + int *frame_y) +{ + *frame_x = content_x; + *frame_y = content_y; +} + +static void +libdecor_plugin_fallback_frame_popup_grab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ +} + +static void +libdecor_plugin_fallback_frame_popup_ungrab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ +} + +static bool +libdecor_plugin_fallback_configuration_get_content_size(struct libdecor_plugin *plugin, + struct libdecor_configuration *configuration, + struct libdecor_frame *frame, + int *content_width, + int *content_height) +{ + return libdecor_configuration_get_window_size(configuration, + content_width, + content_height); +} + +static bool +libdecor_plugin_fallback_frame_get_window_size_for( + struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + int *window_width, + int *window_height) +{ + *window_width = libdecor_state_get_content_width (state); + *window_height = libdecor_state_get_content_height (state); + return true; +} + +static struct libdecor_plugin_interface fallback_plugin_iface = { + .destroy = libdecor_plugin_fallback_destroy, + .get_fd = libdecor_plugin_fallback_get_fd, + .dispatch = libdecor_plugin_fallback_dispatch, + .frame_new = libdecor_plugin_fallback_frame_new, + .frame_free = libdecor_plugin_fallback_frame_free, + .frame_commit = libdecor_plugin_fallback_frame_commit, + .frame_property_changed = libdecor_plugin_fallback_frame_property_changed, + .frame_translate_coordinate = + libdecor_plugin_fallback_frame_translate_coordinate, + .frame_popup_grab = libdecor_plugin_fallback_frame_popup_grab, + .frame_popup_ungrab = libdecor_plugin_fallback_frame_popup_ungrab, + .configuration_get_content_size = libdecor_plugin_fallback_configuration_get_content_size, + .frame_get_window_size_for = libdecor_plugin_fallback_frame_get_window_size_for, +}; + +struct libdecor_plugin * +libdecor_fallback_plugin_new(struct libdecor *context) +{ + struct libdecor_plugin_fallback *plugin; + + plugin = zalloc(sizeof *plugin); + libdecor_plugin_init(&plugin->plugin, context, &fallback_plugin_iface); + plugin->context = context; + + libdecor_notify_plugin_ready(context); + + return &plugin->plugin; +} diff --git a/libdecor/src/libdecor-fallback.h b/libdecor/src/libdecor-fallback.h new file mode 100644 index 000000000..40a5c51d2 --- /dev/null +++ b/libdecor/src/libdecor-fallback.h @@ -0,0 +1,35 @@ +/* + * Copyright © 2019 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef LIBDECOR_FALLBACK_H +#define LIBDECOR_FALLBACK_H + +#include "libdecor.h" +#include "libdecor-plugin.h" + +struct libdecor_plugin * +libdecor_fallback_plugin_new(struct libdecor *context); + +#endif /* LIBDECOR_FALLBACK_H */ diff --git a/libdecor/src/libdecor-plugin.h b/libdecor/src/libdecor-plugin.h new file mode 100644 index 000000000..9e5d9510b --- /dev/null +++ b/libdecor/src/libdecor-plugin.h @@ -0,0 +1,215 @@ +/* + * Copyright © 2017-2018 Red Hat Inc. + * Copyright © 2018 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef LIBDECOR_PLUGIN_H +#define LIBDECOR_PLUGIN_H + +#include "libdecor.h" + +struct libdecor_frame_private; + +struct libdecor_frame { + struct libdecor_frame_private *priv; + struct wl_list link; +}; + +struct libdecor_plugin_private; + +struct libdecor_plugin { + struct libdecor_plugin_private *priv; +}; + +typedef struct libdecor_plugin * (* libdecor_plugin_constructor)(struct libdecor *context); + +#define LIBDECOR_PLUGIN_PRIORITY_HIGH 1000 +#define LIBDECOR_PLUGIN_PRIORITY_MEDIUM 100 +#define LIBDECOR_PLUGIN_PRIORITY_LOW 0 + +struct libdecor_plugin_priority { + const char *desktop; + int priority; +}; + +enum libdecor_plugin_capabilities { + LIBDECOR_PLUGIN_CAPABILITY_BASE = 1 << 0, +}; + +struct libdecor_plugin_description { + /* API version the plugin is compatible with. */ + int api_version; + + /* Human readable string describing the plugin. */ + char *description; + + /* A plugin has a bitmask of capabilities. The plugin loader can use this + * to load a plugin with the right capabilities. */ + enum libdecor_plugin_capabilities capabilities; + + /* + * The priorities field points to a list of per desktop priorities. + * properties[i].desktop is matched against XDG_CURRENT_DESKTOP when + * determining what plugin to use. The last entry in the list MUST have + * the priorities[i].desktop pointer set to NULL as a default + * priority. + */ + const struct libdecor_plugin_priority *priorities; + + /* Vfunc used for constructing a plugin instance. */ + libdecor_plugin_constructor constructor; +}; + +struct libdecor_plugin_interface { + void (* destroy)(struct libdecor_plugin *plugin); + + int (* get_fd)(struct libdecor_plugin *plugin); + int (* dispatch)(struct libdecor_plugin *plugin, + int timeout); + + struct libdecor_frame * (* frame_new)(struct libdecor_plugin *plugin); + void (* frame_free)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame); + void (* frame_commit)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration); + void (*frame_property_changed)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame); + void (* frame_translate_coordinate)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + int content_x, + int content_y, + int *window_x, + int *window_y); + void (* frame_popup_grab)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name); + void (* frame_popup_ungrab)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name); + + bool (* frame_get_window_size_for)(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + int *window_width, + int *window_height); + + bool (* configuration_get_content_size)(struct libdecor_plugin *plugin, + struct libdecor_configuration *configuration, + struct libdecor_frame *frame, + int *content_width, + int *content_height); + + /* Reserved */ + void (* reserved0)(void); + void (* reserved1)(void); + void (* reserved2)(void); + void (* reserved3)(void); + void (* reserved4)(void); + void (* reserved5)(void); + void (* reserved6)(void); + void (* reserved7)(void); + void (* reserved8)(void); + void (* reserved9)(void); +}; + +struct wl_surface * +libdecor_frame_get_wl_surface(struct libdecor_frame *frame); + +int +libdecor_frame_get_content_width(struct libdecor_frame *frame); + +int +libdecor_frame_get_content_height(struct libdecor_frame *frame); + +enum libdecor_window_state +libdecor_frame_get_window_state(struct libdecor_frame *frame); + +void +libdecor_frame_set_window_geometry(struct libdecor_frame *frame, + int32_t x, int32_t y, + int32_t width, int32_t height); + +enum libdecor_capabilities +libdecor_frame_get_capabilities(const struct libdecor_frame *frame); + +void +libdecor_frame_dismiss_popup(struct libdecor_frame *frame, + const char *seat_name); + +void +libdecor_frame_toplevel_commit(struct libdecor_frame *frame); + +struct wl_display * +libdecor_get_wl_display(struct libdecor *context); + +void +libdecor_notify_plugin_ready(struct libdecor *context); + +void +libdecor_notify_plugin_error(struct libdecor *context, + enum libdecor_error error, + const char *__restrict fmt, + ...); + +int +libdecor_state_get_content_width (struct libdecor_state *state); + +int +libdecor_state_get_content_height (struct libdecor_state *state); + +enum libdecor_window_state +libdecor_state_get_window_state(struct libdecor_state *state); + +bool +libdecor_configuration_get_window_size(struct libdecor_configuration *configuration, + int *width, + int *height); + +int +libdecor_plugin_init(struct libdecor_plugin *plugin, + struct libdecor *context, + struct libdecor_plugin_interface *iface); + +void +libdecor_plugin_release(struct libdecor_plugin *plugin); + +/* + * Get the min content size as set before with libdecor_frame_set_min_content_size(). + */ +void +libdecor_frame_get_min_content_size(struct libdecor_frame *frame, + int *pcontent_width, + int *pcontent_height); + +/* + * Get the max content size as set before with libdecor_frame_set_max_content_size(). + */ +void +libdecor_frame_get_max_content_size(struct libdecor_frame *frame, + int *pcontent_width, + int *pcontent_height); + +#endif /* LIBDECOR_PLUGIN_H */ diff --git a/libdecor/src/libdecor.c b/libdecor/src/libdecor.c new file mode 100644 index 000000000..2de2da3be --- /dev/null +++ b/libdecor/src/libdecor.c @@ -0,0 +1,1664 @@ +/* + * Copyright © 2017-2018 Red Hat Inc. + * Copyright © 2018 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include <errno.h> +#include <dirent.h> +#include <dlfcn.h> +#include <stdio.h> +#include <string.h> +#include <sys/types.h> + +#include "libdecor.h" +#include "libdecor-fallback.h" +#include "libdecor-plugin.h" +#include "utils.h" + +#include "xdg-shell-client-protocol.h" +#include "xdg-decoration-client-protocol.h" + +struct libdecor { + int ref_count; + + struct libdecor_interface *iface; + + struct libdecor_plugin *plugin; + bool plugin_ready; + + struct wl_display *wl_display; + struct wl_registry *wl_registry; + struct xdg_wm_base *xdg_wm_base; + struct zxdg_decoration_manager_v1 *decoration_manager; + + struct wl_callback *init_callback; + bool init_done; + bool has_error; + + struct wl_list frames; +}; + +struct libdecor_state { + enum libdecor_window_state window_state; + + int content_width; + int content_height; +}; + +struct libdecor_limits { + int min_width; + int min_height; + int max_width; + int max_height; +}; + +struct libdecor_configuration { + uint32_t serial; + + bool has_window_state; + enum libdecor_window_state window_state; + + bool has_size; + int window_width; + int window_height; +}; + +struct libdecor_frame_private { + int ref_count; + + struct libdecor *context; + + struct wl_surface *wl_surface; + + struct libdecor_frame_interface *iface; + void *user_data; + + struct xdg_surface *xdg_surface; + struct xdg_toplevel *xdg_toplevel; + struct zxdg_toplevel_decoration_v1 *toplevel_decoration; + + bool pending_map; + + struct { + char *app_id; + char *title; + struct libdecor_limits content_limits; + struct xdg_toplevel *parent; + } state; + + struct libdecor_configuration *pending_configuration; + + int content_width; + int content_height; + + enum libdecor_window_state window_state; + + enum zxdg_toplevel_decoration_v1_mode decoration_mode; + + enum libdecor_capabilities capabilities; + + /* original limits for interactive resize */ + struct libdecor_limits interactive_limits; + + bool visible; +}; + +struct libdecor_plugin_private { + struct libdecor_plugin_interface *iface; +}; + +/* gather all states at which a window is non-floating */ +static const enum libdecor_window_state states_non_floating = + LIBDECOR_WINDOW_STATE_MAXIMIZED | LIBDECOR_WINDOW_STATE_FULLSCREEN | + LIBDECOR_WINDOW_STATE_TILED_LEFT | LIBDECOR_WINDOW_STATE_TILED_RIGHT | + LIBDECOR_WINDOW_STATE_TILED_TOP | LIBDECOR_WINDOW_STATE_TILED_BOTTOM; + +static bool +streql(const char *str1, const char *str2) +{ + return (str1 && str2) && (strcmp(str1, str2) == 0); +} + + +static void +do_map(struct libdecor_frame *frame); + +static bool +state_is_floating(enum libdecor_window_state window_state) +{ + return !(window_state & states_non_floating); +} + +static void +constrain_content_size(const struct libdecor_frame *frame, + int *width, + int *height) +{ + const struct libdecor_limits lim = frame->priv->state.content_limits; + + if (lim.min_width > 0) + *width = MAX(lim.min_width, *width); + if (lim.max_width > 0) + *width = MIN(*width, lim.max_width); + + if (lim.min_height > 0) + *height = MAX(lim.min_height, *height); + if (lim.max_height > 0) + *height = MIN(*height, lim.max_height); +} + +static bool +frame_has_visible_client_side_decoration(struct libdecor_frame *frame) +{ + /* visibility by client configuration */ + const bool vis_client = frame->priv->visible; + /* visibility by compositor configuration */ + const bool vis_server = (frame->priv->decoration_mode == + ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE); + + return vis_client && vis_server; +} + +LIBDECOR_EXPORT int +libdecor_state_get_content_width(struct libdecor_state *state) +{ + return state->content_width; +} + +LIBDECOR_EXPORT int +libdecor_state_get_content_height(struct libdecor_state *state) +{ + return state->content_height; +} + +LIBDECOR_EXPORT enum libdecor_window_state +libdecor_state_get_window_state(struct libdecor_state *state) +{ + return state->window_state; +} + +LIBDECOR_EXPORT struct libdecor_state * +libdecor_state_new(int width, + int height) +{ + struct libdecor_state *state; + + state = zalloc(sizeof *state); + state->content_width = width; + state->content_height = height; + + return state; +} + +LIBDECOR_EXPORT void +libdecor_state_free(struct libdecor_state *state) +{ + free(state); +} + +static struct libdecor_configuration * +libdecor_configuration_new(void) +{ + struct libdecor_configuration *configuration; + + configuration = zalloc(sizeof *configuration); + + return configuration; +} + +static void +libdecor_configuration_free(struct libdecor_configuration *configuration) +{ + free(configuration); +} + +static bool +frame_get_window_size_for(struct libdecor_frame *frame, + struct libdecor_state *state, + int *window_width, + int *window_height) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + if (frame_has_visible_client_side_decoration(frame)) { + return plugin->priv->iface->frame_get_window_size_for( + plugin, frame, state, + window_width, window_height); + } else { + *window_width = state->content_width; + *window_height = state->content_height; + return true; + } +} + +static bool +window_size_to_content_size(struct libdecor_configuration *configuration, + struct libdecor_frame *frame, + int *content_width, + int *content_height) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + if (frame_has_visible_client_side_decoration(frame)) { + return plugin->priv->iface->configuration_get_content_size( + plugin, configuration, frame, + content_width, content_height); + } else { + *content_width = configuration->window_width; + *content_height = configuration->window_height; + return true; + } +} + +LIBDECOR_EXPORT bool +libdecor_configuration_get_content_size(struct libdecor_configuration *configuration, + struct libdecor_frame *frame, + int *width, + int *height) +{ + int content_width; + int content_height; + + if (!configuration->has_size) + return false; + + if (configuration->window_width == 0 || configuration->window_height == 0) + return false; + + if (!window_size_to_content_size(configuration, + frame, + &content_width, + &content_height)) + return false; + + *width = content_width; + *height = content_height; + + if (state_is_floating(configuration->window_state)) { + constrain_content_size(frame, width, height); + } + + return true; +} + +LIBDECOR_EXPORT bool +libdecor_configuration_get_window_size(struct libdecor_configuration *configuration, + int *width, + int *height) +{ + if (!configuration->has_size) + return false; + + if (configuration->window_width == 0 || configuration->window_height == 0) + return false; + + *width = configuration->window_width; + *height = configuration->window_height; + return true; +} + +LIBDECOR_EXPORT bool +libdecor_configuration_get_window_state(struct libdecor_configuration *configuration, + enum libdecor_window_state *window_state) +{ + if (!configuration->has_window_state) + return false; + + *window_state = configuration->window_state; + return true; +} + +static void +xdg_surface_configure(void *user_data, + struct xdg_surface *xdg_surface, + uint32_t serial) +{ + struct libdecor_frame *frame = user_data; + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor_configuration *configuration; + + configuration = frame_priv->pending_configuration; + frame_priv->pending_configuration = NULL; + + if (!configuration) + configuration = libdecor_configuration_new(); + + configuration->serial = serial; + + frame_priv->iface->configure(frame, + configuration, + frame_priv->user_data); + + libdecor_configuration_free(configuration); +} + +static const struct xdg_surface_listener xdg_surface_listener = { + xdg_surface_configure, +}; + +static enum libdecor_window_state +parse_states(struct wl_array *states) +{ + enum libdecor_window_state pending_state = LIBDECOR_WINDOW_STATE_NONE; + uint32_t *p; + + wl_array_for_each(p, states) { + enum xdg_toplevel_state state = *p; + + switch (state) { + case XDG_TOPLEVEL_STATE_FULLSCREEN: + pending_state |= LIBDECOR_WINDOW_STATE_FULLSCREEN; + break; + case XDG_TOPLEVEL_STATE_MAXIMIZED: + pending_state |= LIBDECOR_WINDOW_STATE_MAXIMIZED; + break; + case XDG_TOPLEVEL_STATE_ACTIVATED: + pending_state |= LIBDECOR_WINDOW_STATE_ACTIVE; + break; + case XDG_TOPLEVEL_STATE_TILED_LEFT: + pending_state |= LIBDECOR_WINDOW_STATE_TILED_LEFT; + break; + case XDG_TOPLEVEL_STATE_TILED_RIGHT: + pending_state |= LIBDECOR_WINDOW_STATE_TILED_RIGHT; + break; + case XDG_TOPLEVEL_STATE_TILED_TOP: + pending_state |= LIBDECOR_WINDOW_STATE_TILED_TOP; + break; + case XDG_TOPLEVEL_STATE_TILED_BOTTOM: + pending_state |= LIBDECOR_WINDOW_STATE_TILED_BOTTOM; + break; + default: + break; + } + } + + return pending_state; +} + +static void +xdg_toplevel_configure(void *user_data, + struct xdg_toplevel *xdg_toplevel, + int32_t width, + int32_t height, + struct wl_array *states) +{ + struct libdecor_frame *frame = user_data; + struct libdecor_frame_private *frame_priv = frame->priv; + enum libdecor_window_state window_state; + + window_state = parse_states(states); + + frame_priv->pending_configuration = libdecor_configuration_new(); + + frame_priv->pending_configuration->has_size = true; + frame_priv->pending_configuration->window_width = width; + frame_priv->pending_configuration->window_height = height; + + frame_priv->pending_configuration->has_window_state = true; + frame_priv->pending_configuration->window_state = window_state; +} + +static void +xdg_toplevel_close(void *user_data, + struct xdg_toplevel *xdg_toplevel) +{ + struct libdecor_frame *frame = user_data; + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->iface->close(frame, frame_priv->user_data); +} + +static const struct xdg_toplevel_listener xdg_toplevel_listener = { + xdg_toplevel_configure, + xdg_toplevel_close, +}; + +static void +toplevel_decoration_configure( + void *data, + struct zxdg_toplevel_decoration_v1 *zxdg_toplevel_decoration_v1, + uint32_t mode) +{ + ((struct libdecor_frame_private *)(data))->decoration_mode = mode; +} + +static const struct zxdg_toplevel_decoration_v1_listener + xdg_toplevel_decoration_listener = { + toplevel_decoration_configure, +}; + +void +libdecor_frame_create_xdg_decoration(struct libdecor_frame_private *frame_priv) +{ + if (!frame_priv->context->decoration_manager) + return; + + frame_priv->toplevel_decoration = + zxdg_decoration_manager_v1_get_toplevel_decoration( + frame_priv->context->decoration_manager, + frame_priv->xdg_toplevel); + + zxdg_toplevel_decoration_v1_add_listener( + frame_priv->toplevel_decoration, + &xdg_toplevel_decoration_listener, + frame_priv); +} + +static void +init_shell_surface(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + + if (frame_priv->xdg_surface) + return; + + frame_priv->xdg_surface = + xdg_wm_base_get_xdg_surface(context->xdg_wm_base, + frame_priv->wl_surface); + xdg_surface_add_listener(frame_priv->xdg_surface, + &xdg_surface_listener, + frame); + + frame_priv->xdg_toplevel = + xdg_surface_get_toplevel(frame_priv->xdg_surface); + xdg_toplevel_add_listener(frame_priv->xdg_toplevel, + &xdg_toplevel_listener, + frame); + + frame_priv->decoration_mode = + ZXDG_TOPLEVEL_DECORATION_V1_MODE_CLIENT_SIDE; + frame_priv->toplevel_decoration = NULL; + libdecor_frame_create_xdg_decoration(frame_priv); + + if (frame_priv->state.parent) { + xdg_toplevel_set_parent(frame_priv->xdg_toplevel, + frame_priv->state.parent); + } + if (frame_priv->state.title) { + xdg_toplevel_set_title(frame_priv->xdg_toplevel, + frame_priv->state.title); + } + if (frame_priv->state.app_id) { + xdg_toplevel_set_app_id(frame_priv->xdg_toplevel, + frame_priv->state.app_id); + } + + if (frame_priv->pending_map) + do_map(frame); +} + +LIBDECOR_EXPORT struct libdecor_frame * +libdecor_decorate(struct libdecor *context, + struct wl_surface *wl_surface, + struct libdecor_frame_interface *iface, + void *user_data) +{ + struct libdecor_plugin *plugin = context->plugin; + struct libdecor_frame *frame; + struct libdecor_frame_private *frame_priv; + + if (context->has_error) + return NULL; + + frame = plugin->priv->iface->frame_new(plugin); + if (!frame) + return NULL; + + frame_priv = zalloc(sizeof *frame_priv); + frame->priv = frame_priv; + + frame_priv->ref_count = 1; + frame_priv->context = context; + + frame_priv->wl_surface = wl_surface; + frame_priv->iface = iface; + frame_priv->user_data = user_data; + + wl_list_insert(&context->frames, &frame->link); + + libdecor_frame_set_capabilities(frame, + LIBDECOR_ACTION_MOVE | + LIBDECOR_ACTION_RESIZE | + LIBDECOR_ACTION_MINIMIZE | + LIBDECOR_ACTION_FULLSCREEN | + LIBDECOR_ACTION_CLOSE); + + frame_priv->visible = true; + + if (context->init_done) + init_shell_surface(frame); + + return frame; +} + +LIBDECOR_EXPORT void +libdecor_frame_ref(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->ref_count++; +} + +LIBDECOR_EXPORT void +libdecor_frame_unref(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->ref_count--; + if (frame_priv->ref_count == 0) { + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + wl_list_remove(&frame->link); + + if (frame_priv->xdg_toplevel) + xdg_toplevel_destroy(frame_priv->xdg_toplevel); + if (frame_priv->xdg_surface) + xdg_surface_destroy(frame_priv->xdg_surface); + + plugin->priv->iface->frame_free(plugin, frame); + + free(frame_priv->state.title); + free(frame_priv->state.app_id); + + free(frame_priv); + + free(frame); + } +} + +LIBDECOR_EXPORT void +libdecor_frame_set_visibility(struct libdecor_frame *frame, + bool visible) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + frame_priv->visible = visible; + + /* enable/disable decorations that are managed by the compositor, + * only xdg-decoration version 2 and above allows to toggle decoration */ + if (context->decoration_manager && + zxdg_decoration_manager_v1_get_version(context->decoration_manager) > 1) { + if (frame_priv->visible && + frame_priv->toplevel_decoration == NULL) { + /* - request to SHOW decorations + * - decorations are NOT HANDLED + * => create new decorations for already mapped surface */ + libdecor_frame_create_xdg_decoration(frame_priv); + } else if (!frame_priv->visible && + frame_priv->toplevel_decoration != NULL) { + /* - request to HIDE decorations + * - decorations are HANDLED + * => destroy decorations */ + zxdg_toplevel_decoration_v1_destroy(frame_priv->toplevel_decoration); + frame_priv->toplevel_decoration = NULL; + } + } + + /* enable/disable decorations that are managed by a plugin */ + if (frame_has_visible_client_side_decoration(frame)) { + /* show client-side decorations */ + plugin->priv->iface->frame_commit(plugin, frame, NULL, NULL); + } else { + /* destroy client-side decorations */ + plugin->priv->iface->frame_free(plugin, frame); + + libdecor_frame_set_window_geometry(frame, 0, 0, + frame_priv->content_width, + frame_priv->content_height); + } + + libdecor_frame_toplevel_commit(frame); +} + +LIBDECOR_EXPORT bool +libdecor_frame_is_visible(struct libdecor_frame *frame) +{ + return frame->priv->visible; +} + +LIBDECOR_EXPORT void +libdecor_frame_set_parent(struct libdecor_frame *frame, + struct libdecor_frame *parent) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + if (!frame_priv->xdg_toplevel) + return; + + frame_priv->state.parent = parent->priv->xdg_toplevel; + + xdg_toplevel_set_parent(frame_priv->xdg_toplevel, + parent->priv->xdg_toplevel); +} + +LIBDECOR_EXPORT void +libdecor_frame_set_title(struct libdecor_frame *frame, + const char *title) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor_plugin *plugin = frame_priv->context->plugin; + + if (!streql(frame_priv->state.title, title)) { + free(frame_priv->state.title); + frame_priv->state.title = strdup(title); + + if (!frame_priv->xdg_toplevel) + return; + + xdg_toplevel_set_title(frame_priv->xdg_toplevel, title); + + plugin->priv->iface->frame_property_changed(plugin, frame); + } +} + +LIBDECOR_EXPORT const char * +libdecor_frame_get_title(struct libdecor_frame *frame) +{ + return frame->priv->state.title; +} + +LIBDECOR_EXPORT void +libdecor_frame_set_app_id(struct libdecor_frame *frame, + const char *app_id) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + free(frame_priv->state.app_id); + frame_priv->state.app_id = strdup(app_id); + + if (!frame_priv->xdg_toplevel) + return; + + xdg_toplevel_set_app_id(frame_priv->xdg_toplevel, app_id); +} + +static void +notify_on_capability_change(struct libdecor_frame *frame, + const enum libdecor_capabilities old_capabilities) +{ + struct libdecor_plugin *plugin = frame->priv->context->plugin; + struct libdecor_state *state; + + if (frame->priv->capabilities == old_capabilities) + return; + + if (frame->priv->content_width == 0 || + frame->priv->content_height == 0) + return; + + plugin->priv->iface->frame_property_changed(plugin, frame); + + if (!libdecor_frame_has_capability(frame, LIBDECOR_ACTION_RESIZE)) { + frame->priv->interactive_limits = frame->priv->state.content_limits; + /* set fixed window size */ + libdecor_frame_set_min_content_size(frame, + frame->priv->content_width, + frame->priv->content_height); + libdecor_frame_set_max_content_size(frame, + frame->priv->content_width, + frame->priv->content_height); + } else { + /* restore old limits */ + frame->priv->state.content_limits = frame->priv->interactive_limits; + } + + state = libdecor_state_new(frame->priv->content_width, + frame->priv->content_height); + libdecor_frame_commit(frame, state, NULL); + libdecor_state_free(state); + + libdecor_frame_toplevel_commit(frame); +} + +LIBDECOR_EXPORT void +libdecor_frame_set_capabilities(struct libdecor_frame *frame, + enum libdecor_capabilities capabilities) +{ + const enum libdecor_capabilities old_capabilities = + frame->priv->capabilities; + + frame->priv->capabilities |= capabilities; + + notify_on_capability_change(frame, old_capabilities); +} + +LIBDECOR_EXPORT void +libdecor_frame_unset_capabilities(struct libdecor_frame *frame, + enum libdecor_capabilities capabilities) +{ + const enum libdecor_capabilities old_capabilities = + frame->priv->capabilities; + + frame->priv->capabilities &= ~capabilities; + + notify_on_capability_change(frame, old_capabilities); +} + +LIBDECOR_EXPORT bool +libdecor_frame_has_capability(struct libdecor_frame *frame, + enum libdecor_capabilities capability) +{ + return frame->priv->capabilities & capability; +} + +LIBDECOR_EXPORT void +libdecor_frame_popup_grab(struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + plugin->priv->iface->frame_popup_grab(plugin, frame, seat_name); +} + +LIBDECOR_EXPORT void +libdecor_frame_popup_ungrab(struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + plugin->priv->iface->frame_popup_ungrab(plugin, frame, seat_name); +} + +LIBDECOR_EXPORT void +libdecor_frame_dismiss_popup(struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->iface->dismiss_popup(frame, seat_name, frame_priv->user_data); +} + +LIBDECOR_EXPORT void +libdecor_frame_show_window_menu(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial, + int x, + int y) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + if (!frame_priv->xdg_toplevel) { + fprintf(stderr, "Can't show window menu before being mapped\n"); + return; + } + + xdg_toplevel_show_window_menu(frame_priv->xdg_toplevel, + wl_seat, serial, + x, y); +} + +LIBDECOR_EXPORT void +libdecor_frame_translate_coordinate(struct libdecor_frame *frame, + int content_x, + int content_y, + int *frame_x, + int *frame_y) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + plugin->priv->iface->frame_translate_coordinate(plugin, frame, + content_x, content_y, + frame_x, frame_y); +} + +LIBDECOR_EXPORT void +libdecor_frame_set_max_content_size(struct libdecor_frame *frame, + int content_width, + int content_height) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->state.content_limits.max_width = content_width; + frame_priv->state.content_limits.max_height = content_height; +} + +LIBDECOR_EXPORT void +libdecor_frame_set_min_content_size(struct libdecor_frame *frame, + int content_width, + int content_height) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->state.content_limits.min_width = content_width; + frame_priv->state.content_limits.min_height = content_height; +} + +LIBDECOR_EXPORT void +libdecor_frame_get_min_content_size(struct libdecor_frame *frame, + int *pcontent_width, + int *pcontent_height) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + *pcontent_width = frame_priv->state.content_limits.min_width; + *pcontent_height = frame_priv->state.content_limits.min_height; +} + +LIBDECOR_EXPORT void +libdecor_frame_get_max_content_size(struct libdecor_frame *frame, + int *pcontent_width, + int *pcontent_height) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + *pcontent_width = frame_priv->state.content_limits.max_width; + *pcontent_height = frame_priv->state.content_limits.max_height; +} + +LIBDECOR_EXPORT void +libdecor_frame_set_window_geometry(struct libdecor_frame *frame, + int32_t x, int32_t y, + int32_t width, int32_t height) +{ + xdg_surface_set_window_geometry(frame->priv->xdg_surface, x, y, width, height); +} + +LIBDECOR_EXPORT enum libdecor_capabilities +libdecor_frame_get_capabilities(const struct libdecor_frame *frame) +{ + return frame->priv->capabilities; +} + +enum xdg_toplevel_resize_edge +edge_to_xdg_edge(enum libdecor_resize_edge edge) +{ + switch (edge) { + case LIBDECOR_RESIZE_EDGE_NONE: + return XDG_TOPLEVEL_RESIZE_EDGE_NONE; + case LIBDECOR_RESIZE_EDGE_TOP: + return XDG_TOPLEVEL_RESIZE_EDGE_TOP; + case LIBDECOR_RESIZE_EDGE_BOTTOM: + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM; + case LIBDECOR_RESIZE_EDGE_LEFT: + return XDG_TOPLEVEL_RESIZE_EDGE_LEFT; + case LIBDECOR_RESIZE_EDGE_TOP_LEFT: + return XDG_TOPLEVEL_RESIZE_EDGE_TOP_LEFT; + case LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT: + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_LEFT; + case LIBDECOR_RESIZE_EDGE_RIGHT: + return XDG_TOPLEVEL_RESIZE_EDGE_RIGHT; + case LIBDECOR_RESIZE_EDGE_TOP_RIGHT: + return XDG_TOPLEVEL_RESIZE_EDGE_TOP_RIGHT; + case LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT: + return XDG_TOPLEVEL_RESIZE_EDGE_BOTTOM_RIGHT; + } + + abort(); +} + +LIBDECOR_EXPORT void +libdecor_frame_resize(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial, + enum libdecor_resize_edge edge) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + enum xdg_toplevel_resize_edge xdg_edge; + + xdg_edge = edge_to_xdg_edge(edge); + xdg_toplevel_resize(frame_priv->xdg_toplevel, + wl_seat, serial, xdg_edge); +} + +LIBDECOR_EXPORT void +libdecor_frame_move(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + xdg_toplevel_move(frame_priv->xdg_toplevel, wl_seat, serial); +} + +LIBDECOR_EXPORT void +libdecor_frame_set_minimized(struct libdecor_frame *frame) +{ + xdg_toplevel_set_minimized(frame->priv->xdg_toplevel); +} + +LIBDECOR_EXPORT void +libdecor_frame_set_maximized(struct libdecor_frame *frame) +{ + xdg_toplevel_set_maximized(frame->priv->xdg_toplevel); +} + +LIBDECOR_EXPORT void +libdecor_frame_unset_maximized(struct libdecor_frame *frame) +{ + xdg_toplevel_unset_maximized(frame->priv->xdg_toplevel); +} + +LIBDECOR_EXPORT void +libdecor_frame_set_fullscreen(struct libdecor_frame *frame, + struct wl_output *output) +{ + xdg_toplevel_set_fullscreen(frame->priv->xdg_toplevel, output); +} + +LIBDECOR_EXPORT void +libdecor_frame_unset_fullscreen(struct libdecor_frame *frame) +{ + xdg_toplevel_unset_fullscreen(frame->priv->xdg_toplevel); +} + +LIBDECOR_EXPORT bool +libdecor_frame_is_floating(struct libdecor_frame *frame) +{ + return state_is_floating(frame->priv->window_state); +} + +LIBDECOR_EXPORT void +libdecor_frame_close(struct libdecor_frame *frame) +{ + xdg_toplevel_close(frame, frame->priv->xdg_toplevel); +} + +bool +valid_limits(struct libdecor_frame_private *frame_priv) +{ + if (frame_priv->state.content_limits.min_width > 0 && + frame_priv->state.content_limits.max_width > 0 && + frame_priv->state.content_limits.min_width > + frame_priv->state.content_limits.max_width) + return false; + + if (frame_priv->state.content_limits.min_height > 0 && + frame_priv->state.content_limits.max_height > 0 && + frame_priv->state.content_limits.min_height > + frame_priv->state.content_limits.max_height) + return false; + + return true; +} + +static void +libdecor_frame_apply_limits(struct libdecor_frame *frame, + enum libdecor_window_state window_state) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + if (!valid_limits(frame_priv)) { + libdecor_notify_plugin_error( + frame_priv->context, + LIBDECOR_ERROR_INVALID_FRAME_CONFIGURATION, + "minimum size (%i,%i) must be smaller than maximum size (%i,%i)", + frame_priv->state.content_limits.min_width, + frame_priv->state.content_limits.min_height, + frame_priv->state.content_limits.max_width, + frame_priv->state.content_limits.max_height); + } + + /* If the frame is configured as non-resizable before the first + * configure event is received, we have to manually set the min/max + * limits with the configured content size afterwards. */ + if (!libdecor_frame_has_capability(frame, LIBDECOR_ACTION_RESIZE)) { + frame_priv->state.content_limits.min_width = + frame_priv->content_width; + frame_priv->state.content_limits.max_width = + frame_priv->content_width; + + frame_priv->state.content_limits.min_height = + frame_priv->content_height; + frame_priv->state.content_limits.max_height = + frame_priv->content_height; + } + + if (frame_priv->state.content_limits.min_width > 0 && + frame_priv->state.content_limits.min_height > 0) { + struct libdecor_state state_min; + int win_min_width, win_min_height; + + state_min.content_width = frame_priv->state.content_limits.min_width; + state_min.content_height = frame_priv->state.content_limits.min_height; + state_min.window_state = window_state; + + frame_get_window_size_for(frame, &state_min, + &win_min_width, &win_min_height); + xdg_toplevel_set_min_size(frame_priv->xdg_toplevel, + win_min_width, win_min_height); + } else { + xdg_toplevel_set_min_size(frame_priv->xdg_toplevel, 0, 0); + } + + if (frame_priv->state.content_limits.max_width > 0 && + frame_priv->state.content_limits.max_height > 0) { + struct libdecor_state state_max; + int win_max_width, win_max_height; + + state_max.content_width = frame_priv->state.content_limits.max_width; + state_max.content_height = frame_priv->state.content_limits.max_height; + state_max.window_state = window_state; + + frame_get_window_size_for(frame, &state_max, + &win_max_width, &win_max_height); + xdg_toplevel_set_max_size(frame_priv->xdg_toplevel, + win_max_width, win_max_height); + } else { + xdg_toplevel_set_max_size(frame_priv->xdg_toplevel, 0, 0); + } +} + +static void +libdecor_frame_apply_state(struct libdecor_frame *frame, + struct libdecor_state *state) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->content_width = state->content_width; + frame_priv->content_height = state->content_height; + + /* do not set limits in non-floating states */ + if (state_is_floating(state->window_state)) { + libdecor_frame_apply_limits(frame, state->window_state); + } +} + +LIBDECOR_EXPORT void +libdecor_frame_toplevel_commit(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->iface->commit(frame, frame_priv->user_data); +} + +LIBDECOR_EXPORT void +libdecor_frame_commit(struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + struct libdecor *context = frame_priv->context; + struct libdecor_plugin *plugin = context->plugin; + + if (configuration && configuration->has_window_state) { + frame_priv->window_state = configuration->window_state; + state->window_state = configuration->window_state; + } else { + state->window_state = frame_priv->window_state; + } + + libdecor_frame_apply_state(frame, state); + + /* switch between decoration modes */ + if (frame_has_visible_client_side_decoration(frame)) { + plugin->priv->iface->frame_commit(plugin, frame, state, + configuration); + } else { + plugin->priv->iface->frame_free(plugin, frame); + + libdecor_frame_set_window_geometry(frame, 0, 0, + frame_priv->content_width, + frame_priv->content_height); + } + + if (configuration) { + xdg_surface_ack_configure(frame_priv->xdg_surface, + configuration->serial); + } +} + +static void +do_map(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + frame_priv->pending_map = false; + wl_surface_commit(frame_priv->wl_surface); +} + +LIBDECOR_EXPORT void +libdecor_frame_map(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + if (!frame_priv->xdg_surface) { + frame_priv->pending_map = true; + return; + } + + do_map(frame); +} + +LIBDECOR_EXPORT struct wl_surface * +libdecor_frame_get_wl_surface(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + return frame_priv->wl_surface; +} + +LIBDECOR_EXPORT struct xdg_surface * +libdecor_frame_get_xdg_surface(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + return frame_priv->xdg_surface; +} + +LIBDECOR_EXPORT struct xdg_toplevel * +libdecor_frame_get_xdg_toplevel(struct libdecor_frame *frame) +{ + return frame->priv->xdg_toplevel; +} + +LIBDECOR_EXPORT int +libdecor_frame_get_content_width(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + return frame_priv->content_width; +} + +LIBDECOR_EXPORT int +libdecor_frame_get_content_height(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + return frame_priv->content_height; +} + +LIBDECOR_EXPORT enum libdecor_window_state +libdecor_frame_get_window_state(struct libdecor_frame *frame) +{ + struct libdecor_frame_private *frame_priv = frame->priv; + + return frame_priv->window_state; +} + +LIBDECOR_EXPORT int +libdecor_plugin_init(struct libdecor_plugin *plugin, + struct libdecor *context, + struct libdecor_plugin_interface *iface) +{ + plugin->priv = zalloc(sizeof (struct libdecor_plugin_private)); + if (!plugin->priv) + return -1; + + plugin->priv->iface = iface; + + return 0; +} + +LIBDECOR_EXPORT void +libdecor_plugin_release(struct libdecor_plugin *plugin) +{ + free(plugin->priv); +} + +static void +xdg_wm_base_ping(void *user_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 = { + xdg_wm_base_ping, +}; + +static void +init_xdg_wm_base(struct libdecor *context, + uint32_t id, + uint32_t version) +{ + context->xdg_wm_base = wl_registry_bind(context->wl_registry, + id, + &xdg_wm_base_interface, + MIN(version,2)); + xdg_wm_base_add_listener(context->xdg_wm_base, + &xdg_wm_base_listener, + context); +} + +static void +registry_handle_global(void *user_data, + struct wl_registry *wl_registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + struct libdecor *context = user_data; + + if (!strcmp(interface, xdg_wm_base_interface.name)) { + init_xdg_wm_base(context, id, version); + } else if (!strcmp(interface, zxdg_decoration_manager_v1_interface.name)) { + context->decoration_manager = wl_registry_bind( + context->wl_registry, id, + &zxdg_decoration_manager_v1_interface, + MIN(version,2)); + } +} + +static void +registry_handle_global_remove(void *user_data, + struct wl_registry *wl_registry, + uint32_t name) +{ +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static bool +is_compositor_compatible(struct libdecor *context) +{ + if (!context->xdg_wm_base) + return false; + + return true; +} + +static void +notify_error(struct libdecor *context, + enum libdecor_error error, + const char *message) +{ + context->has_error = true; + context->iface->error(context, error, message); + context->plugin->priv->iface->destroy(context->plugin); +} + +static void +finish_init(struct libdecor *context) +{ + struct libdecor_frame *frame; + + wl_list_for_each(frame, &context->frames, link) + init_shell_surface(frame); +} + +static void +init_wl_display_callback(void *user_data, + struct wl_callback *callback, + uint32_t time) +{ + struct libdecor *context = user_data; + + context->init_done = true; + + wl_callback_destroy(callback); + context->init_callback = NULL; + + if (!is_compositor_compatible(context)) { + notify_error(context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + "Compositor is missing required interfaces"); + } + + if (context->plugin_ready) { + finish_init(context); + } +} + +static const struct wl_callback_listener init_wl_display_callback_listener = { + init_wl_display_callback +}; + +struct plugin_loader { + struct wl_list link; + void *lib; + const struct libdecor_plugin_description *description; + int priority; + char *name; +}; + +static int +calculate_priority(const struct libdecor_plugin_description *plugin_description) +{ + const char *current_desktop; + int i; + + if (!plugin_description->priorities) + return -1; + + current_desktop = getenv("XDG_CURRENT_DESKTOP"); + + i = 0; + while (true) { + struct libdecor_plugin_priority priority = + plugin_description->priorities[i]; + + i++; + + if (priority.desktop) { + char *tokens; + char *saveptr; + char *token; + + if (!current_desktop) + continue; + + tokens = strdup(current_desktop); + token = strtok_r(tokens, ":", &saveptr); + while (token) { + if (strcmp(priority.desktop, token) == 0) { + free(tokens); + return priority.priority; + } + token = strtok_r(NULL, ":", &saveptr); + } + free(tokens); + } else { + return priority.priority; + } + } + + return -1; +} + +static struct plugin_loader * +load_plugin_loader(struct libdecor *context, + const char *path, + const char *name) +{ + char *ext; + char *filename; + void *lib; + const struct libdecor_plugin_description *plugin_description; + int priority; + struct plugin_loader *plugin_loader; + + ext = strrchr(name, '.'); + if (ext == NULL || strcmp(ext, ".so") != 0) + return NULL; + + if (asprintf(&filename, "%s/%s", path, name) == -1) + return NULL; + + lib = dlopen(filename, RTLD_NOW | RTLD_LAZY); + free(filename); + if (!lib) { + fprintf(stderr, "Failed to load plugin: '%s'\n", dlerror()); + return NULL; + } + + plugin_description = dlsym(lib, "libdecor_plugin_description"); + if (!plugin_description) { + fprintf(stderr, + "Failed to load plugin '%s': no plugin description symbol\n", + name); + dlclose(lib); + return NULL; + } + + if (plugin_description->api_version != LIBDECOR_PLUGIN_API_VERSION) { + fprintf(stderr, + "Plugin '%s' found, but it's incompatible " + "(expected API version %d, but got %d)\n", + name, + LIBDECOR_PLUGIN_API_VERSION, + plugin_description->api_version); + dlclose(lib); + return NULL; + } + + if (!(plugin_description->capabilities & LIBDECOR_PLUGIN_CAPABILITY_BASE)) { + dlclose(lib); + return NULL; + } + + priority = calculate_priority(plugin_description); + if (priority == -1) { + fprintf(stderr, + "Plugin '%s' found, but has an invalid description\n", + name); + dlclose(lib); + return NULL; + } + + plugin_loader = zalloc(sizeof *plugin_loader); + plugin_loader->description = plugin_description; + plugin_loader->lib = lib; + plugin_loader->priority = priority; + plugin_loader->name = strdup(name); + + return plugin_loader; +} + +static bool +plugin_loader_higher_priority(struct plugin_loader *plugin_loader, + struct plugin_loader *best_plugin_loader) +{ + return plugin_loader->priority > best_plugin_loader->priority; +} + +static int +init_plugins(struct libdecor *context) +{ + const char *plugin_dir_env; + char *all_plugin_dirs; + char *plugin_dir; + char *saveptr; + DIR *dir; + struct wl_list plugin_loaders; + struct plugin_loader *plugin_loader, *tmp; + struct plugin_loader *best_plugin_loader; + struct libdecor_plugin *plugin; + + plugin_dir_env = getenv("LIBDECOR_PLUGIN_DIR"); + if (!plugin_dir_env) { + plugin_dir_env = LIBDECOR_PLUGIN_DIR; + } + all_plugin_dirs = strdup(plugin_dir_env); + + wl_list_init(&plugin_loaders); + plugin_dir = strtok_r(all_plugin_dirs, ":", &saveptr); + while (plugin_dir) { + dir = opendir(plugin_dir); + if (!dir) { + fprintf(stderr, "Couldn't open plugin directory: %s\n", + strerror(errno)); + continue; + } + + while (true) { + struct dirent *de; + + de = readdir(dir); + if (!de) + break; + + plugin_loader = load_plugin_loader(context, plugin_dir, de->d_name); + if (!plugin_loader) + continue; + + wl_list_insert(plugin_loaders.prev, &plugin_loader->link); + } + + closedir(dir); + + plugin_dir = strtok_r(NULL, ":", &saveptr); + } + free(all_plugin_dirs); + +retry_next: + best_plugin_loader = NULL; + wl_list_for_each(plugin_loader, &plugin_loaders, link) { + if (!best_plugin_loader) { + best_plugin_loader = plugin_loader; + continue; + } + + if (plugin_loader_higher_priority(plugin_loader, + best_plugin_loader)) + best_plugin_loader = plugin_loader; + } + + if (!best_plugin_loader) + return -1; + + plugin_loader = best_plugin_loader; + plugin = plugin_loader->description->constructor(context); + if (!plugin) { + fprintf(stderr, + "Failed to load plugin '%s': failed to init\n", + plugin_loader->name); + dlclose(plugin_loader->lib); + wl_list_remove(&plugin_loader->link); + free(plugin_loader->name); + free(plugin_loader); + goto retry_next; + } + + context->plugin = plugin; + + wl_list_remove(&plugin_loader->link); + free(plugin_loader->name); + free(plugin_loader); + + wl_list_for_each_safe(plugin_loader, tmp, &plugin_loaders, link) { + dlclose(plugin_loader->lib); + free(plugin_loader->name); + free(plugin_loader); + } + + return 0; +} + +LIBDECOR_EXPORT int +libdecor_get_fd(struct libdecor *context) +{ + struct libdecor_plugin *plugin = context->plugin; + + return plugin->priv->iface->get_fd(plugin); +} + +LIBDECOR_EXPORT int +libdecor_dispatch(struct libdecor *context, + int timeout) +{ + struct libdecor_plugin *plugin = context->plugin; + + return plugin->priv->iface->dispatch(plugin, timeout); +} + +LIBDECOR_EXPORT struct wl_display * +libdecor_get_wl_display(struct libdecor *context) +{ + return context->wl_display; +} + +LIBDECOR_EXPORT void +libdecor_notify_plugin_ready(struct libdecor *context) +{ + context->plugin_ready = true; + + if (context->init_done) + finish_init(context); +} + +LIBDECOR_EXPORT void +libdecor_notify_plugin_error(struct libdecor *context, + enum libdecor_error error, + const char *__restrict fmt, + ...) +{ + char *msg = NULL; + int nbytes = 0; + va_list argp; + + if (context->has_error) + return; + + va_start(argp, fmt); + nbytes = vasprintf(&msg, fmt, argp); + va_end(argp); + + if (nbytes>0) + notify_error(context, error, msg); + + if (msg) + free(msg); +} + +LIBDECOR_EXPORT void +libdecor_unref(struct libdecor *context) +{ + context->ref_count--; + if (context->ref_count == 0) { + if (context->plugin) + context->plugin->priv->iface->destroy(context->plugin); + if (context->init_callback) + wl_callback_destroy(context->init_callback); + wl_registry_destroy(context->wl_registry); + if (context->xdg_wm_base) + xdg_wm_base_destroy(context->xdg_wm_base); + if (context->decoration_manager) + zxdg_decoration_manager_v1_destroy( + context->decoration_manager); + free(context); + } +} + +LIBDECOR_EXPORT struct libdecor * +libdecor_new(struct wl_display *wl_display, + struct libdecor_interface *iface) +{ + struct libdecor *context; + + context = zalloc(sizeof *context); + + context->ref_count = 1; + context->iface = iface; + context->wl_display = wl_display; + context->wl_registry = wl_display_get_registry(wl_display); + wl_registry_add_listener(context->wl_registry, + ®istry_listener, + context); + context->init_callback = wl_display_sync(context->wl_display); + wl_callback_add_listener(context->init_callback, + &init_wl_display_callback_listener, + context); + + wl_list_init(&context->frames); + + if (init_plugins(context) != 0) { + fprintf(stderr, + "No plugins found, falling back on no decorations\n"); + context->plugin = libdecor_fallback_plugin_new(context); + } + + wl_display_flush(wl_display); + + return context; +} diff --git a/libdecor/src/libdecor.h b/libdecor/src/libdecor.h new file mode 100644 index 000000000..214a10c4e --- /dev/null +++ b/libdecor/src/libdecor.h @@ -0,0 +1,521 @@ +/* + * Copyright © 2017-2018 Red Hat Inc. + * Copyright © 2018 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef LIBDECOR_H +#define LIBDECOR_H + +#include <stdbool.h> +#include <wayland-client.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__GNUC__) && __GNUC__ >= 4 +#define LIBDECOR_EXPORT __attribute__ ((visibility("default"))) +#else +#define LIBDECOR_EXPORT +#endif + +struct xdg_toplevel; + +/** \class libdecor + * + * \brief A libdecor context instance. + */ +struct libdecor; + +/** \class libdecor_frame + * + * \brief A frame used for decorating a Wayland surface. + */ +struct libdecor_frame; + +/** \class libdecor_configuration + * + * \brief An object representing a toplevel window configuration. + */ +struct libdecor_configuration; + +/** \class libdecor_state + * + * \brief An object corresponding to a configured content state. + */ +struct libdecor_state; + +enum libdecor_error { + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + LIBDECOR_ERROR_INVALID_FRAME_CONFIGURATION, +}; + +enum libdecor_window_state { + LIBDECOR_WINDOW_STATE_NONE = 0, + LIBDECOR_WINDOW_STATE_ACTIVE = 1 << 0, + LIBDECOR_WINDOW_STATE_MAXIMIZED = 1 << 1, + LIBDECOR_WINDOW_STATE_FULLSCREEN = 1 << 2, + LIBDECOR_WINDOW_STATE_TILED_LEFT = 1 << 3, + LIBDECOR_WINDOW_STATE_TILED_RIGHT = 1 << 4, + LIBDECOR_WINDOW_STATE_TILED_TOP = 1 << 5, + LIBDECOR_WINDOW_STATE_TILED_BOTTOM = 1 << 6, +}; + +enum libdecor_resize_edge { + LIBDECOR_RESIZE_EDGE_NONE, + LIBDECOR_RESIZE_EDGE_TOP, + LIBDECOR_RESIZE_EDGE_BOTTOM, + LIBDECOR_RESIZE_EDGE_LEFT, + LIBDECOR_RESIZE_EDGE_TOP_LEFT, + LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT, + LIBDECOR_RESIZE_EDGE_RIGHT, + LIBDECOR_RESIZE_EDGE_TOP_RIGHT, + LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT, +}; + +enum libdecor_capabilities { + LIBDECOR_ACTION_MOVE = 1 << 0, + LIBDECOR_ACTION_RESIZE = 1 << 1, + LIBDECOR_ACTION_MINIMIZE = 1 << 2, + LIBDECOR_ACTION_FULLSCREEN = 1 << 3, + LIBDECOR_ACTION_CLOSE = 1 << 4, +}; + +struct libdecor_interface { + /** + * An error event + */ + void (* error)(struct libdecor *context, + enum libdecor_error error, + const char *message); + + /* Reserved */ + void (* reserved0)(void); + void (* reserved1)(void); + void (* reserved2)(void); + void (* reserved3)(void); + void (* reserved4)(void); + void (* reserved5)(void); + void (* reserved6)(void); + void (* reserved7)(void); + void (* reserved8)(void); + void (* reserved9)(void); +}; + +/** + * Interface for integrating a Wayland surface with libdecor. + */ +struct libdecor_frame_interface { + /** + * A new configuration was received. An application should respond to + * this by creating a suitable libdecor_state, and apply it using + * libdecor_frame_commit. + */ + void (* configure)(struct libdecor_frame *frame, + struct libdecor_configuration *configuration, + void *user_data); + + /** + * The window was requested to be closed by the compositor. + */ + void (* close)(struct libdecor_frame *frame, + void *user_data); + + /** + * The window decoration asked to have the main surface to be + * committed. This is required when the decoration is implemented using + * synchronous subsurfaces. + */ + void (* commit)(struct libdecor_frame *frame, + void *user_data); + + /** + * Any mapped popup that has a grab on the given seat should be + * dismissed. + */ + void (* dismiss_popup)(struct libdecor_frame *frame, + const char *seat_name, + void *user_data); + + /* Reserved */ + void (* reserved0)(void); + void (* reserved1)(void); + void (* reserved2)(void); + void (* reserved3)(void); + void (* reserved4)(void); + void (* reserved5)(void); + void (* reserved6)(void); + void (* reserved7)(void); + void (* reserved8)(void); + void (* reserved9)(void); +}; + +/** + * Remove a reference to the libdecor instance. When the reference count + * reaches zero, it is freed. + */ +void +libdecor_unref(struct libdecor *context); + +/** + * Create a new libdecor context for the given wl_display. + */ +struct libdecor * +libdecor_new(struct wl_display *display, + struct libdecor_interface *iface); + +/** + * Get the file descriptor used by libdecor. This is similar to + * wl_display_get_fd(), thus should be polled, and when data is available, + * libdecor_dispatch() should be called. + */ +int +libdecor_get_fd(struct libdecor *context); + +/** + * Dispatch events. This function should be called when data is available on + * the file descriptor returned by libdecor_get_fd(). If timeout is zero, this + * function will never block. + */ +int +libdecor_dispatch(struct libdecor *context, + int timeout); + +/** + * Decorate the given content wl_surface. + * + * This will create an xdg_surface and an xdg_toplevel, and integrate it + * properly with the windowing system, including creating appropriate + * decorations when needed, as well as handle windowing integration events such + * as resizing, moving, maximizing, etc. + * + * The passed wl_surface should only contain actual application content, + * without any window decoration. + */ +struct libdecor_frame * +libdecor_decorate(struct libdecor *context, + struct wl_surface *surface, + struct libdecor_frame_interface *iface, + void *user_data); + +/** + * Add a reference to the frame object. + */ +void +libdecor_frame_ref(struct libdecor_frame *frame); + +/** + * Remove a reference to the frame object. When the reference count reaches + * zero, the frame object is destroyed. + */ +void +libdecor_frame_unref(struct libdecor_frame *frame); + +/** + * Set the visibility of the frame. + * + * If an application wants to be borderless, it can set the frame visibility to + * false. + */ +void +libdecor_frame_set_visibility(struct libdecor_frame *frame, + bool visible); + +/** + * Get the visibility of the frame. + */ +bool +libdecor_frame_is_visible(struct libdecor_frame *frame); + + +/** + * Set the parent of the window. + * + * This can be used to stack multiple toplevel windows above or under each + * other. + */ +void +libdecor_frame_set_parent(struct libdecor_frame *frame, + struct libdecor_frame *parent); + +/** + * Set the title of the window. + */ +void +libdecor_frame_set_title(struct libdecor_frame *frame, + const char *title); + +/** + * Get the title of the window. + */ +const char * +libdecor_frame_get_title(struct libdecor_frame *frame); + +/** + * Set the application ID of the window. + */ +void +libdecor_frame_set_app_id(struct libdecor_frame *frame, + const char *app_id); + +/** + * Set new capabilities of the window. + * + * This determines whether e.g. a window decoration should show a maximize + * button, etc. + * + * Setting a capability does not implicitly unset any other. + */ +void +libdecor_frame_set_capabilities(struct libdecor_frame *frame, + enum libdecor_capabilities capabilities); + +/** + * Unset capabilities of the window. + * + * The opposite of libdecor_frame_set_capabilities. + */ +void +libdecor_frame_unset_capabilities(struct libdecor_frame *frame, + enum libdecor_capabilities capabilities); + +/** + * Check whether the window has any of the given capabilities. + */ +bool +libdecor_frame_has_capability(struct libdecor_frame *frame, + enum libdecor_capabilities capability); + +/** + * Show the window menu. + */ +void +libdecor_frame_show_window_menu(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial, + int x, + int y); + +/** + * Issue a popup grab on the window. Call this when a xdg_popup is mapped, so + * that it can be properly dismissed by the decorations. + */ +void +libdecor_frame_popup_grab(struct libdecor_frame *frame, + const char *seat_name); + +/** + * Release the popup grab. Call this when you unmap a popup. + */ +void +libdecor_frame_popup_ungrab(struct libdecor_frame *frame, + const char *seat_name); + +/** + * Translate content surface local coordinates to toplevel window local + * coordinates. + * + * This can be used to translate surface coordinates to coordinates useful for + * e.g. showing the window menu, or positioning a popup. + */ +void +libdecor_frame_translate_coordinate(struct libdecor_frame *frame, + int surface_x, + int surface_y, + int *frame_x, + int *frame_y); + +/** + * Set the max content size. + * + * This translates roughly to xdg_toplevel_set_max_size(). + */ +void +libdecor_frame_set_max_content_size(struct libdecor_frame *frame, + int content_width, + int content_height); + +/** + * Set the min content size. + * + * This translates roughly to xdg_toplevel_set_min_size(). + */ +void +libdecor_frame_set_min_content_size(struct libdecor_frame *frame, + int content_width, + int content_height); + +/** + * Initiate an interactive resize. + * + * This roughly translates to xdg_toplevel_resize(). + */ +void +libdecor_frame_resize(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial, + enum libdecor_resize_edge edge); + +/** + * Initiate an interactive move. + * + * This roughly translates to xdg_toplevel_move(). + */ +void +libdecor_frame_move(struct libdecor_frame *frame, + struct wl_seat *wl_seat, + uint32_t serial); + +/** + * Commit a new window state. This can be called on application driven resizes + * when the window is floating, or in response to received configurations, i.e. + * from e.g. interactive resizes or state changes. + */ +void +libdecor_frame_commit(struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration); + +/** + * Minimize the window. + * + * Roughly translates to xdg_toplevel_set_minimized(). + */ +void +libdecor_frame_set_minimized(struct libdecor_frame *frame); + +/** + * Maximize the window. + * + * Roughly translates to xdg_toplevel_set_maximized(). + */ +void +libdecor_frame_set_maximized(struct libdecor_frame *frame); + +/** + * Unmaximize the window. + * + * Roughly translates to xdg_toplevel_unset_maximized(). + */ +void +libdecor_frame_unset_maximized(struct libdecor_frame *frame); + +/** + * Fullscreen the window. + * + * Roughly translates to xdg_toplevel_set_fullscreen(). + */ +void +libdecor_frame_set_fullscreen(struct libdecor_frame *frame, + struct wl_output *output); + +/** + * Unfullscreen the window. + * + * Roughly translates to xdg_toplevel_unset_unfullscreen(). + */ +void +libdecor_frame_unset_fullscreen(struct libdecor_frame *frame); + +/** + * Return true if the window is floating. + * + * A window is floating when it's not maximized, tiled, fullscreen, or in any + * similar way with a fixed size and state. + * Note that this function uses the "applied" configuration. If this function + * is used in the 'configure' callback, the provided configuration has to be + * applied via 'libdecor_frame_commit' first, before it will reflect the current + * window state from the provided configuration. + */ +bool +libdecor_frame_is_floating(struct libdecor_frame *frame); + +/** + * Close the window. + * + * Roughly translates to xdg_toplevel_close(). + */ +void +libdecor_frame_close(struct libdecor_frame *frame); + +/** + * Map the window. + * + * This will eventually result in the initial configure event. + */ +void +libdecor_frame_map(struct libdecor_frame *frame); + +/** + * Get the associated xdg_surface for content wl_surface. + */ +struct xdg_surface * +libdecor_frame_get_xdg_surface(struct libdecor_frame *frame); + +/** + * Get the associated xdg_toplevel for the content wl_surface. + */ +struct xdg_toplevel * +libdecor_frame_get_xdg_toplevel(struct libdecor_frame *frame); + +/** + * Create a new content surface state. + */ +struct libdecor_state * +libdecor_state_new(int width, + int height); + +/** + * Free a content surface state. + */ +void +libdecor_state_free(struct libdecor_state *state); + +/** + * Get the expected size of the content for this configuration. + * + * If the configuration doesn't contain a size, false is returned. + */ +bool +libdecor_configuration_get_content_size(struct libdecor_configuration *configuration, + struct libdecor_frame *frame, + int *width, + int *height); + +/** + * Get the window state for this configuration. + * + * If the configuration doesn't contain any associated window state, false is + * returned, and the application should assume the window state remains + * unchanged. + */ +bool +libdecor_configuration_get_window_state(struct libdecor_configuration *configuration, + enum libdecor_window_state *window_state); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBDECOR_H */ diff --git a/libdecor/src/os-compatibility.c b/libdecor/src/os-compatibility.c new file mode 100644 index 000000000..50beb79f8 --- /dev/null +++ b/libdecor/src/os-compatibility.c @@ -0,0 +1,181 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include <sys/types.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#ifdef HAVE_MEMFD_CREATE +#include <sys/mman.h> +#endif + +#include "os-compatibility.h" + +#ifndef HAVE_MKOSTEMP +static int +set_cloexec_or_close(int fd) +{ + long flags; + + if (fd == -1) + return -1; + + flags = fcntl(fd, F_GETFD); + if (flags == -1) + goto err; + + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) + goto err; + + return fd; + +err: + close(fd); + return -1; +} +#endif + +static int +create_tmpfile_cloexec(char *tmpname) +{ + int fd; + +#ifdef HAVE_MKOSTEMP + fd = mkostemp(tmpname, O_CLOEXEC); + if (fd >= 0) + unlink(tmpname); +#else + fd = mkstemp(tmpname); + if (fd >= 0) { + fd = set_cloexec_or_close(fd); + unlink(tmpname); + } +#endif + + return fd; +} + +static int +os_resize_anonymous_file(int fd, off_t size) +{ +#ifdef HAVE_POSIX_FALLOCATE + /* + * Filesystems that do support fallocate will return EINVAL or + * EOPNOTSUPP. In this case we need to fall back to ftruncate + */ + errno = posix_fallocate(fd, 0, size); + if (errno == 0) + return 0; + else if (errno != EINVAL && errno != EOPNOTSUPP) + return -1; +#endif + if (ftruncate(fd, size) < 0) + return -1; + + return 0; +} + +/* + * Create a new, unique, anonymous file of the given size, and + * return the file descriptor for it. The file descriptor is set + * CLOEXEC. The file is immediately suitable for mmap()'ing + * the given size at offset zero. + * + * The file should not have a permanent backing store like a disk, + * but may have if XDG_RUNTIME_DIR is not properly implemented in OS. + * + * The file name is deleted from the file system. + * + * The file is suitable for buffer sharing between processes by + * transmitting the file descriptor over Unix sockets using the + * SCM_RIGHTS methods. + * + * If the C library implements posix_fallocate(), it is used to + * guarantee that disk space is available for the file at the + * given size. If disk space is insufficient, errno is set to ENOSPC. + * If posix_fallocate() is not supported, program may receive + * SIGBUS on accessing mmap()'ed file contents instead. + * + * If the C library implements memfd_create(), it is used to create the + * file purely in memory, without any backing file name on the file + * system, and then sealing off the possibility of shrinking it. This + * can then be checked before accessing mmap()'ed file contents, to + * make sure SIGBUS can't happen. It also avoids requiring + * XDG_RUNTIME_DIR. + */ +int +os_create_anonymous_file(off_t size) +{ + static const char template[] = "/libdecor-shared-XXXXXX"; + const char *path; + char *name; + int fd; + +#ifdef HAVE_MEMFD_CREATE + fd = memfd_create("libdecor", MFD_CLOEXEC | MFD_ALLOW_SEALING); + if (fd >= 0) { + /* We can add this seal before calling posix_fallocate(), as + * the file is currently zero-sized anyway. + * + * There is also no need to check for the return value, we + * couldn't do anything with it anyway. + */ + fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_SEAL); + } else +#endif + { + path = getenv("XDG_RUNTIME_DIR"); + if (!path) { + errno = ENOENT; + return -1; + } + + name = malloc(strlen(path) + sizeof(template)); + if (!name) + return -1; + + strcpy(name, path); + strcat(name, template); + + fd = create_tmpfile_cloexec(name); + + free(name); + + if (fd < 0) + return -1; + } + + if (os_resize_anonymous_file(fd, size) < 0) { + close(fd); + return -1; + } + + return fd; +} diff --git a/libdecor/src/os-compatibility.h b/libdecor/src/os-compatibility.h new file mode 100644 index 000000000..d0e69acd9 --- /dev/null +++ b/libdecor/src/os-compatibility.h @@ -0,0 +1,34 @@ +/* + * Copyright © 2012 Collabora, Ltd. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef OS_COMPATIBILITY_H +#define OS_COMPATIBILITY_H + +#include <sys/types.h> + +int +os_create_anonymous_file(off_t size); + +#endif /* OS_COMPATIBILITY_H */ diff --git a/libdecor/src/plugins/cairo/libdecor-cairo-blur.c b/libdecor/src/plugins/cairo/libdecor-cairo-blur.c new file mode 100644 index 000000000..2cddc7a23 --- /dev/null +++ b/libdecor/src/plugins/cairo/libdecor-cairo-blur.c @@ -0,0 +1,255 @@ +/* + * Copyright © 2008 Kristian Høgsberg + * Copyright © 2012 Intel Corporation + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * functions 'blur_surface' and 'render_shadow' from weston project: + * https://gitlab.freedesktop.org/wayland/weston/raw/master/shared/cairo-util.c + */ + +#include "libdecor-cairo-blur.h" +#include <stdint.h> +#include <stdlib.h> +#include <math.h> + +/** + * Compile-time computation of number of items in a hardcoded array. + * + * @param a the array being measured. + * @return the number of items hardcoded into the array. + */ +#ifndef ARRAY_LENGTH +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) +#endif + +int +blur_surface(cairo_surface_t *surface, int margin) +{ + int32_t width, height, stride, x, y, z, w; + uint8_t *src, *dst; + uint32_t *s, *d, a, p; + int i, j, k, size, half; + uint32_t kernel[71]; + double f; + + size = ARRAY_LENGTH(kernel); + width = cairo_image_surface_get_width(surface); + height = cairo_image_surface_get_height(surface); + stride = cairo_image_surface_get_stride(surface); + src = cairo_image_surface_get_data(surface); + + dst = malloc(height * stride); + if (dst == NULL) + return -1; + + half = size / 2; + a = 0; + for (i = 0; i < size; i++) { + f = (i - half); + kernel[i] = exp(- f * f / ARRAY_LENGTH(kernel)) * 10000; + a += kernel[i]; + } + + for (i = 0; i < height; i++) { + s = (uint32_t *) (src + i * stride); + d = (uint32_t *) (dst + i * stride); + for (j = 0; j < width; j++) { + if (margin < j && j < width - margin) { + d[j] = s[j]; + continue; + } + + x = 0; + y = 0; + z = 0; + w = 0; + for (k = 0; k < size; k++) { + if (j - half + k < 0 || j - half + k >= width) + continue; + p = s[j - half + k]; + + x += (p >> 24) * kernel[k]; + y += ((p >> 16) & 0xff) * kernel[k]; + z += ((p >> 8) & 0xff) * kernel[k]; + w += (p & 0xff) * kernel[k]; + } + d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; + } + } + + for (i = 0; i < height; i++) { + s = (uint32_t *) (dst + i * stride); + d = (uint32_t *) (src + i * stride); + for (j = 0; j < width; j++) { + if (margin <= i && i < height - margin) { + d[j] = s[j]; + continue; + } + + x = 0; + y = 0; + z = 0; + w = 0; + for (k = 0; k < size; k++) { + if (i - half + k < 0 || i - half + k >= height) + continue; + s = (uint32_t *) (dst + (i - half + k) * stride); + p = s[j]; + + x += (p >> 24) * kernel[k]; + y += ((p >> 16) & 0xff) * kernel[k]; + z += ((p >> 8) & 0xff) * kernel[k]; + w += (p & 0xff) * kernel[k]; + } + d[j] = (x / a << 24) | (y / a << 16) | (z / a << 8) | w / a; + } + } + + free(dst); + cairo_surface_mark_dirty(surface); + + return 0; +} + +void +render_shadow(cairo_t *cr, cairo_surface_t *surface, + int x, int y, int width, int height, int margin, int top_margin) +{ + cairo_pattern_t *pattern; + cairo_matrix_t matrix; + int i, fx, fy, shadow_height, shadow_width; + + cairo_set_source_rgba(cr, 0, 0, 0, 0.45); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + pattern = cairo_pattern_create_for_surface (surface); + cairo_pattern_set_filter(pattern, CAIRO_FILTER_NEAREST); + + for (i = 0; i < 4; i++) { + /* when fy is set, then we are working with lower corners, + * when fx is set, then we are working with right corners + * + * 00 ------- 01 + * | | + * | | + * 10 ------- 11 + */ + fx = i & 1; + fy = i >> 1; + + cairo_matrix_init_translate(&matrix, + -x + fx * (128 - width), + -y + fy * (128 - height)); + cairo_pattern_set_matrix(pattern, &matrix); + + shadow_width = margin; + shadow_height = fy ? margin : top_margin; + + /* if the shadows together are greater than the surface, we need + * to fix it - set the shadow size to the half of + * the size of surface. Also handle the case when the size is + * not divisible by 2. In that case we need one part of the + * shadow to be one pixel greater. !fy or !fx, respectively, + * will do the work. + */ + if (height < 2 * shadow_height) + shadow_height = (height + !fy) / 2; + + if (width < 2 * shadow_width) + shadow_width = (width + !fx) / 2; + + cairo_reset_clip(cr); + cairo_rectangle(cr, + x + fx * (width - shadow_width), + y + fy * (height - shadow_height), + shadow_width, shadow_height); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + + shadow_width = width - 2 * margin; + shadow_height = top_margin; + if (height < 2 * shadow_height) + shadow_height = height / 2; + + if (shadow_width > 0 && shadow_height) { + /* Top stretch */ + cairo_matrix_init_translate(&matrix, 60, 0); + cairo_matrix_scale(&matrix, 8.0 / width, 1); + cairo_matrix_translate(&matrix, -x - width / 2, -y); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x + margin, y, shadow_width, shadow_height); + + cairo_reset_clip(cr); + cairo_rectangle(cr, + x + margin, y, + shadow_width, shadow_height); + cairo_clip (cr); + cairo_mask(cr, pattern); + + /* Bottom stretch */ + cairo_matrix_translate(&matrix, 0, -height + 128); + cairo_pattern_set_matrix(pattern, &matrix); + + cairo_reset_clip(cr); + cairo_rectangle(cr, x + margin, y + height - margin, + shadow_width, margin); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + shadow_width = margin; + if (width < 2 * shadow_width) + shadow_width = width / 2; + + shadow_height = height - margin - top_margin; + + /* if height is smaller than sum of margins, + * then the shadow is already done by the corners */ + if (shadow_height > 0 && shadow_width) { + /* Left stretch */ + cairo_matrix_init_translate(&matrix, 0, 60); + cairo_matrix_scale(&matrix, 1, 8.0 / height); + cairo_matrix_translate(&matrix, -x, -y - height / 2); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_reset_clip(cr); + cairo_rectangle(cr, x, y + top_margin, + shadow_width, shadow_height); + cairo_clip (cr); + cairo_mask(cr, pattern); + + /* Right stretch */ + cairo_matrix_translate(&matrix, -width + 128, 0); + cairo_pattern_set_matrix(pattern, &matrix); + cairo_rectangle(cr, x + width - shadow_width, y + top_margin, + shadow_width, shadow_height); + cairo_reset_clip(cr); + cairo_clip (cr); + cairo_mask(cr, pattern); + } + + cairo_pattern_destroy(pattern); + cairo_reset_clip(cr); +} diff --git a/libdecor/src/plugins/cairo/libdecor-cairo-blur.h b/libdecor/src/plugins/cairo/libdecor-cairo-blur.h new file mode 100644 index 000000000..d48e9dd3d --- /dev/null +++ b/libdecor/src/plugins/cairo/libdecor-cairo-blur.h @@ -0,0 +1,10 @@ +#pragma once + +#include <cairo/cairo.h> + +int +blur_surface(cairo_surface_t *surface, int margin); + +void +render_shadow(cairo_t *cr, cairo_surface_t *surface, + int x, int y, int width, int height, int margin, int top_margin); diff --git a/libdecor/src/plugins/cairo/libdecor-cairo.c b/libdecor/src/plugins/cairo/libdecor-cairo.c new file mode 100644 index 000000000..6e4222863 --- /dev/null +++ b/libdecor/src/plugins/cairo/libdecor-cairo.c @@ -0,0 +1,2824 @@ +/* + * Copyright © 2018 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include <linux/input.h> +#include <fcntl.h> +#include <poll.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <limits.h> +#include <wayland-cursor.h> + +#include "libdecor-plugin.h" +#include "utils.h" +#include "cursor-settings.h" +#include "os-compatibility.h" + +#include <cairo/cairo.h> +#include <pango/pangocairo.h> + +#ifndef APPLY_FLTK_CHANGES +#define APPLY_FLTK_CHANGES 1 +#endif + +/* Note for FLTK: This header file changes location, while its content stays unchanged, + between the master and gtk_cairo_single branches */ +#include "libdecor-cairo-blur.h" + +static const size_t SHADOW_MARGIN = 24; /* graspable part of the border */ +static const size_t TITLE_HEIGHT = 24; +static const size_t BUTTON_WIDTH = 32; +static const size_t SYM_DIM = 14; + +static const uint32_t COL_TITLE = 0xFF080706; +static const uint32_t COL_TITLE_INACT = 0xFF303030; +static const uint32_t COL_BUTTON_MIN = 0xFFFFBB00; +static const uint32_t COL_BUTTON_MAX = 0xFF238823; +static const uint32_t COL_BUTTON_CLOSE = 0xFFFB6542; +static const uint32_t COL_BUTTON_INACT = 0xFF404040; +static const uint32_t COL_SYM = 0xFFF4F4EF; +static const uint32_t COL_SYM_ACT = 0xFF20322A; +static const uint32_t COL_SYM_INACT = 0xFF909090; + +static const uint32_t DOUBLE_CLICK_TIME_MS = 400; + +static const char *cursor_names[] = { + "top_side", + "bottom_side", + "left_side", + "top_left_corner", + "bottom_left_corner", + "right_side", + "top_right_corner", + "bottom_right_corner" +}; + + +/* color conversion function from 32bit integer to double components */ + +double +red(const uint32_t *const col) { + return ((const uint8_t*)(col))[2] / (double)(255); +} + +double +green(const uint32_t *const col) { + return ((const uint8_t*)(col))[1] / (double)(255); +} + +double +blue(const uint32_t *const col) { + return ((const uint8_t*)(col))[0] / (double)(255); +} + +double +alpha(const uint32_t *const col) { + return ((const uint8_t*)(col))[3] / (double)(255); +} + +void +cairo_set_rgba32(cairo_t *cr, const uint32_t *const c) { + cairo_set_source_rgba(cr, red(c), green(c), blue(c), alpha(c)); +} + +static bool +streql(const char *str1, const char *str2) +{ + return (str1 && str2) && (strcmp(str1, str2) == 0); +} + +enum decoration_type { + DECORATION_TYPE_NONE, + DECORATION_TYPE_ALL, + DECORATION_TYPE_MAXIMIZED, + DECORATION_TYPE_TILED +}; + +enum component { + NONE = 0, + SHADOW, + TITLE, + BUTTON_MIN, + BUTTON_MAX, + BUTTON_CLOSE, +}; + +enum composite_mode { + COMPOSITE_SERVER, + COMPOSITE_CLIENT, +}; + +struct seat { + struct libdecor_plugin_cairo *plugin_cairo; + + char *name; + + struct wl_seat *wl_seat; + struct wl_pointer *wl_pointer; + + struct wl_surface *cursor_surface; + struct wl_cursor *current_cursor; + int cursor_scale; + struct wl_list cursor_outputs; + + struct wl_cursor_theme *cursor_theme; + /* cursors for resize edges and corners */ + struct wl_cursor *cursors[ARRAY_LENGTH(cursor_names)]; + struct wl_cursor *cursor_left_ptr; + + struct wl_surface *pointer_focus; + + int pointer_x, pointer_y; + + uint32_t pointer_button_time_stamp; + + uint32_t serial; + + bool grabbed; + + struct wl_list link; +}; + +struct output { + struct libdecor_plugin_cairo *plugin_cairo; + + struct wl_output *wl_output; + uint32_t id; + int scale; + + struct wl_list link; +}; + +struct buffer { + struct wl_buffer *wl_buffer; + bool in_use; + bool is_detached; + + void *data; + size_t data_size; + int width; + int height; + int scale; + int buffer_width; + int buffer_height; +}; + +struct border_component { + enum component type; + + bool is_hidden; + bool opaque; + + enum composite_mode composite_mode; + struct { + struct wl_surface *wl_surface; + struct wl_subsurface *wl_subsurface; + struct buffer *buffer; + struct wl_list output_list; + int scale; + } server; + struct { + cairo_surface_t *image; + struct border_component *parent_component; + } client; + + struct wl_list child_components; /* border_component::link */ + struct wl_list link; /* border_component::child_components */ +}; + +struct surface_output { + struct output *output; + struct wl_list link; +}; + +struct cursor_output { + struct output *output; + struct wl_list link; +}; + +struct libdecor_frame_cairo { + struct libdecor_frame frame; + + struct libdecor_plugin_cairo *plugin_cairo; + + int content_width; + int content_height; + + enum decoration_type decoration_type; + + enum libdecor_window_state window_state; + + char *title; + + enum libdecor_capabilities capabilities; + + struct border_component *focus; + struct border_component *active; + struct border_component *grab; + + bool shadow_showing; + struct border_component shadow; + + struct { + bool is_showing; + struct border_component title; + struct border_component min; + struct border_component max; + struct border_component close; + } title_bar; + + /* store pre-processed shadow tile */ + cairo_surface_t *shadow_blur; + + struct wl_list link; +}; + +struct libdecor_plugin_cairo { + struct libdecor_plugin plugin; + + struct wl_callback *globals_callback; + struct wl_callback *globals_callback_shm; + + struct libdecor *context; + + struct wl_registry *wl_registry; + struct wl_subcompositor *wl_subcompositor; + struct wl_compositor *wl_compositor; + + struct wl_shm *wl_shm; + struct wl_callback *shm_callback; + bool has_argb; + + struct wl_list visible_frame_list; + struct wl_list seat_list; + struct wl_list output_list; + + char *cursor_theme_name; + int cursor_size; + + PangoFontDescription *font; +}; + +static const char *libdecor_cairo_proxy_tag = "libdecor-cairo"; + +static void +sync_active_component(struct libdecor_frame_cairo *frame_cairo, + struct seat *seat); + +static void +synthesize_pointer_enter(struct seat *seat); + +static void +synthesize_pointer_leave(struct seat *seat); + +static bool +own_proxy(struct wl_proxy *proxy) +{ + return (wl_proxy_get_tag(proxy) == &libdecor_cairo_proxy_tag); +} + +static bool +own_surface(struct wl_surface *surface) +{ + return own_proxy((struct wl_proxy *) surface); +} + +static bool +own_output(struct wl_output *output) +{ + return own_proxy((struct wl_proxy *) output); +} + +static bool +moveable(struct libdecor_frame_cairo *frame_cairo) { + return libdecor_frame_has_capability(&frame_cairo->frame, + LIBDECOR_ACTION_MOVE); +} + +static bool +resizable(struct libdecor_frame_cairo *frame_cairo) { + return libdecor_frame_has_capability(&frame_cairo->frame, + LIBDECOR_ACTION_RESIZE); +} + +static bool +minimizable(struct libdecor_frame_cairo *frame_cairo) { + return libdecor_frame_has_capability(&frame_cairo->frame, + LIBDECOR_ACTION_MINIMIZE); +} + +static bool +closeable(struct libdecor_frame_cairo *frame_cairo) { + return libdecor_frame_has_capability(&frame_cairo->frame, + LIBDECOR_ACTION_CLOSE); +} + +static void +buffer_free(struct buffer *buffer); + +static void +draw_border_component(struct libdecor_frame_cairo *frame_cairo, + struct border_component *border_component); + +static void +send_cursor(struct seat *seat); + +static bool +update_local_cursor(struct seat *seat); + +static void +libdecor_plugin_cairo_destroy(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_cairo *plugin_cairo = + (struct libdecor_plugin_cairo *) plugin; + struct seat *seat, *seat_tmp; + struct output *output, *output_tmp; + struct libdecor_frame_cairo *frame, *frame_tmp; + + if (plugin_cairo->globals_callback) + wl_callback_destroy(plugin_cairo->globals_callback); + if (plugin_cairo->globals_callback_shm) + wl_callback_destroy(plugin_cairo->globals_callback_shm); + if (plugin_cairo->shm_callback) + wl_callback_destroy(plugin_cairo->shm_callback); + wl_registry_destroy(plugin_cairo->wl_registry); + + wl_list_for_each_safe(seat, seat_tmp, &plugin_cairo->seat_list, link) { + struct cursor_output *cursor_output, *tmp; + + if (seat->wl_pointer) + wl_pointer_destroy(seat->wl_pointer); + if (seat->cursor_surface) + wl_surface_destroy(seat->cursor_surface); + wl_seat_destroy(seat->wl_seat); + if (seat->cursor_theme) + wl_cursor_theme_destroy(seat->cursor_theme); + + wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) { + wl_list_remove(&cursor_output->link); + free(cursor_output); + } + free(seat->name); + + free(seat); + } + + wl_list_for_each_safe(output, output_tmp, + &plugin_cairo->output_list, link) { + wl_output_destroy(output->wl_output); + free(output); + } + + wl_list_for_each_safe(frame, frame_tmp, + &plugin_cairo->visible_frame_list, link) { + wl_list_remove(&frame->link); + } + + free(plugin_cairo->cursor_theme_name); + + wl_shm_destroy(plugin_cairo->wl_shm); + + pango_font_description_free(plugin_cairo->font); + + wl_compositor_destroy(plugin_cairo->wl_compositor); + wl_subcompositor_destroy(plugin_cairo->wl_subcompositor); + + libdecor_plugin_release(&plugin_cairo->plugin); + free(plugin_cairo); +} + +static void +init_server_component(struct border_component *border_component, + enum component type) +{ + border_component->composite_mode = COMPOSITE_SERVER; + wl_list_init(&border_component->child_components); + border_component->type = type; +} + +static void +init_client_component(struct border_component *border_component, + struct border_component *parent, + enum component type) +{ + border_component->composite_mode = COMPOSITE_CLIENT; + wl_list_init(&border_component->child_components); + wl_list_insert(parent->child_components.prev, &border_component->link); + border_component->client.parent_component = parent; + border_component->type = type; +} + +static void +init_components(struct libdecor_frame_cairo *frame_cairo) +{ + init_server_component(&frame_cairo->title_bar.title, + TITLE); + init_client_component(&frame_cairo->title_bar.min, + &frame_cairo->title_bar.title, + BUTTON_MIN); + init_client_component(&frame_cairo->title_bar.max, + &frame_cairo->title_bar.title, + BUTTON_MAX); + init_client_component(&frame_cairo->title_bar.close, + &frame_cairo->title_bar.title, + BUTTON_CLOSE); + init_server_component(&frame_cairo->shadow, + SHADOW); +} + +static struct libdecor_frame_cairo * +libdecor_frame_cairo_new(struct libdecor_plugin_cairo *plugin_cairo) +{ + struct libdecor_frame_cairo *frame_cairo = zalloc(sizeof *frame_cairo); + cairo_t *cr; + + static const int size = 128; + static const int boundary = 32; + + frame_cairo->plugin_cairo = plugin_cairo; + frame_cairo->shadow_blur = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, size, size); + wl_list_insert(&plugin_cairo->visible_frame_list, &frame_cairo->link); + + init_components(frame_cairo); + + cr = cairo_create(frame_cairo->shadow_blur); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_rectangle(cr, boundary, boundary, size-2*boundary, size-2*boundary); + cairo_fill(cr); + cairo_destroy(cr); + blur_surface(frame_cairo->shadow_blur, 64); + + return frame_cairo; +} + +static int +libdecor_plugin_cairo_get_fd(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_cairo *plugin_cairo = + (struct libdecor_plugin_cairo *) plugin; + struct wl_display *wl_display = + libdecor_get_wl_display(plugin_cairo->context); + + return wl_display_get_fd(wl_display); +} + +static int +libdecor_plugin_cairo_dispatch(struct libdecor_plugin *plugin, + int timeout) +{ + struct libdecor_plugin_cairo *plugin_cairo = + (struct libdecor_plugin_cairo *) plugin; + struct wl_display *wl_display = + libdecor_get_wl_display(plugin_cairo->context); + struct pollfd fds[1]; + int ret; + int dispatch_count = 0; + + while (wl_display_prepare_read(wl_display) != 0) + dispatch_count += wl_display_dispatch_pending(wl_display); + + if (wl_display_flush(wl_display) < 0 && + errno != EAGAIN) { + wl_display_cancel_read(wl_display); + return -errno; + } + + fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN }; + + ret = poll(fds, ARRAY_SIZE (fds), timeout); + if (ret > 0) { + if (fds[0].revents & POLLIN) { + wl_display_read_events(wl_display); + dispatch_count += wl_display_dispatch_pending(wl_display); + return dispatch_count; + } else { + wl_display_cancel_read(wl_display); + return dispatch_count; + } + } else if (ret == 0) { + wl_display_cancel_read(wl_display); + return dispatch_count; + } else { + wl_display_cancel_read(wl_display); + return -errno; + } +} + +static struct libdecor_frame * +libdecor_plugin_cairo_frame_new(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_cairo *plugin_cairo = + (struct libdecor_plugin_cairo *) plugin; + struct libdecor_frame_cairo *frame_cairo; + + frame_cairo = libdecor_frame_cairo_new(plugin_cairo); + + return &frame_cairo->frame; +} + +static void +toggle_maximized(struct libdecor_frame *const frame) +{ + if (!resizable((struct libdecor_frame_cairo *)frame)) + return; + + if (!(libdecor_frame_get_window_state(frame) & + LIBDECOR_WINDOW_STATE_MAXIMIZED)) + libdecor_frame_set_maximized(frame); + else + libdecor_frame_unset_maximized(frame); +} + +static void +buffer_release(void *user_data, + struct wl_buffer *wl_buffer) +{ + struct buffer *buffer = user_data; + + if (buffer->is_detached) + buffer_free(buffer); + else + buffer->in_use = false; +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static struct buffer * +create_shm_buffer(struct libdecor_plugin_cairo *plugin_cairo, + int width, + int height, + bool opaque, + int scale) +{ + struct wl_shm_pool *pool; + int fd, size, buffer_width, buffer_height, stride; + void *data; + struct buffer *buffer; + enum wl_shm_format buf_fmt; + + buffer_width = width * scale; + buffer_height = height * scale; + stride = buffer_width * 4; + size = stride * buffer_height; + + fd = os_create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); + return NULL; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + return NULL; + } + + buf_fmt = opaque ? WL_SHM_FORMAT_XRGB8888 : WL_SHM_FORMAT_ARGB8888; + + pool = wl_shm_create_pool(plugin_cairo->wl_shm, fd, size); + buffer = zalloc(sizeof *buffer); + buffer->wl_buffer = wl_shm_pool_create_buffer(pool, 0, + buffer_width, buffer_height, + stride, + buf_fmt); + wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer); + wl_shm_pool_destroy(pool); + close(fd); + + buffer->data = data; + buffer->data_size = size; + buffer->width = width; + buffer->height = height; + buffer->scale = scale; + buffer->buffer_width = buffer_width; + buffer->buffer_height = buffer_height; + + return buffer; +} + +static void +buffer_free(struct buffer *buffer) +{ + if (buffer->wl_buffer) { + wl_buffer_destroy(buffer->wl_buffer); + munmap(buffer->data, buffer->data_size); + buffer->wl_buffer = NULL; + buffer->in_use = false; + } + free(buffer); +} + +static void +free_border_component(struct border_component *border_component) +{ + struct surface_output *surface_output, *surface_output_tmp; + + if (border_component->server.wl_surface) { + wl_subsurface_destroy(border_component->server.wl_subsurface); + border_component->server.wl_subsurface = NULL; + wl_surface_destroy(border_component->server.wl_surface); + border_component->server.wl_surface = NULL; + } + if (border_component->server.buffer) { + buffer_free(border_component->server.buffer); + border_component->server.buffer = NULL; + } + if (border_component->client.image) { + cairo_surface_destroy(border_component->client.image); + border_component->client.image = NULL; + } + if (border_component->server.output_list.next != NULL) { + wl_list_for_each_safe(surface_output, surface_output_tmp, + &border_component->server.output_list, link) { + wl_list_remove(&surface_output->link); + free(surface_output); + } + } +} + +static void +libdecor_plugin_cairo_frame_free(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ + struct libdecor_plugin_cairo *plugin_cairo = + (struct libdecor_plugin_cairo *) plugin; + struct libdecor_frame_cairo *frame_cairo = + (struct libdecor_frame_cairo *) frame; + struct seat *seat; + + wl_list_for_each(seat, &plugin_cairo->seat_list, link) { + if (seat->pointer_focus != NULL && + wl_surface_get_user_data(seat->pointer_focus) == frame_cairo) + seat->pointer_focus = NULL; + } + + free_border_component(&frame_cairo->title_bar.title); + free_border_component(&frame_cairo->title_bar.min); + free_border_component(&frame_cairo->title_bar.max); + free_border_component(&frame_cairo->title_bar.close); + frame_cairo->title_bar.is_showing = false; + free_border_component(&frame_cairo->shadow); + frame_cairo->shadow_showing = false; + if (frame_cairo->shadow_blur != NULL) { + cairo_surface_destroy(frame_cairo->shadow_blur); + frame_cairo->shadow_blur = NULL; + } + + free(frame_cairo->title); + frame_cairo->title = NULL; + + frame_cairo->decoration_type = DECORATION_TYPE_NONE; + + if (frame_cairo->link.next != NULL) + wl_list_remove(&frame_cairo->link); +} + +static bool +is_border_surfaces_showing(struct libdecor_frame_cairo *frame_cairo) +{ + return frame_cairo->shadow_showing; +} + +static bool +is_title_bar_surfaces_showing(struct libdecor_frame_cairo *frame_cairo) +{ + return frame_cairo->title_bar.is_showing; +} + +static struct border_component * +get_server_component(struct border_component *border_component) +{ + switch (border_component->composite_mode) { + case COMPOSITE_SERVER: + return border_component; + case COMPOSITE_CLIENT: + return get_server_component(border_component->client.parent_component); + } + return NULL; +} + +static void +redraw_border_component(struct libdecor_frame_cairo *frame_cairo, + struct border_component *border_component) +{ + struct border_component *server_component; + + server_component = get_server_component(border_component); + draw_border_component(frame_cairo, server_component); +} + +static void +hide_border_component(struct libdecor_frame_cairo *frame_cairo, + struct border_component *border_component) +{ + border_component->is_hidden = true; + + switch (border_component->composite_mode) { + case COMPOSITE_SERVER: + if (!border_component->server.wl_surface) + return; + + wl_surface_attach(border_component->server.wl_surface, + NULL, 0, 0); + wl_surface_commit(border_component->server.wl_surface); + break; + case COMPOSITE_CLIENT: + redraw_border_component(frame_cairo, border_component); + break; + } +} + +static void +hide_border_surfaces(struct libdecor_frame_cairo *frame_cairo) +{ + hide_border_component(frame_cairo, &frame_cairo->shadow); + frame_cairo->shadow_showing = false; +} + +static void +hide_title_bar_surfaces(struct libdecor_frame_cairo *frame_cairo) +{ + hide_border_component(frame_cairo, &frame_cairo->title_bar.title); + hide_border_component(frame_cairo, &frame_cairo->title_bar.min); + hide_border_component(frame_cairo, &frame_cairo->title_bar.max); + hide_border_component(frame_cairo, &frame_cairo->title_bar.close); + frame_cairo->title_bar.is_showing = false; +} + +static struct border_component * +get_component_for_surface(struct libdecor_frame_cairo *frame_cairo, + struct wl_surface *surface) +{ + if (frame_cairo->shadow.server.wl_surface == surface) + return &frame_cairo->shadow; + if (frame_cairo->title_bar.title.server.wl_surface == surface) + return &frame_cairo->title_bar.title; + return NULL; +} + +static void +calculate_component_size(struct libdecor_frame_cairo *frame_cairo, + enum component component, + int *component_x, + int *component_y, + int *component_width, + int *component_height); + +static void +update_component_focus(struct libdecor_frame_cairo *frame_cairo, + struct wl_surface *surface, + struct seat *seat) +{ + static struct border_component *border_component; + static struct border_component *child_component; + static struct border_component *focus_component; + + border_component = get_component_for_surface(frame_cairo, surface); + + focus_component = border_component; + wl_list_for_each(child_component, &border_component->child_components, link) { + int component_x = 0, component_y = 0; + int component_width = 0, component_height = 0; + + calculate_component_size(frame_cairo, child_component->type, + &component_x, &component_y, + &component_width, &component_height); + if (seat->pointer_x >= component_x && + seat->pointer_x < component_x + component_width && + seat->pointer_y >= component_y && + seat->pointer_y < component_y + component_height) { + focus_component = child_component; + break; + } + } + + if (frame_cairo->grab) + frame_cairo->active = frame_cairo->grab; + else + frame_cairo->active = focus_component; + frame_cairo->focus = focus_component; + +} + +static void +ensure_component(struct libdecor_frame_cairo *frame_cairo, + struct border_component *cmpnt); + +static bool +redraw_scale(struct libdecor_frame_cairo *frame_cairo, + struct border_component *cmpnt) +{ + struct surface_output *surface_output; + int scale = 1; + + if (cmpnt->is_hidden) + return false; + + ensure_component(frame_cairo, cmpnt); + + wl_list_for_each(surface_output, &cmpnt->server.output_list, link) { + scale = MAX(scale, surface_output->output->scale); + } + if (scale != cmpnt->server.scale) { + cmpnt->server.scale = scale; + if ((cmpnt->type != SHADOW) || is_border_surfaces_showing(frame_cairo)) { + draw_border_component(frame_cairo, cmpnt); + return true; + } + } + return false; +} + +static bool +add_surface_output(struct libdecor_plugin_cairo *plugin_cairo, + struct wl_output *wl_output, + struct wl_list *list) +{ + struct output *output; + struct surface_output *surface_output; + + if (!own_output(wl_output)) + return false; + + output = wl_output_get_user_data(wl_output); + + if (output == NULL) + return false; + + surface_output = zalloc(sizeof *surface_output); + surface_output->output = output; + wl_list_insert(list, &surface_output->link); + return true; +} + +static void +surface_enter(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct libdecor_frame_cairo *frame_cairo = data; + struct border_component *cmpnt; + + if (!(own_surface(wl_surface) && own_output(wl_output))) + return; + + cmpnt = get_component_for_surface(frame_cairo, wl_surface); + if (cmpnt == NULL) + return; + + if (!add_surface_output(frame_cairo->plugin_cairo, wl_output, + &cmpnt->server.output_list)) + return; + + if (redraw_scale(frame_cairo, cmpnt)) + libdecor_frame_toplevel_commit(&frame_cairo->frame); +} + +static bool +remove_surface_output(struct wl_list *list, struct wl_output *wl_output) +{ + struct surface_output *surface_output; + wl_list_for_each(surface_output, list, link) { + if (surface_output->output->wl_output == wl_output) { + wl_list_remove(&surface_output->link); + free(surface_output); + return true; + } + } + return false; +} + +static void +surface_leave(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct libdecor_frame_cairo *frame_cairo = data; + struct border_component *cmpnt; + + if (!(own_surface(wl_surface) && own_output(wl_output))) + return; + + cmpnt = get_component_for_surface(frame_cairo, wl_surface); + if (cmpnt == NULL) + return; + + if (!remove_surface_output(&cmpnt->server.output_list, wl_output)) + return; + + if (redraw_scale(frame_cairo, cmpnt)) + libdecor_frame_toplevel_commit(&frame_cairo->frame); +} + +static struct wl_surface_listener surface_listener = { + surface_enter, + surface_leave, +}; + +static void +create_surface_subsurface_pair(struct libdecor_frame_cairo *frame_cairo, + struct wl_surface **out_wl_surface, + struct wl_subsurface **out_wl_subsurface) +{ + struct libdecor_plugin_cairo *plugin_cairo = frame_cairo->plugin_cairo; + struct libdecor_frame *frame = &frame_cairo->frame; + struct wl_compositor *wl_compositor = plugin_cairo->wl_compositor; + struct wl_subcompositor *wl_subcompositor = plugin_cairo->wl_subcompositor; + struct wl_surface *wl_surface; + struct wl_surface *parent; + struct wl_subsurface *wl_subsurface; + + wl_surface = wl_compositor_create_surface(wl_compositor); + wl_proxy_set_tag((struct wl_proxy *) wl_surface, + &libdecor_cairo_proxy_tag); + + parent = libdecor_frame_get_wl_surface(frame); + wl_subsurface = wl_subcompositor_get_subsurface(wl_subcompositor, + wl_surface, + parent); + + *out_wl_surface = wl_surface; + *out_wl_subsurface = wl_subsurface; +} + +static void +ensure_component(struct libdecor_frame_cairo *frame_cairo, + struct border_component *cmpnt) +{ + switch (cmpnt->composite_mode) { + case COMPOSITE_SERVER: + if (!cmpnt->server.wl_surface) { + wl_list_init(&cmpnt->server.output_list); + cmpnt->server.scale = 1; + create_surface_subsurface_pair(frame_cairo, + &cmpnt->server.wl_surface, + &cmpnt->server.wl_subsurface); + wl_surface_add_listener(cmpnt->server.wl_surface, + &surface_listener, + frame_cairo); + } + break; + case COMPOSITE_CLIENT: + wl_list_init(&cmpnt->server.output_list); + break; + } + + cmpnt->is_hidden = false; +} + +static void +ensure_border_surfaces(struct libdecor_frame_cairo *frame_cairo) +{ + int min_width, min_height; + + frame_cairo->shadow.opaque = false; + ensure_component(frame_cairo, &frame_cairo->shadow); + +#if ! APPLY_FLTK_CHANGES + libdecor_frame_get_min_content_size(&frame_cairo->frame, + &min_width, &min_height); + libdecor_frame_set_min_content_size(&frame_cairo->frame, + MAX(min_width, (int)MAX(56, 4 * BUTTON_WIDTH)), + MAX(min_height, (int)MAX(56, TITLE_HEIGHT + 1))); +#endif +} + +static void +ensure_title_bar_surfaces(struct libdecor_frame_cairo *frame_cairo) +{ + frame_cairo->title_bar.title.opaque = true; + ensure_component(frame_cairo, &frame_cairo->title_bar.title); + + frame_cairo->title_bar.min.opaque = true; + ensure_component(frame_cairo, &frame_cairo->title_bar.min); + + frame_cairo->title_bar.max.opaque = true; + ensure_component(frame_cairo, &frame_cairo->title_bar.max); + + frame_cairo->title_bar.close.opaque = true; + ensure_component(frame_cairo, &frame_cairo->title_bar.close); +} + +static void +calculate_component_size(struct libdecor_frame_cairo *frame_cairo, + enum component component, + int *component_x, + int *component_y, + int *component_width, + int *component_height) +{ + struct libdecor_frame *frame = &frame_cairo->frame; + int content_width, content_height; + + content_width = libdecor_frame_get_content_width(frame); + content_height = libdecor_frame_get_content_height(frame); + + switch (component) { + case NONE: + *component_width = 0; + *component_height = 0; + return; + case SHADOW: + *component_x = -(int)SHADOW_MARGIN; + *component_y = -(int)(SHADOW_MARGIN+TITLE_HEIGHT); + *component_width = content_width + 2 * SHADOW_MARGIN; + *component_height = content_height + + 2 * SHADOW_MARGIN + + TITLE_HEIGHT; + return; + case TITLE: + *component_x = 0; + *component_y = -(int)TITLE_HEIGHT; + *component_width = content_width; + *component_height = TITLE_HEIGHT; + return; + case BUTTON_MIN: + *component_x = content_width - 3 * BUTTON_WIDTH; + *component_y = 0; + *component_width = BUTTON_WIDTH; + *component_height = TITLE_HEIGHT; + return; + case BUTTON_MAX: + *component_x = content_width - 2 * BUTTON_WIDTH; + *component_y = 0; + *component_width = BUTTON_WIDTH; + *component_height = TITLE_HEIGHT; + return; + case BUTTON_CLOSE: + *component_x = content_width - BUTTON_WIDTH; + *component_y = 0; + *component_width = BUTTON_WIDTH; + *component_height = TITLE_HEIGHT; + return; + } + + abort(); +} + +static int +border_component_get_scale(struct border_component *border_component) +{ + switch (border_component->composite_mode) { + case COMPOSITE_SERVER: + return border_component->server.scale; + case COMPOSITE_CLIENT: + return border_component_get_scale( + border_component->client.parent_component); + } + return 0; +} + +static void +draw_title_text(struct libdecor_frame_cairo *frame_cairo, + cairo_t *cr, + const int *title_width, + bool active) +{ + const uint32_t col_title = active ? COL_TITLE : COL_TITLE_INACT; + const uint32_t col_title_text = active ? COL_SYM : COL_SYM_INACT; + + PangoLayout *layout; + + /* title fade out at buttons */ + const int fade_width = 5 * BUTTON_WIDTH; + int fade_start; + cairo_pattern_t *fade; + + /* text position and dimensions */ + int text_extents_width, text_extents_height; + double text_x, text_y; + double text_width, text_height; + + const char *title; + + title = libdecor_frame_get_title((struct libdecor_frame*) frame_cairo); + if (!title) + return; + + layout = pango_cairo_create_layout(cr); + + pango_layout_set_text(layout, + title, + -1); + pango_layout_set_font_description(layout, frame_cairo->plugin_cairo->font); + pango_layout_get_size(layout, &text_extents_width, &text_extents_height); + + /* set text position and dimensions */ + text_width = text_extents_width / PANGO_SCALE; + text_height = text_extents_height / PANGO_SCALE; + text_x = *title_width / 2.0 - text_width / 2.0; + text_x += MIN(0.0, ((*title_width - fade_width) - (text_x + text_width))); + text_x = MAX(text_x, BUTTON_WIDTH); + text_y = TITLE_HEIGHT / 2.0 - text_height / 2.0; + + /* draw title text */ + cairo_move_to(cr, text_x, text_y); + cairo_set_rgba32(cr, &col_title_text); + pango_cairo_show_layout(cr, layout); + + /* draw fade-out from title text to buttons */ + fade_start = *title_width - fade_width; + fade = cairo_pattern_create_linear(fade_start, 0, + fade_start + 2 * BUTTON_WIDTH, 0); + cairo_pattern_add_color_stop_rgba(fade, 0, + red(&col_title), + green(&col_title), + blue(&col_title), + 0); + cairo_pattern_add_color_stop_rgb(fade, 1, + red(&col_title), + green(&col_title), + blue(&col_title)); + cairo_rectangle(cr, fade_start, 0, fade_width, TITLE_HEIGHT); + cairo_set_source(cr, fade); + cairo_fill(cr); + + cairo_pattern_destroy(fade); + g_object_unref(layout); +} + +static void +draw_component_content(struct libdecor_frame_cairo *frame_cairo, + struct border_component *border_component, + int component_width, + int component_height, + enum component component) +{ + struct buffer *buffer; + cairo_surface_t *surface = NULL; + int width = 0, height = 0; + int scale; + cairo_t *cr; + + /* button symbol origin */ + const double x = BUTTON_WIDTH / 2 - SYM_DIM / 2 + 0.5; + const double y = TITLE_HEIGHT / 2 - SYM_DIM / 2 + 0.5; + + enum libdecor_window_state state; + + bool active; + + uint32_t col_title; + + bool cap_min, cap_max, cap_close; + + /* capabilities of decorations */ + cap_min = minimizable(frame_cairo); + cap_max = resizable(frame_cairo); + cap_close = closeable(frame_cairo); + + scale = border_component_get_scale(border_component); + + state = libdecor_frame_get_window_state((struct libdecor_frame *) frame_cairo); + + active = state & LIBDECOR_WINDOW_STATE_ACTIVE; + + col_title = active ? COL_TITLE : COL_TITLE_INACT; + + /* clear buffer */ + switch (border_component->composite_mode) { + case COMPOSITE_SERVER: + buffer = border_component->server.buffer; + + surface = cairo_image_surface_create_for_data( + buffer->data, CAIRO_FORMAT_ARGB32, + buffer->buffer_width, buffer->buffer_height, + cairo_format_stride_for_width( + CAIRO_FORMAT_ARGB32, + buffer->buffer_width) + ); + cairo_surface_set_device_scale(surface, scale, scale); + width = buffer->width; + height = buffer->height; + break; + case COMPOSITE_CLIENT: + surface = cairo_surface_reference(border_component->client.image); + width = cairo_image_surface_get_width(surface); + height = cairo_image_surface_get_height(surface); + break; + } + + cr = cairo_create(surface); + cairo_save(cr); + cairo_set_source_rgba(cr, 0.0, 0.0, 0.0, 0.0); + cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(cr); + cairo_restore(cr); + + /* background */ + switch (component) { + case NONE: + break; + case SHADOW: + if (frame_cairo->decoration_type != DECORATION_TYPE_TILED) + render_shadow(cr, + frame_cairo->shadow_blur, + -(int)SHADOW_MARGIN/2, + -(int)SHADOW_MARGIN/2, + width + SHADOW_MARGIN, + height + SHADOW_MARGIN, + 64, + 64); + break; + case TITLE: + cairo_set_rgba32(cr, &col_title); + cairo_paint(cr); + break; + case BUTTON_MIN: + if (cap_min && frame_cairo->active == &frame_cairo->title_bar.min) + cairo_set_rgba32(cr, active ? &COL_BUTTON_MIN : &COL_BUTTON_INACT); + else + cairo_set_rgba32(cr, &col_title); + cairo_paint(cr); + break; + case BUTTON_MAX: + if (cap_max && frame_cairo->active == &frame_cairo->title_bar.max) + cairo_set_rgba32(cr, active ? &COL_BUTTON_MAX : &COL_BUTTON_INACT); + else + cairo_set_rgba32(cr, &col_title); + cairo_paint(cr); + break; + case BUTTON_CLOSE: + if (cap_close && frame_cairo->active == &frame_cairo->title_bar.close) + cairo_set_rgba32(cr, active ? &COL_BUTTON_CLOSE : &COL_BUTTON_INACT); + else + cairo_set_rgba32(cr, &col_title); + cairo_paint(cr); + break; + } + + /* button symbols */ + /* https://www.cairographics.org/FAQ/#sharp_lines */ + cairo_set_line_width(cr, 1); + + switch (component) { + case TITLE: + draw_title_text(frame_cairo,cr, &component_width, active); + break; + case BUTTON_MIN: + if (!active) { + /* inactive: use single desaturated color */ + cairo_set_rgba32(cr, &COL_SYM_INACT); + } else { + if (!cap_min || + frame_cairo->active == &frame_cairo->title_bar.min) { + /* active (a.k.a. prelight) */ + cairo_set_rgba32(cr, &COL_SYM_ACT); + } else { + /* normal */ + cairo_set_rgba32(cr, &COL_SYM); + } + } + cairo_move_to(cr, x, y + SYM_DIM - 1); + cairo_rel_line_to(cr, SYM_DIM - 1, 0); + cairo_stroke(cr); + break; + case BUTTON_MAX: + if (!active) { + /* inactive: use single desaturated color */ + cairo_set_rgba32(cr, &COL_SYM_INACT); + } else { + if (!cap_max || + frame_cairo->active == &frame_cairo->title_bar.max) { + /* active (a.k.a. prelight) */ + cairo_set_rgba32(cr, &COL_SYM_ACT); + } else { + /* normal */ + cairo_set_rgba32(cr, &COL_SYM); + } + } + + if (state & LIBDECOR_WINDOW_STATE_MAXIMIZED) { + const size_t small = 12; + cairo_rectangle(cr, + x, + y + SYM_DIM - small, + small - 1, + small - 1); + cairo_move_to(cr, + x + SYM_DIM - small, + y + SYM_DIM - small); + cairo_line_to(cr, x + SYM_DIM - small, y); + cairo_rel_line_to(cr, small - 1, 0); + cairo_rel_line_to(cr, 0, small - 1); + cairo_line_to(cr, x + small - 1, y + small - 1); + } + else { + cairo_rectangle(cr, x, y, SYM_DIM - 1, SYM_DIM - 1); + } + cairo_stroke(cr); + break; + case BUTTON_CLOSE: + if (!active) { + /* inactive: use single desaturated color */ + cairo_set_rgba32(cr, &COL_SYM_INACT); + } else { + if (!cap_close || + frame_cairo->active == &frame_cairo->title_bar.close) { + /* active (a.k.a. prelight) */ + cairo_set_rgba32(cr, &COL_SYM_ACT); + } else { + /* normal */ + cairo_set_rgba32(cr, &COL_SYM); + } + } + cairo_move_to(cr, x, y); + cairo_rel_line_to(cr, SYM_DIM - 1, SYM_DIM - 1); + cairo_move_to(cr, x + SYM_DIM - 1, y); + cairo_line_to(cr, x, y + SYM_DIM - 1); + cairo_stroke(cr); + break; + default: + break; + } + + /* mask the toplevel surface */ + if (component == SHADOW) { + int component_x, component_y, component_width, component_height; + calculate_component_size(frame_cairo, component, + &component_x, &component_y, + &component_width, &component_height); + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + cairo_rectangle(cr, -component_x, -component_y, + libdecor_frame_get_content_width( + &frame_cairo->frame), + libdecor_frame_get_content_height( + &frame_cairo->frame)); + cairo_fill(cr); + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static void +set_component_input_region(struct libdecor_frame_cairo *frame_cairo, + struct border_component *border_component) +{ + if (border_component->type == SHADOW && frame_cairo->shadow_showing) { + struct wl_region *input_region; + int component_x; + int component_y; + int component_width; + int component_height; + + calculate_component_size(frame_cairo, border_component->type, + &component_x, &component_y, + &component_width, &component_height); + + /* + * the input region is the outer surface size minus the inner + * content size + */ + input_region = wl_compositor_create_region( + frame_cairo->plugin_cairo->wl_compositor); + wl_region_add(input_region, 0, 0, + component_width, component_height); + wl_region_subtract(input_region, -component_x, -component_y, + libdecor_frame_get_content_width(&frame_cairo->frame), + libdecor_frame_get_content_height(&frame_cairo->frame)); + wl_surface_set_input_region(border_component->server.wl_surface, + input_region); + wl_region_destroy(input_region); + } +} + +static void +ensure_component_realized_server(struct libdecor_frame_cairo *frame_cairo, + struct border_component *border_component, + int component_width, + int component_height, + int scale) +{ + struct buffer *old_buffer; + struct buffer *buffer = NULL; + + old_buffer = border_component->server.buffer; + if (old_buffer) { + if (!old_buffer->in_use && + old_buffer->buffer_width == component_width * scale && + old_buffer->buffer_height == component_height * scale) { + buffer = old_buffer; + } else { + buffer_free(old_buffer); + border_component->server.buffer = NULL; + } + } + + if (!buffer) + buffer = create_shm_buffer(frame_cairo->plugin_cairo, + component_width, + component_height, + border_component->opaque, + border_component->server.scale); + + border_component->server.buffer = buffer; +} + +static void +ensure_component_realized_client(struct libdecor_frame_cairo *frame_cairo, + struct border_component *border_component, + int component_width, + int component_height, + int scale) +{ + cairo_surface_t *old_image; + + old_image = border_component->client.image; + if (old_image) { + int cairo_buffer_width; + int cairo_buffer_height; + double x_scale; + double y_scale; + + cairo_surface_get_device_scale(old_image, &x_scale, &y_scale); + cairo_buffer_width = + (int) round(cairo_image_surface_get_width(old_image) * + x_scale); + cairo_buffer_height = + (int) round(cairo_image_surface_get_height(old_image) * + y_scale); + + if (cairo_buffer_width != component_width * scale || + cairo_buffer_height != component_height * scale) { + cairo_surface_destroy(old_image); + border_component->client.image = NULL; + } + } + + if (!border_component->client.image) { + cairo_surface_t *new_image; + + new_image = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, + component_width * scale, + component_height * scale); + cairo_surface_set_device_scale(new_image, scale, scale); + border_component->client.image = new_image; + } + +} + +static void +ensure_component_realized(struct libdecor_frame_cairo *frame_cairo, + struct border_component *border_component, + int component_width, + int component_height, + int scale) +{ + switch (border_component->composite_mode) { + case COMPOSITE_SERVER: + ensure_component_realized_server(frame_cairo, border_component, + component_width, + component_height, + scale); + break; + case COMPOSITE_CLIENT: + ensure_component_realized_client(frame_cairo, border_component, + component_width, + component_height, + scale); + break; + } +} + +static cairo_t * +create_cairo_for_parent(struct border_component *border_component) +{ + struct border_component *parent = + border_component->client.parent_component; + struct buffer *buffer; + struct border_component *server_component; + cairo_surface_t *parent_surface; + cairo_t *cr; + + switch (parent->composite_mode) { + case COMPOSITE_SERVER: + buffer = parent->server.buffer; + parent_surface = cairo_image_surface_create_for_data( + buffer->data, CAIRO_FORMAT_ARGB32, + buffer->buffer_width, buffer->buffer_height, + cairo_format_stride_for_width( + CAIRO_FORMAT_ARGB32, + buffer->buffer_width) + ); + cr = cairo_create(parent_surface); + cairo_surface_destroy(parent_surface); + cairo_scale(cr, buffer->scale, buffer->scale); + return cr; + case COMPOSITE_CLIENT: + cr = cairo_create(parent->client.image); + server_component = get_server_component(border_component); + cairo_scale(cr, + server_component->server.scale, + server_component->server.scale); + return cr; + } + return NULL; +} + +static void +draw_border_component(struct libdecor_frame_cairo *frame_cairo, + struct border_component *border_component) +{ + enum component component = border_component->type; + struct buffer *buffer; + cairo_t *cr; + int component_x; + int component_y; + int component_width; + int component_height; + int scale; + struct border_component *child_component; + + if (border_component->is_hidden) + return; + + calculate_component_size(frame_cairo, component, + &component_x, &component_y, + &component_width, &component_height); + + set_component_input_region(frame_cairo, border_component); + + scale = border_component_get_scale(border_component); + ensure_component_realized(frame_cairo, border_component, + component_width, + component_height, + scale); + + draw_component_content(frame_cairo, + border_component, + component_width, component_height, + component); + + switch(border_component->composite_mode) { + case COMPOSITE_SERVER: + buffer = border_component->server.buffer; + wl_surface_attach(border_component->server.wl_surface, + buffer->wl_buffer, + 0, 0); + wl_surface_set_buffer_scale(border_component->server.wl_surface, + buffer->scale); + buffer->in_use = true; + wl_surface_commit(border_component->server.wl_surface); + wl_surface_damage_buffer(border_component->server.wl_surface, 0, 0, + component_width * scale, + component_height * scale); + wl_subsurface_set_position(border_component->server.wl_subsurface, + component_x, component_y); + break; + case COMPOSITE_CLIENT: + cr = create_cairo_for_parent(border_component); + cairo_set_source_surface(cr, + border_component->client.image, + component_x, component_y); + cairo_paint(cr); + cairo_destroy(cr); + break; + } + + wl_list_for_each(child_component, &border_component->child_components, link) + draw_border_component(frame_cairo, child_component); +} + +static void +draw_border(struct libdecor_frame_cairo *frame_cairo) +{ + draw_border_component(frame_cairo, &frame_cairo->shadow); + frame_cairo->shadow_showing = true; +} + +static void +draw_title_bar(struct libdecor_frame_cairo *frame_cairo) +{ + draw_border_component(frame_cairo, &frame_cairo->title_bar.title); + frame_cairo->title_bar.is_showing = true; +} + +static void +draw_decoration(struct libdecor_frame_cairo *frame_cairo) +{ + switch (frame_cairo->decoration_type) { + case DECORATION_TYPE_NONE: + if (frame_cairo->link.next != NULL) + wl_list_remove(&frame_cairo->link); + if (is_border_surfaces_showing(frame_cairo)) + hide_border_surfaces(frame_cairo); + if (is_title_bar_surfaces_showing(frame_cairo)) + hide_title_bar_surfaces(frame_cairo); + break; + case DECORATION_TYPE_TILED: + case DECORATION_TYPE_ALL: + /* show borders */ + ensure_border_surfaces(frame_cairo); + draw_border(frame_cairo); + /* show title bar */ + ensure_title_bar_surfaces(frame_cairo); + draw_title_bar(frame_cairo); + /* link frame */ + if (frame_cairo->link.next == NULL) + wl_list_insert( + &frame_cairo->plugin_cairo->visible_frame_list, + &frame_cairo->link); + break; + case DECORATION_TYPE_MAXIMIZED: + /* hide borders */ + if (is_border_surfaces_showing(frame_cairo)) + hide_border_surfaces(frame_cairo); + /* show title bar */ + ensure_title_bar_surfaces(frame_cairo); + draw_title_bar(frame_cairo); + /* link frame */ + if (frame_cairo->link.next == NULL) + wl_list_insert( + &frame_cairo->plugin_cairo->visible_frame_list, + &frame_cairo->link); + break; + } +} + +static void +set_window_geometry(struct libdecor_frame_cairo *frame_cairo) +{ + struct libdecor_frame *frame = &frame_cairo->frame; + int x = 0, y = 0, width = 0, height = 0; + + switch (frame_cairo->decoration_type) { + case DECORATION_TYPE_NONE: + x = 0; + y = 0; + width = libdecor_frame_get_content_width(frame); + height = libdecor_frame_get_content_height(frame); + break; + case DECORATION_TYPE_ALL: + case DECORATION_TYPE_TILED: + case DECORATION_TYPE_MAXIMIZED: + x = 0; + y = -(int)TITLE_HEIGHT; + width = libdecor_frame_get_content_width(frame); + height = libdecor_frame_get_content_height(frame) + TITLE_HEIGHT; + break; + } + + libdecor_frame_set_window_geometry(frame, x, y, width, height); +} + +static enum decoration_type +window_state_to_decoration_type(enum libdecor_window_state window_state) +{ + if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) + return DECORATION_TYPE_NONE; + else if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) + /* title bar, no shadows */ + return DECORATION_TYPE_MAXIMIZED; + else if (window_state & LIBDECOR_WINDOW_STATE_TILED_LEFT || + window_state & LIBDECOR_WINDOW_STATE_TILED_RIGHT || + window_state & LIBDECOR_WINDOW_STATE_TILED_TOP || + window_state & LIBDECOR_WINDOW_STATE_TILED_BOTTOM) + /* title bar, invisible shadows */ + return DECORATION_TYPE_TILED; + else + /* title bar, shadows */ + return DECORATION_TYPE_ALL; +} + +static void +libdecor_plugin_cairo_frame_commit(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration) +{ + struct libdecor_frame_cairo *frame_cairo = + (struct libdecor_frame_cairo *) frame; + enum libdecor_window_state old_window_state; + enum libdecor_window_state new_window_state; + int old_content_width, old_content_height; + int new_content_width, new_content_height; + enum decoration_type old_decoration_type; + enum decoration_type new_decoration_type; + + old_window_state = frame_cairo->window_state; + new_window_state = libdecor_frame_get_window_state(frame); + + old_content_width = frame_cairo->content_width; + old_content_height = frame_cairo->content_height; + new_content_width = libdecor_frame_get_content_width(frame); + new_content_height = libdecor_frame_get_content_height(frame); + + old_decoration_type = frame_cairo->decoration_type; + new_decoration_type = window_state_to_decoration_type(new_window_state); + + if (old_decoration_type == new_decoration_type && + old_content_width == new_content_width && + old_content_height == new_content_height && + old_window_state == new_window_state) + return; + + frame_cairo->content_width = new_content_width; + frame_cairo->content_height = new_content_height; + frame_cairo->decoration_type = new_decoration_type; + frame_cairo->window_state = new_window_state; + + draw_decoration(frame_cairo); + set_window_geometry(frame_cairo); +} + +static void +libdecor_plugin_cairo_frame_property_changed(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ + struct libdecor_frame_cairo *frame_cairo = + (struct libdecor_frame_cairo *) frame; + bool redraw_needed = false; + const char *new_title; + + new_title = libdecor_frame_get_title(frame); + if (frame_cairo->title_bar.is_showing) { + if (!streql(frame_cairo->title, new_title)) + redraw_needed = true; + } + + if (frame_cairo->title) { + free(frame_cairo->title); + frame_cairo->title = NULL; + } + + if (new_title) { + frame_cairo->title = strdup(new_title); + } + + if (frame_cairo->capabilities != libdecor_frame_get_capabilities(frame)) { + frame_cairo->capabilities = libdecor_frame_get_capabilities(frame); + redraw_needed = true; + } + + if (redraw_needed) { + draw_decoration(frame_cairo); + libdecor_frame_toplevel_commit(frame); + } +} + +static void +libdecor_plugin_cairo_frame_translate_coordinate(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + int content_x, + int content_y, + int *frame_x, + int *frame_y) +{ + struct libdecor_frame_cairo *frame_cairo = + (struct libdecor_frame_cairo *) frame; + + *frame_x = content_x; + *frame_y = content_y; + + if (frame_cairo->title_bar.is_showing) + *frame_y += TITLE_HEIGHT; +} + +static bool +streq(const char *str1, + const char *str2) +{ + if (!str1 && !str2) + return true; + + if (str1 && str2) + return strcmp(str1, str2) == 0; + + return false; +} + +static void +libdecor_plugin_cairo_frame_popup_grab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_cairo *frame_cairo = + (struct libdecor_frame_cairo *) frame; + struct libdecor_plugin_cairo *plugin_cairo = frame_cairo->plugin_cairo; + struct seat *seat; + + wl_list_for_each(seat, &plugin_cairo->seat_list, link) { + if (streq(seat->name, seat_name)) { + if (seat->grabbed) { + fprintf(stderr, "libdecor-WARNING: Application " + "tried to grab seat twice\n"); + } + synthesize_pointer_leave(seat); + seat->grabbed = true; + return; + } + } + + fprintf(stderr, + "libdecor-WARNING: Application tried to grab unknown seat\n"); +} + +static void +libdecor_plugin_cairo_frame_popup_ungrab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_cairo *frame_cairo = + (struct libdecor_frame_cairo *) frame; + struct libdecor_plugin_cairo *plugin_cairo = frame_cairo->plugin_cairo; + struct seat *seat; + + wl_list_for_each(seat, &plugin_cairo->seat_list, link) { + if (streq(seat->name, seat_name)) { + if (!seat->grabbed) { + fprintf(stderr, "libdecor-WARNING: Application " + "tried to ungrab seat twice\n"); + } + seat->grabbed = false; + synthesize_pointer_enter(seat); + sync_active_component(frame_cairo, seat); + return; + } + } + + fprintf(stderr, + "libdecor-WARNING: Application tried to ungrab unknown seat\n"); +} + +static bool +libdecor_plugin_cairo_configuration_get_content_size( + struct libdecor_plugin *plugin, + struct libdecor_configuration *configuration, + struct libdecor_frame *frame, + int *content_width, + int *content_height) +{ + int win_width, win_height; + if (!libdecor_configuration_get_window_size(configuration, + &win_width, + &win_height)) + return false; + + enum libdecor_window_state state; + if (!libdecor_configuration_get_window_state(configuration, &state)) { + return false; + } + + switch (window_state_to_decoration_type(state)) { + case DECORATION_TYPE_NONE: + *content_width = win_width; + *content_height = win_height; + break; + case DECORATION_TYPE_ALL: + case DECORATION_TYPE_TILED: + case DECORATION_TYPE_MAXIMIZED: + *content_width = win_width; + *content_height = win_height - TITLE_HEIGHT; + break; + } + + return true; +} + +static bool +libdecor_plugin_cairo_frame_get_window_size_for( + struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + int *window_width, + int *window_height) +{ + enum libdecor_window_state window_state = + libdecor_state_get_window_state(state); + + switch (window_state_to_decoration_type(window_state)) { + case DECORATION_TYPE_NONE: + *window_width = libdecor_state_get_content_width(state); + *window_height = libdecor_state_get_content_height(state); + break; + case DECORATION_TYPE_ALL: + case DECORATION_TYPE_TILED: + case DECORATION_TYPE_MAXIMIZED: + *window_width = libdecor_state_get_content_width(state); + *window_height = + libdecor_state_get_content_height(state) + TITLE_HEIGHT; + break; + } + + return true; +} + +static struct libdecor_plugin_interface cairo_plugin_iface = { + .destroy = libdecor_plugin_cairo_destroy, + .get_fd = libdecor_plugin_cairo_get_fd, + .dispatch = libdecor_plugin_cairo_dispatch, + + .frame_new = libdecor_plugin_cairo_frame_new, + .frame_free = libdecor_plugin_cairo_frame_free, + .frame_commit = libdecor_plugin_cairo_frame_commit, + .frame_property_changed = libdecor_plugin_cairo_frame_property_changed, + .frame_translate_coordinate = + libdecor_plugin_cairo_frame_translate_coordinate, + .frame_popup_grab = libdecor_plugin_cairo_frame_popup_grab, + .frame_popup_ungrab = libdecor_plugin_cairo_frame_popup_ungrab, + + .configuration_get_content_size = + libdecor_plugin_cairo_configuration_get_content_size, + .frame_get_window_size_for = + libdecor_plugin_cairo_frame_get_window_size_for, +}; + +static void +init_wl_compositor(struct libdecor_plugin_cairo *plugin_cairo, + uint32_t id, + uint32_t version) +{ + plugin_cairo->wl_compositor = + wl_registry_bind(plugin_cairo->wl_registry, + id, &wl_compositor_interface, + MIN(version, 4)); +} + +static void +init_wl_subcompositor(struct libdecor_plugin_cairo *plugin_cairo, + uint32_t id, + uint32_t version) +{ + plugin_cairo->wl_subcompositor = + wl_registry_bind(plugin_cairo->wl_registry, + id, &wl_subcompositor_interface, 1); +} + +static void +shm_format(void *user_data, + struct wl_shm *wl_shm, + uint32_t format) +{ + struct libdecor_plugin_cairo *plugin_cairo = user_data; + + if (format == WL_SHM_FORMAT_ARGB8888) + plugin_cairo->has_argb = true; +} + +struct wl_shm_listener shm_listener = { + shm_format +}; + +static void +shm_callback(void *user_data, + struct wl_callback *callback, + uint32_t time) +{ + struct libdecor_plugin_cairo *plugin_cairo = user_data; + struct libdecor *context = plugin_cairo->context; + + wl_callback_destroy(callback); + plugin_cairo->globals_callback_shm = NULL; + + if (!plugin_cairo->has_argb) { + libdecor_notify_plugin_error( + context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + "Compositor is missing required shm format"); + return; + } + + libdecor_notify_plugin_ready(context); +} + +static const struct wl_callback_listener shm_callback_listener = { + shm_callback +}; + +static void +init_wl_shm(struct libdecor_plugin_cairo *plugin_cairo, + uint32_t id, + uint32_t version) +{ + struct libdecor *context = plugin_cairo->context; + struct wl_display *wl_display = libdecor_get_wl_display(context); + + plugin_cairo->wl_shm = + wl_registry_bind(plugin_cairo->wl_registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(plugin_cairo->wl_shm, &shm_listener, plugin_cairo); + + plugin_cairo->globals_callback_shm = wl_display_sync(wl_display); + wl_callback_add_listener(plugin_cairo->globals_callback_shm, + &shm_callback_listener, + plugin_cairo); +} + +static void +cursor_surface_enter(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct seat *seat = data; + + if(own_output(wl_output)) { + struct cursor_output *cursor_output; + cursor_output = zalloc(sizeof *cursor_output); + cursor_output->output = wl_output_get_user_data(wl_output); + wl_list_insert(&seat->cursor_outputs, &cursor_output->link); + if (update_local_cursor(seat)) + send_cursor(seat); + } +} + +static void +cursor_surface_leave(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct seat *seat = data; + + if(own_output(wl_output)) { + struct cursor_output *cursor_output, *tmp; + wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) { + if (cursor_output->output->wl_output == wl_output) { + wl_list_remove(&cursor_output->link); + free(cursor_output); + } + } + + if (update_local_cursor(seat)) + send_cursor(seat); + } +} + +static struct wl_surface_listener cursor_surface_listener = { + cursor_surface_enter, + cursor_surface_leave, +}; + +static void +ensure_cursor_surface(struct seat *seat) +{ + struct wl_compositor *wl_compositor = seat->plugin_cairo->wl_compositor; + + if (seat->cursor_surface) + return; + + seat->cursor_surface = wl_compositor_create_surface(wl_compositor); + wl_surface_add_listener(seat->cursor_surface, + &cursor_surface_listener, seat); +} + +static bool +ensure_cursor_theme(struct seat *seat) +{ + struct libdecor_plugin_cairo *plugin_cairo = seat->plugin_cairo; + int scale = 1; + struct wl_cursor_theme *theme; + struct cursor_output *cursor_output; + + wl_list_for_each(cursor_output, &seat->cursor_outputs, link) { + scale = MAX(scale, cursor_output->output->scale); + } + + if (seat->cursor_theme && seat->cursor_scale == scale) + return false; + + seat->cursor_scale = scale; + theme = wl_cursor_theme_load(plugin_cairo->cursor_theme_name, + plugin_cairo->cursor_size * scale, + plugin_cairo->wl_shm); + if (theme == NULL) + return false; + + if (seat->cursor_theme) + wl_cursor_theme_destroy(seat->cursor_theme); + + seat->cursor_theme = theme; + + for (unsigned int i = 0; i < ARRAY_LENGTH(cursor_names); i++) { + seat->cursors[i] = wl_cursor_theme_get_cursor( + seat->cursor_theme, + cursor_names[i]); + } + + seat->cursor_left_ptr = wl_cursor_theme_get_cursor(seat->cursor_theme, + "left_ptr"); + seat->current_cursor = seat->cursor_left_ptr; + + return true; +} + +enum libdecor_resize_edge +component_edge(const struct border_component *cmpnt, + const int pointer_x, + const int pointer_y, + const int margin) +{ + const bool top = pointer_y < margin; + const bool bottom = pointer_y > (cmpnt->server.buffer->height - margin); + const bool left = pointer_x < margin; + const bool right = pointer_x > (cmpnt->server.buffer->width - margin); + + if (top) + if (left) + return LIBDECOR_RESIZE_EDGE_TOP_LEFT; + else if (right) + return LIBDECOR_RESIZE_EDGE_TOP_RIGHT; + else + return LIBDECOR_RESIZE_EDGE_TOP; + else if (bottom) + if (left) + return LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT; + else if (right) + return LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT; + else + return LIBDECOR_RESIZE_EDGE_BOTTOM; + else if (left) + return LIBDECOR_RESIZE_EDGE_LEFT; + else if (right) + return LIBDECOR_RESIZE_EDGE_RIGHT; + else + return LIBDECOR_RESIZE_EDGE_NONE; +} + +static bool +update_local_cursor(struct seat *seat) +{ + if (!seat->pointer_focus) { + seat->current_cursor = seat->cursor_left_ptr; + return false; + } + + if (!own_surface(seat->pointer_focus)) + return false; + + struct libdecor_frame_cairo *frame_cairo = + wl_surface_get_user_data(seat->pointer_focus); + struct wl_cursor *wl_cursor = NULL; + + if (!frame_cairo || !frame_cairo->active) { + seat->current_cursor = seat->cursor_left_ptr; + return false; + } + + bool theme_updated = ensure_cursor_theme(seat); + + if (frame_cairo->active->type == SHADOW && + is_border_surfaces_showing(frame_cairo) && + resizable(frame_cairo)) { + enum libdecor_resize_edge edge; + edge = component_edge(frame_cairo->active, + seat->pointer_x, + seat->pointer_y, SHADOW_MARGIN); + + if (edge != LIBDECOR_RESIZE_EDGE_NONE) + wl_cursor = seat->cursors[edge - 1]; + } else { + wl_cursor = seat->cursor_left_ptr; + } + + if (seat->current_cursor != wl_cursor) { + seat->current_cursor = wl_cursor; + return true; + } + + return theme_updated; +} + +static void +send_cursor(struct seat *seat) +{ + struct wl_cursor_image *image; + struct wl_buffer *buffer; + + if (seat->pointer_focus == NULL || seat->current_cursor == NULL) + return; + + image = seat->current_cursor->images[0]; + buffer = wl_cursor_image_get_buffer(image); + wl_surface_attach(seat->cursor_surface, buffer, 0, 0); + wl_surface_set_buffer_scale(seat->cursor_surface, seat->cursor_scale); + wl_surface_damage_buffer(seat->cursor_surface, 0, 0, + image->width * seat->cursor_scale, + image->height * seat->cursor_scale); + wl_surface_commit(seat->cursor_surface); + wl_pointer_set_cursor(seat->wl_pointer, seat->serial, + seat->cursor_surface, + image->hotspot_x / seat->cursor_scale, + image->hotspot_y / seat->cursor_scale); +} + +static void +sync_active_component(struct libdecor_frame_cairo *frame_cairo, + struct seat *seat) +{ + struct border_component *old_active; + + if (!seat->pointer_focus) + return; + + old_active = frame_cairo->active; + update_component_focus(frame_cairo, seat->pointer_focus, seat); + if (old_active != frame_cairo->active) { + draw_decoration(frame_cairo); + libdecor_frame_toplevel_commit(&frame_cairo->frame); + } + + if (update_local_cursor(seat)) + send_cursor(seat); +} + +static void +synthesize_pointer_enter(struct seat *seat) +{ + struct wl_surface *surface; + struct libdecor_frame_cairo *frame_cairo; + + surface = seat->pointer_focus; + if (!surface) + return; + + frame_cairo = wl_surface_get_user_data(surface); + if (!frame_cairo) + return; + + update_component_focus(frame_cairo, seat->pointer_focus, seat); + frame_cairo->grab = NULL; + + /* update decorations */ + if (frame_cairo->active) { + draw_decoration(frame_cairo); + libdecor_frame_toplevel_commit(&frame_cairo->frame); + } + + update_local_cursor(seat); + send_cursor(seat); +} + +static void +synthesize_pointer_leave(struct seat *seat) +{ + struct wl_surface *surface; + struct libdecor_frame_cairo *frame_cairo; + + surface = seat->pointer_focus; + if (!surface) + return; + + frame_cairo = wl_surface_get_user_data(surface); + if (!frame_cairo) + return; + + if (!frame_cairo->active) + return; + + frame_cairo->active = NULL; + draw_decoration(frame_cairo); + libdecor_frame_toplevel_commit(&frame_cairo->frame); + update_local_cursor(seat); +} + +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 = data; + + if (!surface) + return; + + if (!own_surface(surface)) + return; + + ensure_cursor_surface(seat); + + seat->pointer_x = wl_fixed_to_int(surface_x); + seat->pointer_y = wl_fixed_to_int(surface_y); + seat->serial = serial; + seat->pointer_focus = surface; + + if (seat->grabbed) + return; + + synthesize_pointer_enter(seat); +} + +static void +pointer_leave(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + struct wl_surface *surface) +{ + struct seat *seat = data; + + if (!surface) + return; + + if (!own_surface(surface)) + return; + + synthesize_pointer_leave(seat); + seat->pointer_focus = NULL; +} + +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 = data; + struct libdecor_frame_cairo *frame_cairo; + + seat->pointer_x = wl_fixed_to_int(surface_x); + seat->pointer_y = wl_fixed_to_int(surface_y); + + if (seat->grabbed) + return; + + if (!seat->pointer_focus) + return; + + frame_cairo = wl_surface_get_user_data(seat->pointer_focus); + + sync_active_component(frame_cairo, seat); +} + +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 = data; + struct libdecor_frame_cairo *frame_cairo; + + if (!seat->pointer_focus || !own_surface(seat->pointer_focus)) + return; + + frame_cairo = wl_surface_get_user_data(seat->pointer_focus); + if (!frame_cairo) + return; + + if (seat->grabbed) { + libdecor_frame_dismiss_popup(&frame_cairo->frame, seat->name); + return; + } + + if (!frame_cairo->active) + return; + + if (button == BTN_LEFT) { + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + enum libdecor_resize_edge edge = + LIBDECOR_RESIZE_EDGE_NONE; + + frame_cairo->grab = NULL; + + switch (frame_cairo->active->type) { + case SHADOW: + edge = component_edge(frame_cairo->active, + seat->pointer_x, + seat->pointer_y, + SHADOW_MARGIN); + break; + case TITLE: + if (time-seat->pointer_button_time_stamp < + DOUBLE_CLICK_TIME_MS) { + toggle_maximized(&frame_cairo->frame); + } + else if (moveable(frame_cairo)) { + seat->pointer_button_time_stamp = time; + libdecor_frame_move(&frame_cairo->frame, + seat->wl_seat, + serial); + } + break; + case BUTTON_MIN: + case BUTTON_MAX: + case BUTTON_CLOSE: + frame_cairo->grab = frame_cairo->active; + break; + default: + break; + } + + if (edge != LIBDECOR_RESIZE_EDGE_NONE && + resizable(frame_cairo)) { + libdecor_frame_resize( + &frame_cairo->frame, + seat->wl_seat, + serial, + edge); + } + } + else if (state == WL_POINTER_BUTTON_STATE_RELEASED && + frame_cairo->grab) { + if (frame_cairo->grab == frame_cairo->focus) { + switch (frame_cairo->active->type) { + case BUTTON_MIN: + if (minimizable(frame_cairo)) + libdecor_frame_set_minimized( + &frame_cairo->frame); + break; + case BUTTON_MAX: + toggle_maximized(&frame_cairo->frame); + break; + case BUTTON_CLOSE: + if (closeable(frame_cairo)) + libdecor_frame_close(&frame_cairo->frame); + break; + default: + break; + } + } + frame_cairo->grab = NULL; + sync_active_component(frame_cairo, seat); + } + } + else if (button == BTN_RIGHT && + state == WL_POINTER_BUTTON_STATE_PRESSED && + seat->pointer_focus == frame_cairo->title_bar.title.server.wl_surface) { + libdecor_frame_show_window_menu(&frame_cairo->frame, + seat->wl_seat, + serial, + seat->pointer_x, + seat->pointer_y - TITLE_HEIGHT); + } +} + +static void +pointer_axis(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + uint32_t axis, + wl_fixed_t value) +{ +} + +static struct wl_pointer_listener pointer_listener = { + pointer_enter, + pointer_leave, + pointer_motion, + pointer_button, + pointer_axis +}; + +static void +seat_capabilities(void *data, + struct wl_seat *wl_seat, + uint32_t capabilities) +{ + struct seat *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); + } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && + seat->wl_pointer) { + wl_pointer_release(seat->wl_pointer); + seat->wl_pointer = NULL; + } +} + +static void +seat_name(void *data, + struct wl_seat *wl_seat, + const char *name) +{ + struct seat *seat = data; + + seat->name = strdup(name); +} + +static struct wl_seat_listener seat_listener = { + seat_capabilities, + seat_name +}; + +static void +init_wl_seat(struct libdecor_plugin_cairo *plugin_cairo, + uint32_t id, + uint32_t version) +{ + struct seat *seat; + + if (version < 3) { + libdecor_notify_plugin_error( + plugin_cairo->context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + "%s version 3 required but only version %i is available\n", + wl_seat_interface.name, version); + } + + seat = zalloc(sizeof *seat); + seat->cursor_scale = 1; + seat->plugin_cairo = plugin_cairo; + wl_list_init(&seat->cursor_outputs); + wl_list_insert(&plugin_cairo->seat_list, &seat->link); + seat->wl_seat = + wl_registry_bind(plugin_cairo->wl_registry, + id, &wl_seat_interface, 3); + wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); +} + +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) +{ +} + +static void +output_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int32_t width, + int32_t height, + int32_t refresh) +{ +} + +static void +output_done(void *data, + struct wl_output *wl_output) +{ + struct output *output = data; + struct libdecor_frame_cairo *frame_cairo; + struct seat *seat; + + wl_list_for_each(frame_cairo, + &output->plugin_cairo->visible_frame_list, link) { + bool updated = false; + updated |= redraw_scale(frame_cairo, &frame_cairo->shadow); + updated |= redraw_scale(frame_cairo, &frame_cairo->title_bar.title); + if (updated) + libdecor_frame_toplevel_commit(&frame_cairo->frame); + } + wl_list_for_each(seat, &output->plugin_cairo->seat_list, link) { + if (update_local_cursor(seat)) + send_cursor(seat); + } +} + +static void +output_scale(void *data, + struct wl_output *wl_output, + int32_t factor) +{ + struct output *output = data; + + output->scale = factor; +} + +static struct wl_output_listener output_listener = { + output_geometry, + output_mode, + output_done, + output_scale +}; + +static void +init_wl_output(struct libdecor_plugin_cairo *plugin_cairo, + uint32_t id, + uint32_t version) +{ + struct output *output; + + if (version < 2) { + libdecor_notify_plugin_error( + plugin_cairo->context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + "%s version 2 required but only version %i is available\n", + wl_output_interface.name, version); + } + + output = zalloc(sizeof *output); + output->plugin_cairo = plugin_cairo; + wl_list_insert(&plugin_cairo->output_list, &output->link); + output->id = id; + output->wl_output = + wl_registry_bind(plugin_cairo->wl_registry, + id, &wl_output_interface, 2); + wl_proxy_set_tag((struct wl_proxy *) output->wl_output, + &libdecor_cairo_proxy_tag); + wl_output_add_listener(output->wl_output, &output_listener, output); +} + +static void +registry_handle_global(void *user_data, + struct wl_registry *wl_registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + struct libdecor_plugin_cairo *plugin_cairo = user_data; + + if (strcmp(interface, "wl_compositor") == 0) + init_wl_compositor(plugin_cairo, id, version); + else if (strcmp(interface, "wl_subcompositor") == 0) + init_wl_subcompositor(plugin_cairo, id, version); + else if (strcmp(interface, "wl_shm") == 0) + init_wl_shm(plugin_cairo, id, version); + else if (strcmp(interface, "wl_seat") == 0) + init_wl_seat(plugin_cairo, id, version); + else if (strcmp(interface, "wl_output") == 0) + init_wl_output(plugin_cairo, id, version); +} + +static void +remove_surface_outputs(struct border_component *cmpnt, struct output *output) +{ + struct surface_output *surface_output; + wl_list_for_each(surface_output, &cmpnt->server.output_list, link) { + if (surface_output->output == output) { + wl_list_remove(&surface_output->link); + free(surface_output); + break; + } + } +} + +static void +output_removed(struct libdecor_plugin_cairo *plugin_cairo, + struct output *output) +{ + struct libdecor_frame_cairo *frame_cairo; + struct seat *seat; + + wl_list_for_each(frame_cairo, &plugin_cairo->visible_frame_list, link) { + remove_surface_outputs(&frame_cairo->shadow, output); + remove_surface_outputs(&frame_cairo->title_bar.title, output); + remove_surface_outputs(&frame_cairo->title_bar.min, output); + remove_surface_outputs(&frame_cairo->title_bar.max, output); + remove_surface_outputs(&frame_cairo->title_bar.close, output); + } + wl_list_for_each(seat, &plugin_cairo->seat_list, link) { + struct cursor_output *cursor_output, *tmp; + wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) { + if (cursor_output->output == output) { + wl_list_remove(&cursor_output->link); + free(cursor_output); + } + } + } + + wl_list_remove(&output->link); + wl_output_destroy(output->wl_output); + free(output); +} + +static void +registry_handle_global_remove(void *user_data, + struct wl_registry *wl_registry, + uint32_t name) +{ + struct libdecor_plugin_cairo *plugin_cairo = user_data; + struct output *output; + + wl_list_for_each(output, &plugin_cairo->output_list, link) { + if (output->id == name) { + output_removed(plugin_cairo, output); + break; + } + } +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static bool +has_required_globals(struct libdecor_plugin_cairo *plugin_cairo) +{ + if (!plugin_cairo->wl_compositor) + return false; + if (!plugin_cairo->wl_subcompositor) + return false; + if (!plugin_cairo->wl_shm) + return false; + + return true; +} + +static void +globals_callback(void *user_data, + struct wl_callback *callback, + uint32_t time) +{ + struct libdecor_plugin_cairo *plugin_cairo = user_data; + + wl_callback_destroy(callback); + plugin_cairo->globals_callback = NULL; + + if (!has_required_globals(plugin_cairo)) { + libdecor_notify_plugin_error( + plugin_cairo->context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + "Compositor is missing required globals"); + } +} + +static const struct wl_callback_listener globals_callback_listener = { + globals_callback +}; + +static struct libdecor_plugin * +libdecor_plugin_new(struct libdecor *context) +{ + struct libdecor_plugin_cairo *plugin_cairo; + struct wl_display *wl_display; + + plugin_cairo = zalloc(sizeof *plugin_cairo); + libdecor_plugin_init(&plugin_cairo->plugin, + context, + &cairo_plugin_iface); + plugin_cairo->context = context; + + wl_list_init(&plugin_cairo->visible_frame_list); + wl_list_init(&plugin_cairo->seat_list); + wl_list_init(&plugin_cairo->output_list); + + /* fetch cursor theme and size*/ + if (!libdecor_get_cursor_settings(&plugin_cairo->cursor_theme_name, + &plugin_cairo->cursor_size)) { + plugin_cairo->cursor_theme_name = NULL; + plugin_cairo->cursor_size = 24; + } + + /* define a sens-serif bold font at symbol size */ + plugin_cairo->font = pango_font_description_new(); + pango_font_description_set_family(plugin_cairo->font, "sans"); + pango_font_description_set_weight(plugin_cairo->font, PANGO_WEIGHT_BOLD); + pango_font_description_set_size(plugin_cairo->font, SYM_DIM * PANGO_SCALE); + + wl_display = libdecor_get_wl_display(context); + plugin_cairo->wl_registry = wl_display_get_registry(wl_display); + wl_registry_add_listener(plugin_cairo->wl_registry, + ®istry_listener, + plugin_cairo); + + plugin_cairo->globals_callback = wl_display_sync(wl_display); + wl_callback_add_listener(plugin_cairo->globals_callback, + &globals_callback_listener, + plugin_cairo); + + return &plugin_cairo->plugin; +} + +static struct libdecor_plugin_priority priorities[] = { + { NULL, LIBDECOR_PLUGIN_PRIORITY_MEDIUM } +}; + +LIBDECOR_EXPORT const struct libdecor_plugin_description +libdecor_plugin_description = { + .api_version = LIBDECOR_PLUGIN_API_VERSION, + .capabilities = LIBDECOR_PLUGIN_CAPABILITY_BASE, + .description = "libdecor plugin using Cairo", + .priorities = priorities, + .constructor = libdecor_plugin_new, +}; diff --git a/libdecor/src/plugins/dummy/libdecor-dummy.c b/libdecor/src/plugins/dummy/libdecor-dummy.c new file mode 100644 index 000000000..1891b7e51 --- /dev/null +++ b/libdecor/src/plugins/dummy/libdecor-dummy.c @@ -0,0 +1,176 @@ +/* + * Copyright © 2021 Jonas Ådahl + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include "libdecor-plugin.h" + +#include <errno.h> +#include <fcntl.h> +#include <poll.h> +#include <stdlib.h> +#include <wayland-cursor.h> + +#include "utils.h" + +struct libdecor_plugin_dummy { + struct libdecor_plugin plugin; + struct libdecor *context; +}; + +static void +libdecor_plugin_dummy_destroy(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_dummy *plugin_dummy = + (struct libdecor_plugin_dummy *) plugin; + + free(plugin_dummy); +} + +static struct libdecor_frame * +libdecor_plugin_dummy_frame_new(struct libdecor_plugin *plugin) +{ + struct libdecor_frame *frame; + + frame = zalloc(sizeof *frame); + + return frame; +} + +static void +libdecor_plugin_dummy_frame_free(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ +} + +static void +libdecor_plugin_dummy_frame_commit(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration) +{ +} + +static void +libdecor_plugin_dummy_frame_property_changed(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ +} + +static void +libdecor_plugin_dummy_frame_translate_coordinate(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + int content_x, + int content_y, + int *frame_x, + int *frame_y) +{ + *frame_x = content_x; + *frame_y = content_y; +} + +static void +libdecor_plugin_dummy_frame_popup_grab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ +} + +static void +libdecor_plugin_dummy_frame_popup_ungrab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ +} + +static bool +libdecor_plugin_dummy_configuration_get_content_size( + struct libdecor_plugin *plugin, + struct libdecor_configuration *configuration, + struct libdecor_frame *frame, + int *content_width, + int *content_height) +{ + return libdecor_configuration_get_window_size(configuration, + content_width, + content_height); +} + +static bool +libdecor_plugin_dummy_frame_get_window_size_for( + struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + int *window_width, + int *window_height) +{ + *window_width = libdecor_state_get_content_width (state); + *window_height = libdecor_state_get_content_height (state); + return true; +} + +static struct libdecor_plugin_interface dummy_plugin_iface = { + .destroy = libdecor_plugin_dummy_destroy, + + .frame_new = libdecor_plugin_dummy_frame_new, + .frame_free = libdecor_plugin_dummy_frame_free, + .frame_commit = libdecor_plugin_dummy_frame_commit, + .frame_property_changed = libdecor_plugin_dummy_frame_property_changed, + .frame_translate_coordinate = + libdecor_plugin_dummy_frame_translate_coordinate, + .frame_popup_grab = libdecor_plugin_dummy_frame_popup_grab, + .frame_popup_ungrab = libdecor_plugin_dummy_frame_popup_ungrab, + + .configuration_get_content_size = + libdecor_plugin_dummy_configuration_get_content_size, + .frame_get_window_size_for = + libdecor_plugin_dummy_frame_get_window_size_for, +}; + +static struct libdecor_plugin * +libdecor_plugin_new(struct libdecor *context) +{ + struct libdecor_plugin_dummy *plugin_dummy; + + plugin_dummy = zalloc(sizeof *plugin_dummy); + plugin_dummy->plugin.iface = &dummy_plugin_iface; + plugin_dummy->context = context; + + libdecor_notify_plugin_ready(context); + + return &plugin_dummy->plugin; +} + +static struct libdecor_plugin_priority priorities[] = { + { NULL, LIBDECOR_PLUGIN_PRIORITY_LOW } +}; + +LIBDECOR_EXPORT const struct libdecor_plugin_description +libdecor_plugin_description = { + .api_version = LIBDECOR_PLUGIN_API_VERSION, + .description = "dummy libdecor plugin", + .priorities = priorities, + .constructor = libdecor_plugin_new, +}; diff --git a/libdecor/src/plugins/gtk/libdecor-gtk.c b/libdecor/src/plugins/gtk/libdecor-gtk.c new file mode 100644 index 000000000..d5261c153 --- /dev/null +++ b/libdecor/src/plugins/gtk/libdecor-gtk.c @@ -0,0 +1,2762 @@ +#include "config.h" + +#include <linux/input.h> +#include <fcntl.h> +#include <string.h> +#include <sys/mman.h> +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <wayland-cursor.h> + +#include "libdecor-plugin.h" +#include "utils.h" +#include "cursor-settings.h" + +#include <cairo/cairo.h> + +#ifndef APPLY_FLTK_CHANGES +#define APPLY_FLTK_CHANGES 1 +#endif + +/* Note for FLTK: This header file changes location, while its content stays unchanged, + between the master and gtk_cairo_single branches */ +#include "../cairo/libdecor-cairo-blur.h" +#include <poll.h> + +#include <gtk/gtk.h> + +static const size_t SHADOW_MARGIN = 24; /* graspable part of the border */ + +static const char *cursor_names[] = { + "top_side", + "bottom_side", + "left_side", + "top_left_corner", + "bottom_left_corner", + "right_side", + "top_right_corner", + "bottom_right_corner" +}; + +enum header_element { + HDR_NONE, + HDR_HDR, /* entire header bar */ + HDR_TITLE, /* label */ + HDR_MIN, + HDR_MAX, + HDR_CLOSE, +}; + +struct header_element_data { + const char* name; + enum header_element type; + /* pointer to button or NULL if not found*/ + GtkWidget *widget; + GtkStateFlags state; +}; + +static void +find_widget_by_name(GtkWidget *widget, void *data) +{ + if (GTK_IS_WIDGET(widget)) { + char *style_ctx = gtk_style_context_to_string( + gtk_widget_get_style_context(widget), + GTK_STYLE_CONTEXT_PRINT_SHOW_STYLE); + if (strstr(style_ctx, ((struct header_element_data *)data)->name)) { + ((struct header_element_data *)data)->widget = widget; + free(style_ctx); + return; + } + free(style_ctx); + } + + if (GTK_IS_CONTAINER(widget)) { + /* recursively traverse container */ + gtk_container_forall(GTK_CONTAINER(widget), &find_widget_by_name, data); + } +} + +static struct header_element_data +find_widget_by_type(GtkWidget *widget, enum header_element type) +{ + char* name = NULL; + switch (type) { + case HDR_HDR: + name = "headerbar.titlebar:"; + break; + case HDR_TITLE: + name = "label.title:"; + break; + case HDR_MIN: + name = ".minimize"; + break; + case HDR_MAX: + name = ".maximize"; + break; + case HDR_CLOSE: + name = ".close"; + break; + default: + break; + } + + struct header_element_data data = {.name = name, .type = type, .widget = NULL}; + find_widget_by_name(widget, &data); + return data; +} + +static bool +in_region(const cairo_rectangle_int_t *rect, const int *x, const int *y) +{ + return (*x>=rect->x) & (*y>=rect->y) & + (*x<(rect->x+rect->width)) & (*y<(rect->y+rect->height)); +} + +static struct header_element_data +get_header_focus(const GtkHeaderBar *header_bar, const int x, const int y) +{ + /* we have to check child widgets (buttons, title) before the 'HDR_HDR' root widget */ + static const enum header_element elems[] = + {HDR_TITLE, HDR_MIN, HDR_MAX, HDR_CLOSE}; + + for (size_t i=0; i<4; i++) { + struct header_element_data elem = + find_widget_by_type(GTK_WIDGET(header_bar), elems[i]); + GtkAllocation allocation; +#if APPLY_FLTK_CHANGES + if (elem.widget) +#endif + gtk_widget_get_allocation(GTK_WIDGET(elem.widget), &allocation); + if (elem.widget && in_region(&allocation, &x, &y)) { + return elem; + } + } + + struct header_element_data elem_none = { .widget=NULL}; + return elem_none; +} + +static bool +streq(const char *str1, + const char *str2) +{ + if (!str1 && !str2) + return true; + + if (str1 && str2) + return strcmp(str1, str2) == 0; + + return false; +} + +enum decoration_type { + DECORATION_TYPE_NONE, + DECORATION_TYPE_ALL, + DECORATION_TYPE_TITLE_ONLY +}; + +enum component { + NONE = 0, + SHADOW, + HEADER, +}; + +struct seat { + struct libdecor_plugin_gtk *plugin_gtk; + + char *name; + + struct wl_seat *wl_seat; + struct wl_pointer *wl_pointer; + + struct wl_surface *cursor_surface; + struct wl_cursor *current_cursor; + int cursor_scale; + struct wl_list cursor_outputs; + + struct wl_cursor_theme *cursor_theme; + /* cursors for resize edges and corners */ + struct wl_cursor *cursors[ARRAY_LENGTH(cursor_names)]; + struct wl_cursor *cursor_left_ptr; + + struct wl_surface *pointer_focus; + + int pointer_x, pointer_y; + + uint32_t pointer_button_time_stamp; + + uint32_t serial; + + bool grabbed; + + struct wl_list link; +}; + +struct output { + struct libdecor_plugin_gtk *plugin_gtk; + + struct wl_output *wl_output; + uint32_t id; + int scale; + + struct wl_list link; +}; + +struct buffer { + struct wl_buffer *wl_buffer; + bool in_use; + bool is_detached; + + void *data; + size_t data_size; + int width; + int height; + int scale; + int buffer_width; + int buffer_height; +}; + +struct border_component { + enum component type; + struct wl_surface *wl_surface; + struct wl_subsurface *wl_subsurface; + struct buffer *buffer; + bool opaque; + struct wl_list output_list; + int scale; + + struct wl_list child_components; /* border_component::link */ + struct wl_list link; /* border_component::child_components */ +}; + +struct surface_output { + struct output *output; + struct wl_list link; +}; + +struct cursor_output { + struct output *output; + struct wl_list link; +}; + +struct libdecor_frame_gtk { + struct libdecor_frame frame; + + struct libdecor_plugin_gtk *plugin_gtk; + + int content_width; + int content_height; + + enum libdecor_window_state window_state; + + enum decoration_type decoration_type; + + char *title; + + enum libdecor_capabilities capabilities; + + struct border_component *active; + + struct border_component *focus; + struct border_component *grab; + + bool shadow_showing; + struct border_component shadow; + + GtkWidget *window; /* offscreen window for rendering */ + GtkWidget *header; /* header bar with widgets */ + struct border_component headerbar; + struct header_element_data hdr_focus; + + /* store pre-processed shadow tile */ + cairo_surface_t *shadow_blur; + + struct wl_list link; +}; + +struct libdecor_plugin_gtk { + struct libdecor_plugin plugin; + + struct wl_callback *globals_callback; + struct wl_callback *globals_callback_shm; + + struct libdecor *context; + + struct wl_registry *wl_registry; + struct wl_subcompositor *wl_subcompositor; + struct wl_compositor *wl_compositor; + + struct wl_shm *wl_shm; + struct wl_callback *shm_callback; + bool has_argb; + + struct wl_list visible_frame_list; + struct wl_list seat_list; + struct wl_list output_list; + + char *cursor_theme_name; + int cursor_size; + + GtkApplication *app; + int double_click_time_ms; +}; + +static const char *libdecor_gtk_proxy_tag = "libdecor-gtk"; + +static bool +own_proxy(struct wl_proxy *proxy) +{ + return (wl_proxy_get_tag(proxy) == &libdecor_gtk_proxy_tag); +} + +static bool +own_surface(struct wl_surface *surface) +{ + return own_proxy((struct wl_proxy *) surface); +} + +static bool +own_output(struct wl_output *output) +{ + return own_proxy((struct wl_proxy *) output); +} + +static bool +moveable(struct libdecor_frame_gtk *frame_gtk) { + return libdecor_frame_has_capability(&frame_gtk->frame, + LIBDECOR_ACTION_MOVE); +} + +static bool +resizable(struct libdecor_frame_gtk *frame_gtk) { + return libdecor_frame_has_capability(&frame_gtk->frame, + LIBDECOR_ACTION_RESIZE); +} + +static bool +minimizable(struct libdecor_frame_gtk *frame_gtk) { + return libdecor_frame_has_capability(&frame_gtk->frame, + LIBDECOR_ACTION_MINIMIZE); +} + +static bool +closeable(struct libdecor_frame_gtk *frame_gtk) { + return libdecor_frame_has_capability(&frame_gtk->frame, + LIBDECOR_ACTION_CLOSE); +} + +#if APPLY_FLTK_CHANGES +/*FLTK : remove this useless forward declaration */ +#else +struct libdecor_plugin * +libdecor_plugin_new(struct libdecor *context); +#endif + +static void +buffer_free(struct buffer *buffer); + +static void +draw_border_component(struct libdecor_frame_gtk *frame_gtk, + struct border_component *border_component, + enum component component); + +static void +send_cursor(struct seat *seat); + +static bool +update_local_cursor(struct seat *seat); + +static void +libdecor_plugin_gtk_destroy(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_gtk *plugin_gtk = + (struct libdecor_plugin_gtk *) plugin; + struct seat *seat, *seat_tmp; + struct output *output, *output_tmp; + struct libdecor_frame_gtk *frame, *frame_tmp; + + if (plugin_gtk->globals_callback) + wl_callback_destroy(plugin_gtk->globals_callback); + if (plugin_gtk->globals_callback_shm) + wl_callback_destroy(plugin_gtk->globals_callback_shm); + if (plugin_gtk->shm_callback) + wl_callback_destroy(plugin_gtk->shm_callback); + wl_registry_destroy(plugin_gtk->wl_registry); + + wl_list_for_each_safe(seat, seat_tmp, &plugin_gtk->seat_list, link) { + struct cursor_output *cursor_output, *tmp; + + if (seat->wl_pointer) + wl_pointer_destroy(seat->wl_pointer); + if (seat->cursor_surface) + wl_surface_destroy(seat->cursor_surface); + wl_seat_destroy(seat->wl_seat); + if (seat->cursor_theme) + wl_cursor_theme_destroy(seat->cursor_theme); + + wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) { + wl_list_remove(&cursor_output->link); + free(cursor_output); + } + + free(seat); + } + + wl_list_for_each_safe(output, output_tmp, + &plugin_gtk->output_list, link) { + wl_output_destroy(output->wl_output); + free(output); + } + + wl_list_for_each_safe(frame, frame_tmp, + &plugin_gtk->visible_frame_list, link) { + wl_list_remove(&frame->link); + } + + free(plugin_gtk->cursor_theme_name); + + wl_shm_destroy(plugin_gtk->wl_shm); + + wl_compositor_destroy(plugin_gtk->wl_compositor); + wl_subcompositor_destroy(plugin_gtk->wl_subcompositor); + + g_object_unref(plugin_gtk->app); +// free(plugin_gtk->gtk_theme_name); + + free(plugin_gtk); +} + +static struct libdecor_frame_gtk * +libdecor_frame_gtk_new(struct libdecor_plugin_gtk *plugin_gtk) +{ + struct libdecor_frame_gtk *frame_gtk = zalloc(sizeof *frame_gtk); + cairo_t *cr; + + static const int size = 128; + static const int boundary = 32; + + frame_gtk->plugin_gtk = plugin_gtk; + frame_gtk->shadow_blur = cairo_image_surface_create( + CAIRO_FORMAT_ARGB32, size, size); + wl_list_insert(&plugin_gtk->visible_frame_list, &frame_gtk->link); + + cr = cairo_create(frame_gtk->shadow_blur); + cairo_set_operator(cr, CAIRO_OPERATOR_OVER); + cairo_set_source_rgba(cr, 0, 0, 0, 1); + cairo_rectangle(cr, boundary, boundary, size-2*boundary, size-2*boundary); + cairo_fill(cr); + cairo_destroy(cr); + blur_surface(frame_gtk->shadow_blur, 64); + + return frame_gtk; +} + +static int +libdecor_plugin_gtk_get_fd(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_gtk *plugin_gtk = + (struct libdecor_plugin_gtk *) plugin; + struct wl_display *wl_display = + libdecor_get_wl_display(plugin_gtk->context); + + return wl_display_get_fd(wl_display); +} + +static int +libdecor_plugin_gtk_dispatch(struct libdecor_plugin *plugin, + int timeout) +{ + struct libdecor_plugin_gtk *plugin_gtk = + (struct libdecor_plugin_gtk *) plugin; + struct wl_display *wl_display = + libdecor_get_wl_display(plugin_gtk->context); + struct pollfd fds[1]; + int ret; + int dispatch_count = 0; + + while (g_main_context_pending(NULL)) { + g_main_context_iteration(NULL, timeout > 0); + } + + while (wl_display_prepare_read(wl_display) != 0) + dispatch_count += wl_display_dispatch_pending(wl_display); + + if (wl_display_flush(wl_display) < 0 && + errno != EAGAIN) { + wl_display_cancel_read(wl_display); + return -errno; + } + + fds[0] = (struct pollfd) { wl_display_get_fd(wl_display), POLLIN }; + + ret = poll(fds, ARRAY_SIZE (fds), timeout); + if (ret > 0) { + if (fds[0].revents & POLLIN) { + wl_display_read_events(wl_display); + dispatch_count += wl_display_dispatch_pending(wl_display); + return dispatch_count; + } else { + wl_display_cancel_read(wl_display); + return dispatch_count; + } + } else if (ret == 0) { + wl_display_cancel_read(wl_display); + return dispatch_count; + } else { + wl_display_cancel_read(wl_display); + return -errno; + } +} + +static struct libdecor_frame * +libdecor_plugin_gtk_frame_new(struct libdecor_plugin *plugin) +{ + struct libdecor_plugin_gtk *plugin_gtk = + (struct libdecor_plugin_gtk *) plugin; + struct libdecor_frame_gtk *frame_gtk; + + frame_gtk = libdecor_frame_gtk_new(plugin_gtk); + + return &frame_gtk->frame; +} + +static int +create_anonymous_file(off_t size) +{ + int ret; + + int fd; + + fd = memfd_create("libdecor-gtk", MFD_CLOEXEC | MFD_ALLOW_SEALING); + + if (fd < 0) + return -1; + + 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; + return -1; + } + + return fd; +} + +static void +toggle_maximized(struct libdecor_frame *const frame) +{ + if (!resizable((struct libdecor_frame_gtk *)frame)) + return; + + if (!(libdecor_frame_get_window_state(frame) & + LIBDECOR_WINDOW_STATE_MAXIMIZED)) + libdecor_frame_set_maximized(frame); + else + libdecor_frame_unset_maximized(frame); +} + +static void +buffer_release(void *user_data, + struct wl_buffer *wl_buffer) +{ + struct buffer *buffer = user_data; + + if (buffer->is_detached) + buffer_free(buffer); + else + buffer->in_use = false; +} + +static const struct wl_buffer_listener buffer_listener = { + buffer_release +}; + +static struct buffer * +create_shm_buffer(struct libdecor_plugin_gtk *plugin_gtk, + int width, + int height, + bool opaque, + int scale) +{ + struct wl_shm_pool *pool; + int fd, size, buffer_width, buffer_height, stride; + void *data; + struct buffer *buffer; + enum wl_shm_format buf_fmt; + + buffer_width = width * scale; + buffer_height = height * scale; + stride = buffer_width * 4; + size = stride * buffer_height; + + fd = create_anonymous_file(size); + if (fd < 0) { + fprintf(stderr, "creating a buffer file for %d B failed: %s\n", + size, strerror(errno)); + return NULL; + } + + data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (data == MAP_FAILED) { + fprintf(stderr, "mmap failed: %s\n", strerror(errno)); + close(fd); + return NULL; + } + + buf_fmt = opaque ? WL_SHM_FORMAT_XRGB8888 : WL_SHM_FORMAT_ARGB8888; + + pool = wl_shm_create_pool(plugin_gtk->wl_shm, fd, size); + buffer = zalloc(sizeof *buffer); + buffer->wl_buffer = wl_shm_pool_create_buffer(pool, 0, + buffer_width, buffer_height, + stride, + buf_fmt); + wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer); + wl_shm_pool_destroy(pool); + close(fd); + + buffer->data = data; + buffer->data_size = size; + buffer->width = width; + buffer->height = height; + buffer->scale = scale; + buffer->buffer_width = buffer_width; + buffer->buffer_height = buffer_height; + + return buffer; +} + +static void +buffer_free(struct buffer *buffer) +{ + if (buffer->wl_buffer) { + wl_buffer_destroy(buffer->wl_buffer); + munmap(buffer->data, buffer->data_size); + buffer->wl_buffer = NULL; + buffer->in_use = false; + } + free(buffer); +} + +static void +free_border_component(struct border_component *border_component) +{ + struct surface_output *surface_output, *surface_output_tmp; + + if (border_component->wl_surface) { + wl_subsurface_destroy(border_component->wl_subsurface); + border_component->wl_subsurface = NULL; + wl_surface_destroy(border_component->wl_surface); + border_component->wl_surface = NULL; + } + if (border_component->buffer) { + buffer_free(border_component->buffer); + border_component->buffer = NULL; + } + if (border_component->output_list.next != NULL) { + wl_list_for_each_safe(surface_output, surface_output_tmp, + &border_component->output_list, link) { + wl_list_remove(&surface_output->link); + free(surface_output); + } + } +} + +static void +libdecor_plugin_gtk_frame_free(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + +#if APPLY_FLTK_CHANGES + if (GTK_IS_WIDGET(frame_gtk->header)) +#endif + gtk_widget_destroy(frame_gtk->header); +#if APPLY_FLTK_CHANGES + if (GTK_IS_WIDGET(frame_gtk->window)) +#endif + gtk_widget_destroy(frame_gtk->window); + + free_border_component(&frame_gtk->headerbar); + free_border_component(&frame_gtk->shadow); + frame_gtk->shadow_showing = false; + if (frame_gtk->shadow_blur != NULL) { + cairo_surface_destroy(frame_gtk->shadow_blur); + frame_gtk->shadow_blur = NULL; + } + + free(frame_gtk->title); + + frame_gtk->decoration_type = DECORATION_TYPE_NONE; + + if (frame_gtk->link.next != NULL) + wl_list_remove(&frame_gtk->link); +} + +static bool +is_border_surfaces_showing(struct libdecor_frame_gtk *frame_gtk) +{ + return frame_gtk->shadow_showing; +} + +static void +hide_border_component(struct border_component *border_component) +{ + if (!border_component->wl_surface) + return; + + wl_surface_attach(border_component->wl_surface, NULL, 0, 0); + wl_surface_commit(border_component->wl_surface); +} + +static void +hide_border_surfaces(struct libdecor_frame_gtk *frame_gtk) +{ + hide_border_component(&frame_gtk->shadow); + frame_gtk->shadow_showing = false; +} + +static struct border_component * +get_component_for_surface(struct libdecor_frame_gtk *frame_gtk, + struct wl_surface *surface) +{ + if (frame_gtk->shadow.wl_surface == surface) + return &frame_gtk->shadow; + if (frame_gtk->headerbar.wl_surface == surface) + return &frame_gtk->headerbar; + return NULL; +} + +static bool +redraw_scale(struct libdecor_frame_gtk *frame_gtk, + struct border_component *cmpnt) +{ + struct surface_output *surface_output; + int scale = 1; + + if (cmpnt->wl_surface == NULL) + return false; + + wl_list_for_each(surface_output, &cmpnt->output_list, link) { + scale = MAX(scale, surface_output->output->scale); + } + if (scale != cmpnt->scale) { + cmpnt->scale = scale; + if ((cmpnt->type != SHADOW) || is_border_surfaces_showing(frame_gtk)) { + draw_border_component(frame_gtk, cmpnt, cmpnt->type); + return true; + } + } + return false; +} + +static bool +add_surface_output(struct libdecor_plugin_gtk *plugin_gtk, + struct wl_output *wl_output, + struct wl_list *list) +{ + struct output *output; + struct surface_output *surface_output; + + if (!own_output(wl_output)) + return false; + + output = wl_output_get_user_data(wl_output); + + if (output == NULL) + return false; + + surface_output = zalloc(sizeof *surface_output); + surface_output->output = output; + wl_list_insert(list, &surface_output->link); + return true; +} + +static void +surface_enter(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct libdecor_frame_gtk *frame_gtk = data; + struct border_component *cmpnt; + + if (!(own_surface(wl_surface) && own_output(wl_output))) + return; + + cmpnt = get_component_for_surface(frame_gtk, wl_surface); + if (cmpnt == NULL) + return; + + if (!add_surface_output(frame_gtk->plugin_gtk, wl_output, + &cmpnt->output_list)) + return; + + if (redraw_scale(frame_gtk, cmpnt)) + libdecor_frame_toplevel_commit(&frame_gtk->frame); +} + +static bool +remove_surface_output(struct wl_list *list, struct wl_output *wl_output) +{ + struct surface_output *surface_output; + wl_list_for_each(surface_output, list, link) { + if (surface_output->output->wl_output == wl_output) { + wl_list_remove(&surface_output->link); + free(surface_output); + return true; + } + } + return false; +} + +static void +surface_leave(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct libdecor_frame_gtk *frame_gtk = data; + struct border_component *cmpnt; + + if (!(own_surface(wl_surface) && own_output(wl_output))) + return; + + cmpnt = get_component_for_surface(frame_gtk, wl_surface); + if (cmpnt == NULL) + return; + + if (!remove_surface_output(&cmpnt->output_list, wl_output)) + return; + + if (redraw_scale(frame_gtk, cmpnt)) + libdecor_frame_toplevel_commit(&frame_gtk->frame); +} + +static struct wl_surface_listener surface_listener = { + surface_enter, + surface_leave, +}; + +static void +create_surface_subsurface_pair(struct libdecor_frame_gtk *frame_gtk, + struct wl_surface **out_wl_surface, + struct wl_subsurface **out_wl_subsurface) +{ + struct libdecor_plugin_gtk *plugin_gtk = frame_gtk->plugin_gtk; + struct libdecor_frame *frame = &frame_gtk->frame; + struct wl_compositor *wl_compositor = plugin_gtk->wl_compositor; + struct wl_subcompositor *wl_subcompositor = plugin_gtk->wl_subcompositor; + struct wl_surface *wl_surface; + struct wl_surface *parent; + struct wl_subsurface *wl_subsurface; + + wl_surface = wl_compositor_create_surface(wl_compositor); + wl_proxy_set_tag((struct wl_proxy *) wl_surface, + &libdecor_gtk_proxy_tag); + + parent = libdecor_frame_get_wl_surface(frame); + wl_subsurface = wl_subcompositor_get_subsurface(wl_subcompositor, + wl_surface, + parent); + + *out_wl_surface = wl_surface; + *out_wl_subsurface = wl_subsurface; +} + +static void +ensure_component(struct libdecor_frame_gtk *frame_gtk, + struct border_component *cmpnt) +{ + if (!cmpnt->wl_surface) { + wl_list_init(&cmpnt->output_list); + cmpnt->scale = 1; + create_surface_subsurface_pair(frame_gtk, + &cmpnt->wl_surface, + &cmpnt->wl_subsurface); + wl_surface_add_listener(cmpnt->wl_surface, &surface_listener, + frame_gtk); + } +} + +static void +ensure_border_surfaces(struct libdecor_frame_gtk *frame_gtk) +{ + frame_gtk->shadow.type = SHADOW; + frame_gtk->shadow.opaque = false; + ensure_component(frame_gtk, &frame_gtk->shadow); +} + +static void +ensure_title_bar_surfaces(struct libdecor_frame_gtk *frame_gtk) +{ + GtkStyleContext *context_hdr; + + frame_gtk->headerbar.type = HEADER; + frame_gtk->headerbar.opaque = false; + ensure_component(frame_gtk, &frame_gtk->headerbar); + + /* create an offscreen window with a header bar */ + /* TODO: This should only be done once at frame consutrction, but then + * the window and headerbar would not change style (e.g. backdrop) + * after construction. So we just destroy and re-create them. + */ +#if APPLY_FLTK_CHANGES + if (GTK_IS_WIDGET(frame_gtk->header)) +#else + if (frame_gtk->header) +#endif + gtk_widget_destroy(frame_gtk->header); +#if APPLY_FLTK_CHANGES + if (GTK_IS_WIDGET(frame_gtk->window)) +#else + if (frame_gtk->window) +#endif + gtk_widget_destroy(frame_gtk->window); + frame_gtk->window = gtk_offscreen_window_new(); + frame_gtk->header = gtk_header_bar_new(); + + g_object_get(gtk_widget_get_settings(frame_gtk->window), + "gtk-double-click-time", + &frame_gtk->plugin_gtk->double_click_time_ms, NULL); + /* set as "default" decoration */ + g_object_set(frame_gtk->header, + "title", libdecor_frame_get_title(&frame_gtk->frame), + "has-subtitle", FALSE, + "show-close-button", TRUE, + NULL); + + context_hdr = gtk_widget_get_style_context(frame_gtk->header); + gtk_style_context_add_class(context_hdr, GTK_STYLE_CLASS_TITLEBAR); + gtk_style_context_add_class(context_hdr, "default-decoration"); + + gtk_window_set_titlebar(GTK_WINDOW(frame_gtk->window), frame_gtk->header); + gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(frame_gtk->header), TRUE); + + gtk_window_set_resizable(GTK_WINDOW(frame_gtk->window), resizable(frame_gtk)); +} + +static void +calculate_component_size(struct libdecor_frame_gtk *frame_gtk, + enum component component, + int *component_x, + int *component_y, + int *component_width, + int *component_height) +{ + struct libdecor_frame *frame = &frame_gtk->frame; + int content_width, content_height; + + content_width = libdecor_frame_get_content_width(frame); + content_height = libdecor_frame_get_content_height(frame); + +#if APPLY_FLTK_CHANGES + const int title_height = GTK_IS_WIDGET(frame_gtk->header) ? gtk_widget_get_allocated_height(frame_gtk->header) : 0; +#else + const int title_height = frame_gtk->header ? gtk_widget_get_allocated_height(frame_gtk->header) : 0; +#endif + + switch (component) { + case NONE: + *component_width = 0; + *component_height = 0; + return; + case SHADOW: + *component_x = -(int)SHADOW_MARGIN; + *component_y = -(int)(SHADOW_MARGIN+title_height); + *component_width = content_width + 2 * SHADOW_MARGIN; + *component_height = content_height + + 2 * SHADOW_MARGIN + + title_height; + return; + case HEADER: + *component_x = 0; +#if APPLY_FLTK_CHANGES + *component_y = -title_height; +#else + *component_y = -gtk_widget_get_allocated_height(frame_gtk->header); +#endif + *component_width = gtk_widget_get_allocated_width(frame_gtk->header); +#if APPLY_FLTK_CHANGES + *component_height = title_height; +#else + *component_height = gtk_widget_get_allocated_height(frame_gtk->header); +#endif + return; + } + + abort(); +} + +static void +array_append(enum header_element **array, size_t *n, enum header_element item) +{ + (*n)++; + *array = realloc(*array, (*n) * sizeof (enum header_element)); + (*array)[(*n)-1] = item; +} + +static void +draw_header(struct libdecor_frame_gtk *frame_gtk, + cairo_t *cr, + cairo_surface_t *surface) +{ + { + /* background */ + GtkAllocation allocation; + GtkStyleContext* style; +#if APPLY_FLTK_CHANGES + if (!frame_gtk->header) return; // was deleted +#endif + gtk_widget_get_allocation(GTK_WIDGET(frame_gtk->header), &allocation); + style = gtk_widget_get_style_context(frame_gtk->header); + gtk_render_background(style, cr, allocation.x, allocation.y, allocation.width, allocation.height); + } + + { + /* title */ + GtkWidget *label; + GtkAllocation allocation; + cairo_surface_t *label_surface = NULL; + cairo_t *cr; + + label = find_widget_by_type(frame_gtk->header, HDR_TITLE).widget; + gtk_widget_get_allocation(find_widget_by_type(frame_gtk->header, HDR_TITLE).widget, &allocation); + + /* create subsection in which to draw label */ + label_surface = cairo_surface_create_for_rectangle( + surface, + allocation.x, allocation.y, + allocation.width, allocation.height); + cr = cairo_create(label_surface); + gtk_widget_draw(label, cr); + cairo_destroy(cr); + cairo_surface_destroy(label_surface); + } + + { + /* buttons */ + enum libdecor_window_state window_state; + enum header_element *buttons = NULL; + size_t nbuttons = 0; + + window_state = libdecor_frame_get_window_state( + (struct libdecor_frame*)frame_gtk); + + /* set buttons by capability */ + if (minimizable(frame_gtk)) + array_append(&buttons, &nbuttons, HDR_MIN); + if (resizable(frame_gtk)) + array_append(&buttons, &nbuttons, HDR_MAX); + if (closeable(frame_gtk)) + array_append(&buttons, &nbuttons, HDR_CLOSE); + + for (size_t i=0; i<nbuttons; i++) { + struct header_element_data elem; + GtkWidget *button; + GtkStyleContext* button_style; + GtkStateFlags style_state; + + elem = find_widget_by_type(frame_gtk->header, buttons[i]); + button = elem.widget; + if (!button) + continue; + button_style = gtk_widget_get_style_context(button); + style_state = elem.state; + + /* change style based on window state and focus */ + if (!(window_state & LIBDECOR_WINDOW_STATE_ACTIVE)) { + style_state |= GTK_STATE_FLAG_BACKDROP; + } + if (frame_gtk->hdr_focus.widget == button) { + style_state |= GTK_STATE_FLAG_PRELIGHT; + if (frame_gtk->hdr_focus.state & GTK_STATE_FLAG_ACTIVE) { + style_state |= GTK_STATE_FLAG_ACTIVE; + } + } + + GtkAllocation allocation; + { + /* background */ + gtk_widget_get_clip(button, &allocation); + +#if 0 + // DBG + switch (i) { + case 0: cairo_set_source_rgb(cr, 1, 0, 0); break; + case 1: cairo_set_source_rgb(cr, 0, 1, 0); break; + case 2: cairo_set_source_rgb(cr, 0, 0, 1); break; + } + cairo_set_line_width(cr, 1); + cairo_rectangle(cr, allocation.x, allocation.y, allocation.width, allocation.height); + cairo_stroke_preserve(cr); +#endif + + gtk_style_context_save(button_style); + gtk_style_context_set_state(button_style, style_state); + gtk_render_background(button_style, cr, + allocation.x, allocation.y, + allocation.width, allocation.height); + gtk_render_frame(button_style, cr, + allocation.x, allocation.y, + allocation.width, allocation.height); + gtk_style_context_restore(button_style); + } + + { + /* symbol */ + gchar *icon_name; + int scale; + GtkWidget *icon_widget; + GtkAllocation allocation_icon; + GtkIconInfo* icon_info; + + switch (buttons[i]) { + case HDR_MIN: + icon_name = "window-minimize-symbolic"; + break; + case HDR_MAX: + icon_name = (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED) ? + "window-restore-symbolic" : + "window-maximize-symbolic"; + break; + case HDR_CLOSE: + icon_name = "window-close-symbolic"; + break; + default: + icon_name = NULL; + break; + } + + { + /* get scale */ + double sx, sy; + cairo_surface_get_device_scale(surface, &sx, &sy); + scale = (sx+sy) / 2.0; + } + + /* get original icon dimensions */ + icon_widget = gtk_bin_get_child(GTK_BIN(button)); + gtk_widget_get_allocation(icon_widget, &allocation_icon); + + { + /* icon info */ + gint icon_width, icon_height; + gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &icon_width, &icon_height); + icon_info = gtk_icon_theme_lookup_icon_for_scale( + gtk_icon_theme_get_default(), icon_name, + icon_width, scale, (GtkIconLookupFlags)0); + } + + { + /* icon pixel buffer*/ + gtk_style_context_save(button_style); + gtk_style_context_set_state(button_style, style_state); + GdkPixbuf* icon_pixbuf = gtk_icon_info_load_symbolic_for_context( + icon_info, button_style, NULL, NULL); + cairo_surface_t* icon_surface = + gdk_cairo_surface_create_from_pixbuf(icon_pixbuf, scale, NULL); + gtk_style_context_restore(button_style); + + gint width = 0, height = 0; + gtk_style_context_get(button_style, gtk_style_context_get_state(button_style), + "min-width", &width, "min-height", &height, NULL); + + gint iconWidth, iconHeight; + if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &iconWidth, &iconHeight)) { + iconWidth = 16; + iconHeight = 16; + } + if (width < iconWidth) width = iconWidth; + if (height < iconHeight) height = iconHeight; + + gint left = 0, top = 0, right = 0, bottom = 0; + GtkBorder border; + gtk_style_context_get_border(button_style, gtk_style_context_get_state(button_style), &border); + left += border.left; + right += border.right; + top += border.top; + bottom += border.bottom; + + GtkBorder padding; + gtk_style_context_get_padding(button_style, gtk_style_context_get_state(button_style), &padding); + left += padding.left; + right += padding.right; + top += padding.top; + bottom += padding.bottom; + + width += left + right; + height += top + bottom; + int iconXPosition = (width - iconWidth) / 2; + int iconYPosition = (height - iconHeight) / 2; + + gtk_render_icon_surface(gtk_widget_get_style_context(icon_widget), + cr, icon_surface, + allocation.x+iconXPosition, + allocation.y+iconYPosition); + cairo_paint(cr); + cairo_surface_destroy(icon_surface); + g_object_unref(icon_pixbuf); + } + } /* symbol */ + } /* loop buttons */ + free(buttons); + } +} + +static void +draw_component_content(struct libdecor_frame_gtk *frame_gtk, + struct buffer *buffer, + int component_width, + int component_height, + enum component component) +{ + cairo_surface_t *surface; + cairo_t *cr; + + /* clear buffer */ + memset(buffer->data, 0, buffer->data_size); + + surface = cairo_image_surface_create_for_data( + buffer->data, CAIRO_FORMAT_ARGB32, + buffer->buffer_width, buffer->buffer_height, + cairo_format_stride_for_width( + CAIRO_FORMAT_ARGB32, + buffer->buffer_width) + ); + + cr = cairo_create(surface); + + cairo_surface_set_device_scale(surface, buffer->scale, buffer->scale); + + /* background */ + switch (component) { + case NONE: + break; + case SHADOW: + render_shadow(cr, + frame_gtk->shadow_blur, + -(int)SHADOW_MARGIN/2, + -(int)SHADOW_MARGIN/2, + buffer->width + SHADOW_MARGIN, + buffer->height + SHADOW_MARGIN, + 64, + 64); + break; + case HEADER: + draw_header(frame_gtk, cr, surface); + break; + } + + /* mask the toplevel surface */ + if (component == SHADOW) { + int component_x, component_y, component_width, component_height; + calculate_component_size(frame_gtk, component, + &component_x, &component_y, + &component_width, &component_height); + cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR); + cairo_rectangle(cr, -component_x, -component_y, + libdecor_frame_get_content_width( + &frame_gtk->frame), + libdecor_frame_get_content_height( + &frame_gtk->frame)); + cairo_fill(cr); + } + + cairo_destroy(cr); + cairo_surface_destroy(surface); +} + +static void +set_component_input_region(struct libdecor_frame_gtk *frame_gtk, + struct border_component *border_component) +{ + if (border_component->type == SHADOW && frame_gtk->shadow_showing) { + struct wl_region *input_region; + int component_x; + int component_y; + int component_width; + int component_height; + + calculate_component_size(frame_gtk, border_component->type, + &component_x, &component_y, + &component_width, &component_height); + + /* + * the input region is the outer surface size minus the inner + * content size + */ + input_region = wl_compositor_create_region( + frame_gtk->plugin_gtk->wl_compositor); + wl_region_add(input_region, 0, 0, + component_width, component_height); + wl_region_subtract(input_region, -component_x, -component_y, + libdecor_frame_get_content_width(&frame_gtk->frame), + libdecor_frame_get_content_height(&frame_gtk->frame)); + wl_surface_set_input_region(border_component->wl_surface, + input_region); + wl_region_destroy(input_region); + } +} + +static void +draw_border_component(struct libdecor_frame_gtk *frame_gtk, + struct border_component *border_component, + enum component component) +{ + struct libdecor_plugin_gtk *plugin_gtk = frame_gtk->plugin_gtk; + struct buffer *old_buffer; + struct buffer *buffer = NULL; + int component_x; + int component_y; + int component_width; + int component_height; + int scale = border_component->scale; + + if (border_component->wl_surface == NULL) + return; + + calculate_component_size(frame_gtk, component, + &component_x, &component_y, + &component_width, &component_height); + + set_component_input_region(frame_gtk, border_component); + + old_buffer = border_component->buffer; + if (old_buffer) { + if (!old_buffer->in_use && + old_buffer->buffer_width == component_width * scale && + old_buffer->buffer_height == component_height * scale) { + buffer = old_buffer; + } else { + buffer_free(old_buffer); + border_component->buffer = NULL; + } + } + + if (!buffer) + buffer = create_shm_buffer(plugin_gtk, + component_width, + component_height, + border_component->opaque, + border_component->scale); + + draw_component_content(frame_gtk, buffer, + component_width, component_height, + component); + + wl_surface_attach(border_component->wl_surface, buffer->wl_buffer, 0, 0); + wl_surface_set_buffer_scale(border_component->wl_surface, buffer->scale); + buffer->in_use = true; + wl_surface_commit(border_component->wl_surface); + wl_surface_damage_buffer(border_component->wl_surface, 0, 0, + component_width * scale, + component_height * scale); + wl_subsurface_set_position(border_component->wl_subsurface, + component_x, component_y); + + border_component->buffer = buffer; +} + +static void +draw_border(struct libdecor_frame_gtk *frame_gtk) +{ + draw_border_component(frame_gtk, &frame_gtk->shadow, SHADOW); + frame_gtk->shadow_showing = true; +} + +static void +draw_title_bar(struct libdecor_frame_gtk *frame_gtk) +{ + GtkAllocation allocation = {0, 0, frame_gtk->content_width, 0}; + enum libdecor_window_state state; + GtkStyleContext *style; + + state = libdecor_frame_get_window_state((struct libdecor_frame*)frame_gtk); + style = gtk_widget_get_style_context(frame_gtk->window); + + if (!(state & LIBDECOR_WINDOW_STATE_ACTIVE)) { + gtk_widget_set_state_flags(frame_gtk->window, GTK_STATE_FLAG_BACKDROP, true); + } + else { + gtk_widget_unset_state_flags(frame_gtk->window, GTK_STATE_FLAG_BACKDROP); + } + + if (libdecor_frame_is_floating(&frame_gtk->frame)) { + gtk_style_context_remove_class(style, "maximized"); + } + else { + gtk_style_context_add_class(style, "maximized"); + } + + gtk_widget_show_all(frame_gtk->window); + + gtk_header_bar_set_title(GTK_HEADER_BAR(frame_gtk->header), libdecor_frame_get_title(&frame_gtk->frame)); + + /* set default height */ + gtk_widget_get_preferred_height(frame_gtk->header, NULL, &allocation.height); + +#if ! APPLY_FLTK_CHANGES + int pref_width; + gtk_widget_get_preferred_width(frame_gtk->header, NULL, &pref_width); + libdecor_frame_set_min_content_size(&frame_gtk->frame, pref_width, 1); + gtk_widget_size_allocate(frame_gtk->header, &allocation); +#else // new code for FLTK + int min_GTK_width = 15; + /* // Comment out to remove need for hiding (see below) warnings sent to stderr + // just enough space not to create: Gtk-WARNING **: Negative content width …… + min_GTK_width = 134; + if (!libdecor_frame_has_capability(&frame_gtk->frame, LIBDECOR_ACTION_FULLSCREEN)) + min_GTK_width = 100; // no maximize button, window can be narrower*/ + int current_min_w, current_min_h; + libdecor_frame_get_min_content_size(&frame_gtk->frame, ¤t_min_w, ¤t_min_h); + bool need_change = false; + if (current_min_w < min_GTK_width) { + current_min_w = min_GTK_width; + need_change = true; + } + if (current_min_h < 1) { + current_min_h = 1; + need_change = true; + } + if (need_change) { + libdecor_frame_set_min_content_size(&frame_gtk->frame, current_min_w, current_min_h); + if (!libdecor_frame_has_capability(&frame_gtk->frame, LIBDECOR_ACTION_FULLSCREEN)) { + libdecor_frame_set_max_content_size(&frame_gtk->frame, current_min_w, current_min_h); + } + } + int W = libdecor_frame_get_content_width(&frame_gtk->frame); + int H = libdecor_frame_get_content_height(&frame_gtk->frame); + need_change = false; + if (W < current_min_w) {W = current_min_w; need_change=true;} + if (H < current_min_h) {H = current_min_h; need_change=true;} + if (need_change) { + struct libdecor_state *libdecor_state = libdecor_state_new(W, H); + libdecor_frame_commit(&frame_gtk->frame, libdecor_state, NULL); + libdecor_state_free(libdecor_state); + return; + } + // hack to hide "Gtk-WARNING **: Negative content width" sent to stderr + static FILE *null_stderr = NULL; + if (!null_stderr) null_stderr = fopen("/dev/null", "w+"); + FILE *old_stderr = stderr; + stderr = null_stderr; + gtk_widget_size_allocate(frame_gtk->header, &allocation); // warnings are sent here + stderr = old_stderr; +#endif // end of new code for FLTK + draw_border_component(frame_gtk, &frame_gtk->headerbar, HEADER); +} + +static void +draw_decoration(struct libdecor_frame_gtk *frame_gtk) +{ + switch (frame_gtk->decoration_type) { + case DECORATION_TYPE_NONE: + if (frame_gtk->link.next != NULL) + wl_list_remove(&frame_gtk->link); + if (is_border_surfaces_showing(frame_gtk)) + hide_border_surfaces(frame_gtk); + hide_border_component(&frame_gtk->headerbar); + break; + case DECORATION_TYPE_ALL: + /* show borders */ + ensure_border_surfaces(frame_gtk); + draw_border(frame_gtk); + /* show title bar */ + ensure_title_bar_surfaces(frame_gtk); + draw_title_bar(frame_gtk); + /* link frame */ + if (frame_gtk->link.next == NULL) + wl_list_insert( + &frame_gtk->plugin_gtk->visible_frame_list, + &frame_gtk->link); + break; + case DECORATION_TYPE_TITLE_ONLY: + /* hide borders */ + if (is_border_surfaces_showing(frame_gtk)) + hide_border_surfaces(frame_gtk); + /* show title bar */ + ensure_title_bar_surfaces(frame_gtk); + draw_title_bar(frame_gtk); + /* link frame */ + if (frame_gtk->link.next == NULL) + wl_list_insert( + &frame_gtk->plugin_gtk->visible_frame_list, + &frame_gtk->link); + break; + } +} + +static void +set_window_geometry(struct libdecor_frame_gtk *frame_gtk) +{ + struct libdecor_frame *frame = &frame_gtk->frame; + int x = 0, y = 0, width = 0, height = 0; +#if APPLY_FLTK_CHANGES + const int title_height = GTK_IS_WIDGET(frame_gtk->header) ? gtk_widget_get_allocated_height(frame_gtk->header) : 0; +#else + const int title_height = gtk_widget_get_allocated_height(frame_gtk->header); +#endif + + switch (frame_gtk->decoration_type) { + case DECORATION_TYPE_NONE: + x = 0; + y = 0; + width = libdecor_frame_get_content_width(frame); + height = libdecor_frame_get_content_height(frame); + break; + case DECORATION_TYPE_ALL: + case DECORATION_TYPE_TITLE_ONLY: + x = 0; + y = -title_height; + width = libdecor_frame_get_content_width(frame); + height = libdecor_frame_get_content_height(frame) + title_height; + break; + } + + libdecor_frame_set_window_geometry(frame, x, y, width, height); +} + +static enum decoration_type +window_state_to_decoration_type(enum libdecor_window_state window_state) +{ + if (window_state & LIBDECOR_WINDOW_STATE_FULLSCREEN) + return DECORATION_TYPE_NONE; + else if (window_state & LIBDECOR_WINDOW_STATE_MAXIMIZED || + window_state & LIBDECOR_WINDOW_STATE_TILED_LEFT || + window_state & LIBDECOR_WINDOW_STATE_TILED_RIGHT || + window_state & LIBDECOR_WINDOW_STATE_TILED_TOP || + window_state & LIBDECOR_WINDOW_STATE_TILED_BOTTOM) + /* title bar, no shadows */ + return DECORATION_TYPE_TITLE_ONLY; + else + /* title bar, shadows */ + return DECORATION_TYPE_ALL; +} + +static void +libdecor_plugin_gtk_frame_commit(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + struct libdecor_configuration *configuration) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + enum libdecor_window_state old_window_state; + enum libdecor_window_state new_window_state; + int old_content_width, old_content_height; + int new_content_width, new_content_height; + enum decoration_type old_decoration_type; + enum decoration_type new_decoration_type; + + old_window_state = frame_gtk->window_state; + new_window_state = libdecor_frame_get_window_state(frame); + + old_content_width = frame_gtk->content_width; + old_content_height = frame_gtk->content_height; + new_content_width = libdecor_frame_get_content_width(frame); + new_content_height = libdecor_frame_get_content_height(frame); + + old_decoration_type = frame_gtk->decoration_type; + new_decoration_type = window_state_to_decoration_type(new_window_state); + + if (old_decoration_type == new_decoration_type && + old_content_width == new_content_width && + old_content_height == new_content_height && + old_window_state == new_window_state) + return; + + frame_gtk->content_width = new_content_width; + frame_gtk->content_height = new_content_height; + frame_gtk->window_state = new_window_state; + frame_gtk->decoration_type = new_decoration_type; + + draw_decoration(frame_gtk); + set_window_geometry(frame_gtk); + + /* set fixed window size */ + if (!resizable(frame_gtk)) { + libdecor_frame_set_min_content_size(frame, + frame_gtk->content_width, + frame_gtk->content_height); + libdecor_frame_set_max_content_size(frame, + frame_gtk->content_width, + frame_gtk->content_height); + } +} + +static void +libdecor_plugin_gtk_frame_property_changed(struct libdecor_plugin *plugin, + struct libdecor_frame *frame) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + bool redraw_needed = false; + const char *new_title; + + new_title = libdecor_frame_get_title(frame); +#if APPLY_FLTK_CHANGES + if (frame_gtk->title && !streq(frame_gtk->title, new_title)) { +#else + if (!streq(frame_gtk->title, new_title)) +#endif + redraw_needed = true; + free(frame_gtk->title); + if (new_title) + frame_gtk->title = strdup(new_title); + else + frame_gtk->title = NULL; +#if APPLY_FLTK_CHANGES + } +#endif + + if (frame_gtk->capabilities != libdecor_frame_get_capabilities(frame)) { + frame_gtk->capabilities = libdecor_frame_get_capabilities(frame); + redraw_needed = true; + } + + if (redraw_needed) { + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(frame); + } +} + +static void +libdecor_plugin_gtk_frame_translate_coordinate(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + int content_x, + int content_y, + int *frame_x, + int *frame_y) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + + *frame_x = content_x; + *frame_y = content_y; + +#if APPLY_FLTK_CHANGES + *frame_y += (frame_gtk->header ? gtk_widget_get_allocated_height(frame_gtk->header) : 0); +#else + *frame_y += gtk_widget_get_allocated_height(frame_gtk->header); +#endif +} + +static void +update_component_focus(struct libdecor_frame_gtk *frame_gtk, + struct wl_surface *surface, + struct seat *seat) +{ + static struct border_component *border_component; + static struct border_component *child_component; + static struct border_component *focus_component; + + border_component = get_component_for_surface(frame_gtk, surface); + + focus_component = border_component; + wl_list_for_each(child_component, &border_component->child_components, link) { + int component_x = 0, component_y = 0; + int component_width = 0, component_height = 0; + + calculate_component_size(frame_gtk, child_component->type, + &component_x, &component_y, + &component_width, &component_height); + if (seat->pointer_x >= component_x && + seat->pointer_x < component_x + component_width && + seat->pointer_y >= component_y && + seat->pointer_y < component_y + component_height) { + focus_component = child_component; + break; + } + } + + if (frame_gtk->grab) + frame_gtk->active = frame_gtk->grab; + else + frame_gtk->active = focus_component; + frame_gtk->focus = focus_component; + +} + +static void +sync_active_component(struct libdecor_frame_gtk *frame_gtk, + struct seat *seat) +{ + struct border_component *old_active; + + if (!seat->pointer_focus) + return; + + old_active = frame_gtk->active; + update_component_focus(frame_gtk, seat->pointer_focus, seat); + if (old_active != frame_gtk->active) { + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + + if (update_local_cursor(seat)) + send_cursor(seat); +} + +static void +synthesize_pointer_enter(struct seat *seat) +{ + struct wl_surface *surface; + struct libdecor_frame_gtk *frame_gtk; + + surface = seat->pointer_focus; + if (!surface) + return; + + frame_gtk = wl_surface_get_user_data(surface); + if (!frame_gtk) + return; + + update_component_focus(frame_gtk, seat->pointer_focus, seat); + frame_gtk->grab = NULL; + + /* update decorations */ + if (frame_gtk->active) { + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + + update_local_cursor(seat); + send_cursor(seat); +} + +static void +synthesize_pointer_leave(struct seat *seat) +{ + struct wl_surface *surface; + struct libdecor_frame_gtk *frame_gtk; + + surface = seat->pointer_focus; + if (!surface) + return; + + frame_gtk = wl_surface_get_user_data(surface); + if (!frame_gtk) + return; + + if (!frame_gtk->active) + return; + + frame_gtk->active = NULL; + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + update_local_cursor(seat); +} + +static void +libdecor_plugin_gtk_frame_popup_grab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + struct libdecor_plugin_gtk *plugin_gtk = frame_gtk->plugin_gtk; + struct seat *seat; + + wl_list_for_each(seat, &plugin_gtk->seat_list, link) { + if (streq(seat->name, seat_name)) { + if (seat->grabbed) { + fprintf(stderr, "libdecor-WARNING: Application " + "tried to grab seat twice\n"); + } + synthesize_pointer_leave(seat); + seat->grabbed = true; + return; + } + } + + fprintf(stderr, + "libdecor-WARNING: Application tried to grab unknown seat\n"); +} + +static void +libdecor_plugin_gtk_frame_popup_ungrab(struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + const char *seat_name) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + struct libdecor_plugin_gtk *plugin_gtk = frame_gtk->plugin_gtk; + struct seat *seat; + + wl_list_for_each(seat, &plugin_gtk->seat_list, link) { + if (streq(seat->name, seat_name)) { + if (!seat->grabbed) { + fprintf(stderr, "libdecor-WARNING: Application " + "tried to ungrab seat twice\n"); + } + seat->grabbed = false; + synthesize_pointer_enter(seat); + sync_active_component(frame_gtk, seat); + return; + } + } + + fprintf(stderr, + "libdecor-WARNING: Application tried to ungrab unknown seat\n"); +} + +static bool +libdecor_plugin_gtk_configuration_get_content_size( + struct libdecor_plugin *plugin, + struct libdecor_configuration *configuration, + struct libdecor_frame *frame, + int *content_width, + int *content_height) +{ + struct libdecor_frame_gtk *frame_gtk = + (struct libdecor_frame_gtk *) frame; + int win_width, win_height; + enum libdecor_window_state state; + + if (!libdecor_configuration_get_window_size(configuration, + &win_width, + &win_height)) + return false; + + if (!libdecor_configuration_get_window_state(configuration, &state)) + return false; +#if APPLY_FLTK_CHANGES + const int title_bar_height = GTK_IS_WIDGET(frame_gtk->header)? gtk_widget_get_allocated_height(frame_gtk->header) : 0; +#else + const int title_bar_height = gtk_widget_get_allocated_height(frame_gtk->header); +#endif + + switch (window_state_to_decoration_type(state)) { + case DECORATION_TYPE_NONE: + *content_width = win_width; + *content_height = win_height; + break; + case DECORATION_TYPE_ALL: + case DECORATION_TYPE_TITLE_ONLY: + *content_width = win_width; + *content_height = win_height - title_bar_height; + break; + } + + return true; +} + +static bool +libdecor_plugin_gtk_frame_get_window_size_for( + struct libdecor_plugin *plugin, + struct libdecor_frame *frame, + struct libdecor_state *state, + int *window_width, + int *window_height) +{ + enum libdecor_window_state window_state = + libdecor_state_get_window_state (state); + +#if APPLY_FLTK_CHANGES + struct libdecor_frame_gtk *frame_gtk = (struct libdecor_frame_gtk *)frame; + const int title_bar_height = (GTK_IS_WIDGET(frame_gtk->header) ? gtk_widget_get_allocated_height( + frame_gtk->header) : 0); +#else + const int title_bar_height = gtk_widget_get_allocated_height( + ((struct libdecor_frame_gtk *)frame)->header); +#endif + + switch (window_state_to_decoration_type(window_state)) { + case DECORATION_TYPE_NONE: + *window_width = libdecor_state_get_content_width(state); + *window_height = libdecor_state_get_content_height(state); + break; + case DECORATION_TYPE_ALL: + case DECORATION_TYPE_TITLE_ONLY: + *window_width = libdecor_state_get_content_width(state); + *window_height = + libdecor_state_get_content_height(state) + title_bar_height; + break; + } + + return true; +} + +static struct libdecor_plugin_interface gtk_plugin_iface = { + .destroy = libdecor_plugin_gtk_destroy, + .get_fd = libdecor_plugin_gtk_get_fd, + .dispatch = libdecor_plugin_gtk_dispatch, + + .frame_new = libdecor_plugin_gtk_frame_new, + .frame_free = libdecor_plugin_gtk_frame_free, + .frame_commit = libdecor_plugin_gtk_frame_commit, + .frame_property_changed = libdecor_plugin_gtk_frame_property_changed, + .frame_translate_coordinate = + libdecor_plugin_gtk_frame_translate_coordinate, + .frame_popup_grab = libdecor_plugin_gtk_frame_popup_grab, + .frame_popup_ungrab = libdecor_plugin_gtk_frame_popup_ungrab, + + .configuration_get_content_size = + libdecor_plugin_gtk_configuration_get_content_size, + .frame_get_window_size_for = + libdecor_plugin_gtk_frame_get_window_size_for, +}; + +static void +init_wl_compositor(struct libdecor_plugin_gtk *plugin_gtk, + uint32_t id, + uint32_t version) +{ + plugin_gtk->wl_compositor = + wl_registry_bind(plugin_gtk->wl_registry, + id, &wl_compositor_interface, + MIN(version, 4)); +} + +static void +init_wl_subcompositor(struct libdecor_plugin_gtk *plugin_gtk, + uint32_t id, + uint32_t version) +{ + plugin_gtk->wl_subcompositor = + wl_registry_bind(plugin_gtk->wl_registry, + id, &wl_subcompositor_interface, 1); +} + +static void +shm_format(void *user_data, + struct wl_shm *wl_shm, + uint32_t format) +{ + struct libdecor_plugin_gtk *plugin_gtk = user_data; + + if (format == WL_SHM_FORMAT_ARGB8888) + plugin_gtk->has_argb = true; +} + +struct wl_shm_listener shm_listener = { + shm_format +}; + +static void +shm_callback(void *user_data, + struct wl_callback *callback, + uint32_t time) +{ + struct libdecor_plugin_gtk *plugin_gtk = user_data; + struct libdecor *context = plugin_gtk->context; + + wl_callback_destroy(callback); + plugin_gtk->globals_callback_shm = NULL; + + if (!plugin_gtk->has_argb) { + libdecor_notify_plugin_error( + context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + "Compositor is missing required shm format"); + return; + } + + libdecor_notify_plugin_ready(context); +} + +static const struct wl_callback_listener shm_callback_listener = { + shm_callback +}; + +static void +init_wl_shm(struct libdecor_plugin_gtk *plugin_gtk, + uint32_t id, + uint32_t version) +{ + struct libdecor *context = plugin_gtk->context; + struct wl_display *wl_display = libdecor_get_wl_display(context); + + plugin_gtk->wl_shm = + wl_registry_bind(plugin_gtk->wl_registry, + id, &wl_shm_interface, 1); + wl_shm_add_listener(plugin_gtk->wl_shm, &shm_listener, plugin_gtk); + + plugin_gtk->globals_callback_shm = wl_display_sync(wl_display); + wl_callback_add_listener(plugin_gtk->globals_callback_shm, + &shm_callback_listener, + plugin_gtk); +} + +static void +cursor_surface_enter(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct seat *seat = data; + + if(own_output(wl_output)) { + struct cursor_output *cursor_output; + cursor_output = zalloc(sizeof *cursor_output); + cursor_output->output = wl_output_get_user_data(wl_output); + wl_list_insert(&seat->cursor_outputs, &cursor_output->link); + if (update_local_cursor(seat)) + send_cursor(seat); + } +} + +static void +cursor_surface_leave(void *data, + struct wl_surface *wl_surface, + struct wl_output *wl_output) +{ + struct seat *seat = data; + + if(own_output(wl_output)) { + struct cursor_output *cursor_output, *tmp; + wl_list_for_each_safe(cursor_output, tmp, &seat->cursor_outputs, link) { + if (cursor_output->output->wl_output == wl_output) { + wl_list_remove(&cursor_output->link); + free(cursor_output); + } + } + + if (update_local_cursor(seat)) + send_cursor(seat); + } +} + +static struct wl_surface_listener cursor_surface_listener = { + cursor_surface_enter, + cursor_surface_leave, +}; + +static void +ensure_cursor_surface(struct seat *seat) +{ + struct wl_compositor *wl_compositor = seat->plugin_gtk->wl_compositor; + + if (seat->cursor_surface) + return; + + seat->cursor_surface = wl_compositor_create_surface(wl_compositor); + wl_surface_add_listener(seat->cursor_surface, + &cursor_surface_listener, seat); +} + +static bool +ensure_cursor_theme(struct seat *seat) +{ + struct libdecor_plugin_gtk *plugin_gtk = seat->plugin_gtk; + int scale = 1; + struct wl_cursor_theme *theme; + struct cursor_output *cursor_output; + + wl_list_for_each(cursor_output, &seat->cursor_outputs, link) { + scale = MAX(scale, cursor_output->output->scale); + } + + if (seat->cursor_theme && seat->cursor_scale == scale) + return false; + + seat->cursor_scale = scale; + theme = wl_cursor_theme_load(plugin_gtk->cursor_theme_name, + plugin_gtk->cursor_size * scale, + plugin_gtk->wl_shm); + if (theme == NULL) + return false; + + if (seat->cursor_theme) + wl_cursor_theme_destroy(seat->cursor_theme); + + seat->cursor_theme = theme; + + for (unsigned int i = 0; i < ARRAY_LENGTH(cursor_names); i++) { + seat->cursors[i] = wl_cursor_theme_get_cursor( + seat->cursor_theme, + cursor_names[i]); + } + + seat->cursor_left_ptr = wl_cursor_theme_get_cursor(seat->cursor_theme, + "left_ptr"); + seat->current_cursor = seat->cursor_left_ptr; + + return true; +} + +enum libdecor_resize_edge +component_edge(const struct border_component *cmpnt, + const int pointer_x, + const int pointer_y, + const int margin) +{ + const bool top = pointer_y < margin; + const bool bottom = pointer_y > (cmpnt->buffer->height - margin); + const bool left = pointer_x < margin; + const bool right = pointer_x > (cmpnt->buffer->width - margin); + + if (top) + if (left) + return LIBDECOR_RESIZE_EDGE_TOP_LEFT; + else if (right) + return LIBDECOR_RESIZE_EDGE_TOP_RIGHT; + else + return LIBDECOR_RESIZE_EDGE_TOP; + else if (bottom) + if (left) + return LIBDECOR_RESIZE_EDGE_BOTTOM_LEFT; + else if (right) + return LIBDECOR_RESIZE_EDGE_BOTTOM_RIGHT; + else + return LIBDECOR_RESIZE_EDGE_BOTTOM; + else if (left) + return LIBDECOR_RESIZE_EDGE_LEFT; + else if (right) + return LIBDECOR_RESIZE_EDGE_RIGHT; + else + return LIBDECOR_RESIZE_EDGE_NONE; +} + +static bool +update_local_cursor(struct seat *seat) +{ + if (!seat->pointer_focus) { + seat->current_cursor = seat->cursor_left_ptr; + return false; + } + + if (!own_surface(seat->pointer_focus)) + return false; + + struct libdecor_frame_gtk *frame_gtk = + wl_surface_get_user_data(seat->pointer_focus); + struct wl_cursor *wl_cursor = NULL; + + if (!frame_gtk || !frame_gtk->active) { + seat->current_cursor = seat->cursor_left_ptr; + return false; + } + + bool theme_updated = ensure_cursor_theme(seat); + + if (frame_gtk->active->type == SHADOW && + is_border_surfaces_showing(frame_gtk) && + resizable(frame_gtk)) { + enum libdecor_resize_edge edge; + edge = component_edge(frame_gtk->active, + seat->pointer_x, + seat->pointer_y, SHADOW_MARGIN); + + if (edge != LIBDECOR_RESIZE_EDGE_NONE) + wl_cursor = seat->cursors[edge - 1]; + } else { + wl_cursor = seat->cursor_left_ptr; + } + + if (seat->current_cursor != wl_cursor) { + seat->current_cursor = wl_cursor; + return true; + } + + return theme_updated; +} + +static void +send_cursor(struct seat *seat) +{ + struct wl_cursor_image *image; + struct wl_buffer *buffer; + + if (seat->pointer_focus == NULL || seat->current_cursor == NULL) + return; + + image = seat->current_cursor->images[0]; + buffer = wl_cursor_image_get_buffer(image); + wl_surface_attach(seat->cursor_surface, buffer, 0, 0); + wl_surface_set_buffer_scale(seat->cursor_surface, seat->cursor_scale); + wl_surface_damage_buffer(seat->cursor_surface, 0, 0, + image->width * seat->cursor_scale, + image->height * seat->cursor_scale); + wl_surface_commit(seat->cursor_surface); + wl_pointer_set_cursor(seat->wl_pointer, seat->serial, + seat->cursor_surface, + image->hotspot_x / seat->cursor_scale, + image->hotspot_y / seat->cursor_scale); +} + +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) +{ + if (!surface) + return; + + struct seat *seat = data; + struct libdecor_frame_gtk *frame_gtk; + + if (!own_surface(surface)) + return; + + frame_gtk = wl_surface_get_user_data(surface); + + ensure_cursor_surface(seat); + + seat->pointer_x = wl_fixed_to_int(surface_x); + seat->pointer_y = wl_fixed_to_int(surface_y); + seat->serial = serial; + seat->pointer_focus = surface; + + if (!frame_gtk) + return; + + frame_gtk->active = get_component_for_surface(frame_gtk, surface); + + /* update decorations */ + if (frame_gtk->active) { + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + + update_local_cursor(seat); + send_cursor(seat); +} + +static void +pointer_leave(void *data, + struct wl_pointer *wl_pointer, + uint32_t serial, + struct wl_surface *surface) +{ + if (!surface) + return; + + struct seat *seat = data; + struct libdecor_frame_gtk *frame_gtk; + + if (!own_surface(surface)) + return; + + frame_gtk = wl_surface_get_user_data(surface); + + seat->pointer_focus = NULL; + if (frame_gtk) { + frame_gtk->active = NULL; + frame_gtk->hdr_focus.widget = NULL; + draw_decoration(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + update_local_cursor(seat); + } +} + +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 = data; + struct libdecor_frame_gtk *frame_gtk; + + if (!seat->pointer_focus || !own_surface(seat->pointer_focus)) + return; + + seat->pointer_x = wl_fixed_to_int(surface_x); + seat->pointer_y = wl_fixed_to_int(surface_y); + if (update_local_cursor(seat)) + send_cursor(seat); + + frame_gtk = wl_surface_get_user_data(seat->pointer_focus); +#if APPLY_FLTK_CHANGES + if (!frame_gtk->header) return; +#endif + if (frame_gtk->active->type == HEADER) { + struct header_element_data new_focus = get_header_focus( + GTK_HEADER_BAR(frame_gtk->header), + seat->pointer_x, seat->pointer_y); + /* only update if widget change so that we keep the state */ + if (frame_gtk->hdr_focus.widget != new_focus.widget) { + frame_gtk->hdr_focus = new_focus; + } + frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_PRELIGHT; + /* redraw with updated button visuals */ + draw_title_bar(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + else { + frame_gtk->hdr_focus.type = HDR_NONE; + } +} + +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 = data; + struct libdecor_frame_gtk *frame_gtk; + + if (!seat->pointer_focus || !own_surface(seat->pointer_focus)) + return; + + frame_gtk = wl_surface_get_user_data(seat->pointer_focus); + if (!frame_gtk) + return; + + if (button == BTN_LEFT) { + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + enum libdecor_resize_edge edge = + LIBDECOR_RESIZE_EDGE_NONE; + switch (frame_gtk->active->type) { + case SHADOW: + edge = component_edge(frame_gtk->active, + seat->pointer_x, + seat->pointer_y, + SHADOW_MARGIN); + break; + case HEADER: + switch (frame_gtk->hdr_focus.type) { + case HDR_MIN: + case HDR_MAX: + case HDR_CLOSE: + frame_gtk->hdr_focus.state |= GTK_STATE_FLAG_ACTIVE; + draw_title_bar(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + break; + default: + if (time-seat->pointer_button_time_stamp < + (uint32_t)frame_gtk->plugin_gtk->double_click_time_ms) { + toggle_maximized(&frame_gtk->frame); + } + else if (moveable(frame_gtk)) { + seat->pointer_button_time_stamp = time; + libdecor_frame_move(&frame_gtk->frame, + seat->wl_seat, + serial); + } + break; + } + break; + default: + break; + } + + if (edge != LIBDECOR_RESIZE_EDGE_NONE && + resizable(frame_gtk)) { + libdecor_frame_resize( + &frame_gtk->frame, + seat->wl_seat, + serial, + edge); + } + } + else if (state == WL_POINTER_BUTTON_STATE_RELEASED) { + switch (frame_gtk->active->type) { + case HEADER: + switch (frame_gtk->hdr_focus.type) { + case HDR_MIN: + if (minimizable(frame_gtk)) + libdecor_frame_set_minimized( + &frame_gtk->frame); + break; + case HDR_MAX: + toggle_maximized(&frame_gtk->frame); + break; + case HDR_CLOSE: + if (closeable(frame_gtk)) +#if APPLY_FLTK_CHANGES + {libdecor_frame_close(&frame_gtk->frame); + frame_gtk->header = NULL; // marks deletion + return; + } +#else + libdecor_frame_close(&frame_gtk->frame); +#endif + break; + default: + break; + } + /* unset active/clicked state once released */ + frame_gtk->hdr_focus.state &= ~GTK_STATE_FLAG_ACTIVE; + draw_title_bar(frame_gtk); + libdecor_frame_toplevel_commit(&frame_gtk->frame); + break; + default: + break; + } + } + } + else if (button == BTN_RIGHT && + state == WL_POINTER_BUTTON_STATE_PRESSED && + seat->pointer_focus == frame_gtk->headerbar.wl_surface) { +#if APPLY_FLTK_CHANGES + const int title_height = frame_gtk->header ? gtk_widget_get_allocated_height(frame_gtk->header) : 0; +#else + const int title_height = gtk_widget_get_allocated_height(frame_gtk->header); +#endif + libdecor_frame_show_window_menu(&frame_gtk->frame, + seat->wl_seat, + serial, + seat->pointer_x, + seat->pointer_y + -title_height); + } +} + +static void +pointer_axis(void *data, + struct wl_pointer *wl_pointer, + uint32_t time, + uint32_t axis, + wl_fixed_t value) +{ +} + +static struct wl_pointer_listener pointer_listener = { + pointer_enter, + pointer_leave, + pointer_motion, + pointer_button, + pointer_axis +}; + +static void +seat_capabilities(void *data, + struct wl_seat *wl_seat, + uint32_t capabilities) +{ + struct seat *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); + } else if (!(capabilities & WL_SEAT_CAPABILITY_POINTER) && + seat->wl_pointer) { + wl_pointer_release(seat->wl_pointer); + seat->wl_pointer = NULL; + } +} + +static void +seat_name(void *data, + struct wl_seat *wl_seat, + const char *name) +{ +#if APPLY_FLTK_CHANGES + struct seat *seat = (struct seat*)data; + seat->name = strdup(name); +#endif +} + +static struct wl_seat_listener seat_listener = { + seat_capabilities, + seat_name +}; + +static void +init_wl_seat(struct libdecor_plugin_gtk *plugin_gtk, + uint32_t id, + uint32_t version) +{ + struct seat *seat; + + if (version < 3) { + char *err_msg; + asprintf(&err_msg, + "%s version 3 required but only version %i is available\n", + wl_seat_interface.name, version); + libdecor_notify_plugin_error( + plugin_gtk->context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + err_msg); + free(err_msg); + } + + seat = zalloc(sizeof *seat); + seat->cursor_scale = 1; + seat->plugin_gtk = plugin_gtk; + wl_list_init(&seat->cursor_outputs); + wl_list_insert(&plugin_gtk->seat_list, &seat->link); + seat->wl_seat = + wl_registry_bind(plugin_gtk->wl_registry, + id, &wl_seat_interface, 3); + wl_seat_add_listener(seat->wl_seat, &seat_listener, seat); +} + +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) +{ +} + +static void +output_mode(void *data, + struct wl_output *wl_output, + uint32_t flags, + int32_t width, + int32_t height, + int32_t refresh) +{ +} + +static void +output_done(void *data, + struct wl_output *wl_output) +{ + struct output *output = data; + struct libdecor_frame_gtk *frame_gtk; + struct seat *seat; + + wl_list_for_each(frame_gtk, + &output->plugin_gtk->visible_frame_list, link) { + bool updated = false; + updated |= redraw_scale(frame_gtk, &frame_gtk->shadow); + if (updated) + libdecor_frame_toplevel_commit(&frame_gtk->frame); + } + wl_list_for_each(seat, &output->plugin_gtk->seat_list, link) { + if (update_local_cursor(seat)) + send_cursor(seat); + } +} + +static void +output_scale(void *data, + struct wl_output *wl_output, + int32_t factor) +{ + struct output *output = data; + + output->scale = factor; +} + +static struct wl_output_listener output_listener = { + output_geometry, + output_mode, + output_done, + output_scale +}; + +static void +init_wl_output(struct libdecor_plugin_gtk *plugin_gtk, + uint32_t id, + uint32_t version) +{ + struct output *output; + + if (version < 2) { + char *err_msg; + asprintf(&err_msg, + "%s version 2 required but only version %i is available\n", + wl_output_interface.name, version); + libdecor_notify_plugin_error( + plugin_gtk->context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + err_msg); + free(err_msg); + } + + output = zalloc(sizeof *output); + output->plugin_gtk = plugin_gtk; + wl_list_insert(&plugin_gtk->output_list, &output->link); + output->id = id; + output->wl_output = + wl_registry_bind(plugin_gtk->wl_registry, + id, &wl_output_interface, 2); + wl_proxy_set_tag((struct wl_proxy *) output->wl_output, + &libdecor_gtk_proxy_tag); + wl_output_add_listener(output->wl_output, &output_listener, output); +} + +static void +registry_handle_global(void *user_data, + struct wl_registry *wl_registry, + uint32_t id, + const char *interface, + uint32_t version) +{ + struct libdecor_plugin_gtk *plugin_gtk = user_data; + + if (strcmp(interface, "wl_compositor") == 0) + init_wl_compositor(plugin_gtk, id, version); + else if (strcmp(interface, "wl_subcompositor") == 0) + init_wl_subcompositor(plugin_gtk, id, version); + else if (strcmp(interface, "wl_shm") == 0) + init_wl_shm(plugin_gtk, id, version); + else if (strcmp(interface, "wl_seat") == 0) + init_wl_seat(plugin_gtk, id, version); + else if (strcmp(interface, "wl_output") == 0) + init_wl_output(plugin_gtk, id, version); +} + +static void +remove_surface_outputs(struct border_component *cmpnt, struct output *output) +{ + struct surface_output *surface_output; + wl_list_for_each(surface_output, &cmpnt->output_list, link) { + if (surface_output->output == output) { + wl_list_remove(&surface_output->link); + free(surface_output); + break; + } + } +} + +static void +output_removed(struct libdecor_plugin_gtk *plugin_gtk, + struct output *output) +{ + struct libdecor_frame_gtk *frame_gtk; + struct seat *seat; + + wl_list_for_each(frame_gtk, &plugin_gtk->visible_frame_list, link) { + remove_surface_outputs(&frame_gtk->shadow, output); + } + wl_list_for_each(seat, &plugin_gtk->seat_list, link) { + struct cursor_output *cursor_output; + wl_list_for_each(cursor_output, &seat->cursor_outputs, link) { + if (cursor_output->output == output) { + wl_list_remove(&cursor_output->link); + free(cursor_output); + } + } + } + + wl_list_remove(&output->link); + wl_output_destroy(output->wl_output); + free(output); +} + +static void +registry_handle_global_remove(void *user_data, + struct wl_registry *wl_registry, + uint32_t name) +{ + struct libdecor_plugin_gtk *plugin_gtk = user_data; + struct output *output; + + wl_list_for_each(output, &plugin_gtk->output_list, link) { + if (output->id == name) { + output_removed(plugin_gtk, output); + break; + } + } +} + +static const struct wl_registry_listener registry_listener = { + registry_handle_global, + registry_handle_global_remove +}; + +static bool +has_required_globals(struct libdecor_plugin_gtk *plugin_gtk) +{ + if (!plugin_gtk->wl_compositor) + return false; + if (!plugin_gtk->wl_subcompositor) + return false; + if (!plugin_gtk->wl_shm) + return false; + + return true; +} + +static void +globals_callback(void *user_data, + struct wl_callback *callback, + uint32_t time) +{ + struct libdecor_plugin_gtk *plugin_gtk = user_data; + + wl_callback_destroy(callback); + plugin_gtk->globals_callback = NULL; + + if (!has_required_globals(plugin_gtk)) { + struct libdecor *context = plugin_gtk->context; + + libdecor_notify_plugin_error( + context, + LIBDECOR_ERROR_COMPOSITOR_INCOMPATIBLE, + "Compositor is missing required globals"); + } +} + +static const struct wl_callback_listener globals_callback_listener = { + globals_callback +}; + +static void +on_activate(GtkApplication* app, gpointer user_data) { } + +#if APPLY_FLTK_CHANGES +/* FLTK: replace export by static which makes GTK plugin do as Cairo plugin does */ +static +#else +LIBDECOR_EXPORT +#endif +struct libdecor_plugin * +libdecor_plugin_new(struct libdecor *context) +{ + struct libdecor_plugin_gtk *plugin_gtk; + struct wl_display *wl_display; + + plugin_gtk = zalloc(sizeof *plugin_gtk); + libdecor_plugin_init(&plugin_gtk->plugin, + context, + >k_plugin_iface); + plugin_gtk->context = context; + + wl_list_init(&plugin_gtk->visible_frame_list); + wl_list_init(&plugin_gtk->seat_list); + wl_list_init(&plugin_gtk->output_list); + + /* fetch cursor theme and size*/ + if (!libdecor_get_cursor_settings(&plugin_gtk->cursor_theme_name, + &plugin_gtk->cursor_size)) { + plugin_gtk->cursor_theme_name = NULL; + plugin_gtk->cursor_size = 24; + } + + wl_display = libdecor_get_wl_display(context); + plugin_gtk->wl_registry = wl_display_get_registry(wl_display); + wl_registry_add_listener(plugin_gtk->wl_registry, + ®istry_listener, + plugin_gtk); + + plugin_gtk->globals_callback = wl_display_sync(wl_display); + wl_callback_add_listener(plugin_gtk->globals_callback, + &globals_callback_listener, + plugin_gtk); + + /* setup GTK context */ + plugin_gtk->app = gtk_application_new("org.libdecor.gtk", G_APPLICATION_NON_UNIQUE); + g_signal_connect (plugin_gtk->app, "activate", G_CALLBACK(on_activate), NULL); + g_application_run(G_APPLICATION(plugin_gtk->app), 0, NULL); + + return &plugin_gtk->plugin; +} + +static struct libdecor_plugin_priority priorities[] = { + { NULL, LIBDECOR_PLUGIN_PRIORITY_MEDIUM } +}; + +LIBDECOR_EXPORT const struct libdecor_plugin_description +libdecor_plugin_description = { + .api_version = LIBDECOR_PLUGIN_API_VERSION, + .capabilities = LIBDECOR_PLUGIN_CAPABILITY_BASE, + .description = "GTK plugin", + .priorities = priorities, + .constructor = libdecor_plugin_new, +}; diff --git a/libdecor/src/utils.h b/libdecor/src/utils.h new file mode 100644 index 000000000..7892793b6 --- /dev/null +++ b/libdecor/src/utils.h @@ -0,0 +1,48 @@ +/* + * Copyright © 2017 Red Hat Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef UTILS_H +#define UTILS_H + +#include <stdlib.h> + +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) + +#ifndef ARRAY_LENGTH +#define ARRAY_LENGTH(a) (sizeof (a) / sizeof (a)[0]) +#endif + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +static inline void * +zalloc(size_t size) +{ + return calloc(1, size); +} + +#endif /* UTILS_H */ diff --git a/makeinclude.in b/makeinclude.in index 5e8d1a09f..e17315543 100644 --- a/makeinclude.in +++ b/makeinclude.in @@ -1,7 +1,7 @@ # # Make include file for the Fast Light Tool Kit (FLTK). # -# Copyright 1998-2021 by Bill Spitzak and others. +# 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 @@ -24,7 +24,7 @@ FL_ABI_VERSION = @FL_ABI_VERSION@ FLTK_VERSION = @FLTK_VERSION@ -# FLTK configuration options: BUILD = { WIN | OSX | X11 | XFT } +# FLTK configuration options: BUILD = { WIN | OSX | X11 | XFT | WAYLAND } BUILD = @BUILD@ @@ -105,6 +105,9 @@ FLTKCAIROOPTION = @FLTKCAIROOPTION@ LINKSHARED = @DSOLINK@ @LINKSHARED@ $(IMAGELIBS) $(CAIROLIBS) IMAGELIBS = -L../lib @IMAGELIBS@ +# optional extra build step for libdecor: +LIBDECORDIR = @LIBDECORDIR@ + # image libraries to build... IMAGEDIRS = @JPEG@ @ZLIB@ @PNG@ CAIRODIR = @CAIRODIR@ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 61e30c3a3..0d3e05be4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -195,10 +195,11 @@ if (FLTK_USE_X11) # X11 (including APPLE with X11) set (DRIVER_FILES - drivers/Posix/Fl_Posix_System_Driver.cxx drivers/Posix/Fl_Posix_Printer_Driver.cxx drivers/X11/Fl_X11_Screen_Driver.cxx drivers/X11/Fl_X11_Window_Driver.cxx + drivers/Posix/Fl_Posix_System_Driver.cxx + drivers/Unix/Fl_Unix_System_Driver.cxx drivers/X11/Fl_X11_System_Driver.cxx drivers/Xlib/Fl_Xlib_Graphics_Driver.cxx drivers/Xlib/Fl_Xlib_Graphics_Driver_arci.cxx @@ -241,6 +242,25 @@ if (FLTK_USE_X11) drivers/Xlib/Fl_Font.H ) +elseif (OPTION_USE_WAYLAND) + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_CURRENT_BINARY_DIR}") + set (DRIVER_FILES + drivers/Posix/Fl_Posix_System_Driver.cxx + drivers/Posix/Fl_Posix_Printer_Driver.cxx + drivers/Wayland/Fl_Wayland_Screen_Driver.cxx + drivers/Wayland/Fl_Wayland_Window_Driver.cxx + drivers/Wayland/Fl_Wayland_System_Driver.cxx + drivers/Unix/Fl_Unix_System_Driver.cxx + drivers/Wayland/Fl_Wayland_Graphics_Driver.cxx + drivers/Wayland/Fl_Wayland_Copy_Surface_Driver.cxx + drivers/Wayland/Fl_Wayland_Image_Surface_Driver.cxx + drivers/Wayland/Fl_wayland.cxx + drivers/Cairo/Fl_Cairo_Graphics_Driver.cxx + Fl_Native_File_Chooser_FLTK.cxx + Fl_Native_File_Chooser_GTK.cxx + Fl_Native_File_Chooser_Kdialog.cxx + ) + elseif (APPLE) # Apple Quartz @@ -353,6 +373,8 @@ set (GL_DRIVER_FILES ) if (FLTK_USE_X11) set (GL_DRIVER_FILES ${GL_DRIVER_FILES} drivers/X11/Fl_X11_Gl_Window_Driver.cxx) +elseif (OPTION_USE_WAYLAND) + set (GL_DRIVER_FILES ${GL_DRIVER_FILES} drivers/Wayland/Fl_Wayland_Gl_Window_Driver.cxx) elseif (APPLE) set (GL_DRIVER_FILES ${GL_DRIVER_FILES} drivers/Cocoa/Fl_Cocoa_Gl_Window_Driver.cxx) elseif (WIN32) @@ -410,6 +432,40 @@ if (FLTK_USE_X11) endif (NOT USE_XFT) endif (FLTK_USE_X11) +if (OPTION_USE_WAYLAND) + pkg_check_modules(DBUS dbus-1) + include_directories(${DBUS_INCLUDE_DIRS}) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_CURRENT_BINARY_DIR} -fPIC -D_GNU_SOURCE -DHAS_DBUS") + if (OPTION_USE_SYSTEM_LIBDECOR) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_SYSTEM_LIBDECOR") + get_filename_component(PATH_TO_SHARED_LIBS ${HAVE_LIB_PANGO} DIRECTORY) + set (LIBDECOR_PLUGIN_DIR "\\\"${PATH_TO_SHARED_LIBS}/libdecor/plugins-1\\\" " ) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DLIBDECOR_PLUGIN_DIR=${LIBDECOR_PLUGIN_DIR} ") + set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DUSE_SYSTEM_LIBDECOR") + else() + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/../libdecor/src -DLIBDECOR_PLUGIN_API_VERSION=1 -DLIBDECOR_PLUGIN_DIR=\\\"/usr/local/lib\\\" ") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DUSE_SYSTEM_LIBDECOR=0") + endif (OPTION_USE_SYSTEM_LIBDECOR) + + if (GTK_FOUND) + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DHAVE_GTK") + endif (GTK_FOUND) + + list (APPEND CFILES + xutf8/keysym2Ucs.c + scandir_posix.c + ../libdecor/src/cursor-settings.c + ../libdecor/build/fl_libdecor-plugins.c + ) + if (NOT OPTION_USE_SYSTEM_LIBDECOR) + list (APPEND CFILES + ../libdecor/build/fl_libdecor.c + ../libdecor/src/os-compatibility.c + ../libdecor/src/plugins/cairo/libdecor-cairo-blur.c + ) + endif (NOT OPTION_USE_SYSTEM_LIBDECOR) +endif (OPTION_USE_WAYLAND) + if (WIN32) list (APPEND CFILES scandir_win32.c @@ -495,6 +551,52 @@ if (USE_XFT) endif (LIB_fontconfig) endif (USE_XFT) +if (OPTION_USE_WAYLAND) + add_custom_command( + OUTPUT xdg-shell-protocol.c xdg-shell-client-protocol.h + COMMAND wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-protocol.c + COMMAND wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-client-protocol.h + DEPENDS /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml + VERBATIM + ) + list (APPEND STATIC_FILES "xdg-shell-protocol.c") + list (APPEND SHARED_FILES "xdg-shell-protocol.c") + if (NOT OPTION_USE_SYSTEM_LIBDECOR) + add_custom_command( + OUTPUT xdg-decoration-protocol.c xdg-decoration-client-protocol.h + COMMAND wayland-scanner private-code /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml xdg-decoration-protocol.c + COMMAND wayland-scanner client-header /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml xdg-decoration-client-protocol.h + DEPENDS /usr/share/wayland-protocols/unstable/xdg-decoration/xdg-decoration-unstable-v1.xml + VERBATIM + ) + list (APPEND STATIC_FILES "xdg-decoration-protocol.c") + list (APPEND SHARED_FILES "xdg-decoration-protocol.c") + endif (NOT OPTION_USE_SYSTEM_LIBDECOR) + add_custom_command( + OUTPUT text-input-protocol.c text-input-client-protocol.h + COMMAND wayland-scanner private-code /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml text-input-protocol.c + COMMAND wayland-scanner client-header /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml text-input-client-protocol.h + DEPENDS /usr/share/wayland-protocols/unstable/text-input/text-input-unstable-v3.xml + VERBATIM + ) + list (APPEND STATIC_FILES "text-input-protocol.c") + list (APPEND SHARED_FILES "text-input-protocol.c") + + if (OPTION_USE_GL) + list (APPEND OPTIONAL_LIBS "-lwayland-egl -lEGL") + endif (OPTION_USE_GL) + if (OPTION_USE_SYSTEM_LIBDECOR) + list (APPEND OPTIONAL_LIBS "-ldecor-0") + endif (OPTION_USE_SYSTEM_LIBDECOR) + list (APPEND OPTIONAL_LIBS "-lwayland-cursor -lwayland-client -lxkbcommon -ldl -ldbus-1") + if (GTK_FOUND) + list (APPEND OPTIONAL_LIBS ${GTK_LDFLAGS} ) + endif (GTK_FOUND) + if (NOT OPTION_BUILD_SHARED_LIBS) + list (APPEND OPTIONAL_LIBS "-no-pie") + endif (NOT OPTION_BUILD_SHARED_LIBS) +endif (OPTION_USE_WAYLAND) + ####################################################################### FL_ADD_LIBRARY (fltk STATIC "${STATIC_FILES}") diff --git a/src/Fl_Menu.cxx b/src/Fl_Menu.cxx index 62fbd56ba..7b153d38c 100644 --- a/src/Fl_Menu.cxx +++ b/src/Fl_Menu.cxx @@ -22,6 +22,7 @@ #include <FL/Fl.H> #include "Fl_System_Driver.H" +#include "Fl_Window_Driver.H" #include <FL/Fl_Menu_Window.H> #include <FL/Fl_Menu_.H> #include <FL/fl_draw.H> @@ -116,10 +117,13 @@ public: // each vertical menu has one of these: class menuwindow : public Fl_Menu_Window { + friend class Fl_Window_Driver; + friend class Fl_Menu_Item; void draw(); void drawentry(const Fl_Menu_Item*, int i, int erase); int handle_part1(int); int handle_part2(int e, int ret); + static Fl_Window *parent_; public: menutitle* title; int handle(int); @@ -141,6 +145,12 @@ public: int is_inside(int x, int y); }; +Fl_Window *menuwindow::parent_ = NULL; + +Fl_Window *Fl_Window_Driver::menu_parent() { + return menuwindow::parent_; +} + extern char fl_draw_shortcut; /** @@ -293,7 +303,7 @@ menuwindow::menuwindow(const Fl_Menu_Item* m, int X, int Y, int Wp, int Hp, int scr_x, scr_y, scr_w, scr_h; int tx = X, ty = Y; - Fl::screen_work_area(scr_x, scr_y, scr_w, scr_h); + Fl_Window_Driver::driver(this)->menu_window_area(scr_x, scr_y, scr_w, scr_h); if (!right_edge || right_edge > scr_x+scr_w) right_edge = scr_x+scr_w; end(); @@ -439,14 +449,14 @@ void menuwindow::autoscroll(int n) { int Y = y()+Fl::box_dx(box())+2+n*itemheight; int xx, ww; - Fl::screen_work_area(xx, scr_y, ww, scr_h); + Fl_Window_Driver::driver(this)->menu_window_area(xx, scr_y, ww, scr_h); if (Y <= scr_y) Y = scr_y-Y+10; else { Y = Y+itemheight-scr_h-scr_y; if (Y < 0) return; Y = -Y-10; } - Fl_Menu_Window::position(x(), y()+Y); + Fl_Window_Driver::driver(this)->reposition_menu_window(x(), y()+Y); // y(y()+Y); // don't wait for response from X } @@ -885,6 +895,7 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown( button = pbutton; if (pbutton && pbutton->window()) { + menuwindow::parent_ = pbutton->top_window(); for (Fl_Window* w = pbutton->window(); w; w = w->window()) { X += w->x(); Y += w->y(); @@ -892,6 +903,7 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown( } else { X += Fl::event_x_root()-Fl::event_x(); Y += Fl::event_y_root()-Fl::event_y(); + menuwindow::parent_ = Fl::first_window(); } menuwindow mw(this, X, Y, W, H, initial_item, title, menubar); Fl::grab(mw); @@ -991,7 +1003,7 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown( int dy = n->y()-nY; int dx = n->x()-nX; int waX, waY, waW, waH; - Fl::screen_work_area(waX, waY, waW, waH, X, Y); + Fl_Window_Driver::driver(n)->menu_window_area(waX, waY, waW, waH, Fl::screen_num(X, Y)); for (int menu = 0; menu <= pp.menu_number; menu++) { menuwindow* tt = pp.p[menu]; int nx = tt->x()+dx; if (nx < waX) {nx = waX; dx = -tt->x() + waX;} @@ -1030,6 +1042,7 @@ const Fl_Menu_Item* Fl_Menu_Item::pulldown( while (pp.nummenus>1) delete pp.p[--pp.nummenus]; mw.hide(); Fl::grab(0); + menuwindow::parent_ = NULL; return m; } diff --git a/src/Fl_Native_File_Chooser_GTK.cxx b/src/Fl_Native_File_Chooser_GTK.cxx index 07cc3ff7a..842115d37 100644 --- a/src/Fl_Native_File_Chooser_GTK.cxx +++ b/src/Fl_Native_File_Chooser_GTK.cxx @@ -772,7 +772,7 @@ int Fl_GTK_Native_File_Chooser_Driver::fl_gtk_chooser_wrapper() Fl_Event_Dispatch old_dispatch = Fl::event_dispatch(); // prevent FLTK from processing any event Fl::event_dispatch(fnfc_dispatch); - + void *control = ((Fl_Unix_System_Driver*)Fl::system_driver())->control_maximize_button(NULL); gint response_id = GTK_RESPONSE_NONE; fl_g_signal_connect_data(gtkw_ptr, "response", G_CALLBACK(run_response_handler), &response_id, NULL, (GConnectFlags) 0); while (response_id == GTK_RESPONSE_NONE) { // loop that shows the GTK dialog window @@ -836,6 +836,7 @@ int Fl_GTK_Native_File_Chooser_Driver::fl_gtk_chooser_wrapper() while (fl_gtk_events_pending ()) fl_gtk_main_iteration (); Fl::event_dispatch(old_dispatch); + if (control) ((Fl_Unix_System_Driver*)Fl::system_driver())->control_maximize_button(control); return result; } // fl_gtk_chooser_wrapper diff --git a/src/Fl_Native_File_Chooser_Kdialog.cxx b/src/Fl_Native_File_Chooser_Kdialog.cxx index 814f19d97..5955659d6 100644 --- a/src/Fl_Native_File_Chooser_Kdialog.cxx +++ b/src/Fl_Native_File_Chooser_Kdialog.cxx @@ -18,6 +18,7 @@ #include <FL/Fl_Native_File_Chooser.H> #include "Fl_Native_File_Chooser_Kdialog.H" #include "Fl_Window_Driver.H" +#include "drivers/Unix/Fl_Unix_System_Driver.H" #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -129,12 +130,14 @@ int Fl_Kdialog_Native_File_Chooser_Driver::show() { Fl_Event_Dispatch old_dispatch = Fl::event_dispatch(); // prevent FLTK from processing any event Fl::event_dispatch(fnfc_dispatch); + void *control = ((Fl_Unix_System_Driver*)Fl::system_driver())->control_maximize_button(NULL); // run event loop until pipe finishes while (data.fd >= 0) Fl::wait(); Fl::remove_fd(fileno(pipe)); pclose(pipe); // return to previous event processing by FLTK Fl::event_dispatch(old_dispatch); + if (control) ((Fl_Unix_System_Driver*)Fl::system_driver())->control_maximize_button(control); if (data.all_files) { // process text received from pipe if (data.all_files[strlen(data.all_files)-1] == '\n') data.all_files[strlen(data.all_files)-1] = 0; diff --git a/src/Fl_Window_Driver.H b/src/Fl_Window_Driver.H index 85f758bfb..3766980a3 100644 --- a/src/Fl_Window_Driver.H +++ b/src/Fl_Window_Driver.H @@ -184,6 +184,12 @@ public: int /*dest_x*/, int /*dest_y*/, void (*)(void*, int,int,int,int), void*) { return 0; } static inline Fl_Window_Driver* driver(const Fl_Window *win) {return win->pWindowDriver;} + + // --- support for menu windows + // the default implementation of next 2 members is most probably enough + 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); + static Fl_Window *menu_parent(); }; #endif // FL_WINDOW_DRIVER_H diff --git a/src/Fl_Window_Driver.cxx b/src/Fl_Window_Driver.cxx index a21776381..025aa36ea 100644 --- a/src/Fl_Window_Driver.cxx +++ b/src/Fl_Window_Driver.cxx @@ -26,6 +26,7 @@ #include <FL/fl_draw.H> #include <FL/Fl.H> #include <FL/platform.H> +#include "Fl_Screen_Driver.H" extern void fl_throw_focus(Fl_Widget *o); @@ -247,6 +248,17 @@ void Fl_Window_Driver::resize_after_scale_change(int ns, float old_f, float new_ is_a_rescale_ = false; } +void Fl_Window_Driver::reposition_menu_window(int x, int y) { + if (y != pWindow->y() || x != pWindow->x()) pWindow->Fl_Widget::position(x, y); +} + +void Fl_Window_Driver::menu_window_area(int &X, int &Y, int &W, int &H, int nscreen) { + int mx, my; + Fl_Screen_Driver *scr_driver = Fl::screen_driver(); + if (nscreen < 0) nscreen = scr_driver->get_mouse(mx, my); + scr_driver->screen_work_area(X, Y, W, H, nscreen); +} + /** \} \endcond diff --git a/src/Fl_x.cxx b/src/Fl_x.cxx index 34528a0b2..40c0e02ea 100644 --- a/src/Fl_x.cxx +++ b/src/Fl_x.cxx @@ -78,155 +78,16 @@ static bool have_xfixes = false; # include <X11/extensions/Xrender.h> # endif -extern Fl_Widget *fl_selection_requestor; - -static void open_display_i(Display *d); // open display (internal) - -//////////////////////////////////////////////////////////////// - -// Hack to speed up bg box drawing - aka "boxcheat": -// If the boxtype of a window is a filled rectangle, we can make the -// redisplay *look* faster by using X's background pixel erasing. -// This is done by setting a flag when the window is shown for the first time. - -// Note to FLTK devs: -// This can cause unexpected behavior, for instance if the box() or -// color() of a window is changed after show(), and it does presumably not -// have much effect on current systems (compared to 1998). -// It is also fragile WRT checking the box type if any other scheme than -// the default scheme is loaded. -// Hence this is disabled since FLTK 1.4.0 (AlbrechtS Feb 02, 2022) - -// Note to FLTK users: -// You may define ENABLE_BOXCHEAT to use it anyway but please tell the -// FLTK devs why you believe that you need it. Should we re-enable it? - -#ifdef ENABLE_BOXCHEAT -static int fl_background_pixel = -1; -static inline int can_boxcheat(Fl_Boxtype b) { - return (b == 1 || ((b & 2) && b <= 15)); -} -#endif // (ENABLE_BOXCHEAT) - -//////////////////////////////////////////////////////////////// -// interface to poll/select call: - # if USE_POLL - # include <poll.h> -static pollfd *pollfds = 0; - # else -# if HAVE_SYS_SELECT_H -# include <sys/select.h> -# endif /* HAVE_SYS_SELECT_H */ - -// The following #define is only needed for HP-UX 9.x and earlier: -//#define select(a,b,c,d,e) select((a),(int *)(b),(int *)(c),(int *)(d),(e)) - -static fd_set fdsets[3]; -static int maxfd; # define POLLIN 1 -# define POLLOUT 4 -# define POLLERR 8 - # endif /* USE_POLL */ -static int nfds = 0; -static int fd_array_size = 0; -struct FD { -# if !USE_POLL - int fd; - short events; -# endif - void (*cb)(int, void*); - void* arg; -}; - -static FD *fd = 0; - -void Fl_X11_System_Driver::add_fd(int n, int events, void (*cb)(int, void*), void *v) { - remove_fd(n,events); - int i = nfds++; - if (i >= fd_array_size) { - FD *temp; - fd_array_size = 2*fd_array_size+1; - - if (!fd) temp = (FD*)malloc(fd_array_size*sizeof(FD)); - else temp = (FD*)realloc(fd, fd_array_size*sizeof(FD)); - - if (!temp) return; - fd = temp; - -# if USE_POLL - pollfd *tpoll; - - if (!pollfds) tpoll = (pollfd*)malloc(fd_array_size*sizeof(pollfd)); - else tpoll = (pollfd*)realloc(pollfds, fd_array_size*sizeof(pollfd)); - - if (!tpoll) return; - pollfds = tpoll; -# endif - } - fd[i].cb = cb; - fd[i].arg = v; -# if USE_POLL - pollfds[i].fd = n; - pollfds[i].events = events; -# else - fd[i].fd = n; - fd[i].events = events; - if (events & POLLIN) FD_SET(n, &fdsets[0]); - if (events & POLLOUT) FD_SET(n, &fdsets[1]); - if (events & POLLERR) FD_SET(n, &fdsets[2]); - if (n > maxfd) maxfd = n; -# endif -} - -void Fl_X11_System_Driver::add_fd(int n, void (*cb)(int, void*), void* v) { - add_fd(n, POLLIN, cb, v); -} +extern Fl_Widget *fl_selection_requestor; -void Fl_X11_System_Driver::remove_fd(int n, int events) { - int i,j; -# if !USE_POLL - maxfd = -1; // recalculate maxfd on the fly -# endif - for (i=j=0; i<nfds; i++) { -# if USE_POLL - if (pollfds[i].fd == n) { - int e = pollfds[i].events & ~events; - if (!e) continue; // if no events left, delete this fd - pollfds[j].events = e; - } -# else - if (fd[i].fd == n) { - int e = fd[i].events & ~events; - if (!e) continue; // if no events left, delete this fd - fd[i].events = e; - } - if (fd[i].fd > maxfd) maxfd = fd[i].fd; -# endif - // move it down in the array if necessary: - if (j<i) { - fd[j] = fd[i]; -# if USE_POLL - pollfds[j] = pollfds[i]; -# endif - } - j++; - } - nfds = j; -# if !USE_POLL - if (events & POLLIN) FD_CLR(n, &fdsets[0]); - if (events & POLLOUT) FD_CLR(n, &fdsets[1]); - if (events & POLLERR) FD_CLR(n, &fdsets[2]); -# endif -} +static void open_display_i(Display *d); // open display (internal) -void Fl_X11_System_Driver::remove_fd(int n) { - remove_fd(n, -1); -} extern int fl_send_system_handlers(void *e); @@ -255,10 +116,6 @@ static void do_queued_events() { #endif } -// these pointers are set by the Fl::lock() function: -static void nothing() {} -void (*fl_lock_function)() = nothing; -void (*fl_unlock_function)() = nothing; // This is never called with time_to_wait < 0.0: // It should return negative on error, 0 if nothing happens before @@ -269,69 +126,13 @@ int Fl_X11_System_Driver::poll_or_select_with_delay(double time_to_wait) { // unnecessarily and thus cause the file descriptor to not be ready, // so we must check for already-read events: if (fl_display && XQLength(fl_display)) {do_queued_events(); return 1;} - -# if !USE_POLL - fd_set fdt[3]; - fdt[0] = fdsets[0]; - fdt[1] = fdsets[1]; - fdt[2] = fdsets[2]; -# endif - int n; - - fl_unlock_function(); - - if (time_to_wait < 2147483.648) { -# if USE_POLL - n = ::poll(pollfds, nfds, int(time_to_wait*1000 + .5)); -# else - timeval t; - t.tv_sec = int(time_to_wait); - t.tv_usec = int(1000000 * (time_to_wait-t.tv_sec)); - n = ::select(maxfd+1,&fdt[0],&fdt[1],&fdt[2],&t); -# endif - } else { -# if USE_POLL - n = ::poll(pollfds, nfds, -1); -# else - n = ::select(maxfd+1,&fdt[0],&fdt[1],&fdt[2],0); -# endif - } - - fl_lock_function(); - - if (n > 0) { - for (int i=0; i<nfds; i++) { -# if USE_POLL - if (pollfds[i].revents) fd[i].cb(pollfds[i].fd, fd[i].arg); -# else - int f = fd[i].fd; - short revents = 0; - if (FD_ISSET(f,&fdt[0])) revents |= POLLIN; - if (FD_ISSET(f,&fdt[1])) revents |= POLLOUT; - if (FD_ISSET(f,&fdt[2])) revents |= POLLERR; - if (fd[i].events & revents) fd[i].cb(f, fd[i].arg); -# endif - } - } - return n; + return Fl_Unix_System_Driver::poll_or_select_with_delay(time_to_wait); } // just like Fl_X11_System_Driver::poll_or_select_with_delay(0.0) except no callbacks are done: int Fl_X11_System_Driver::poll_or_select() { if (XQLength(fl_display)) return 1; - if (!nfds) return 0; // nothing to select or poll -# if USE_POLL - return ::poll(pollfds, nfds, 0); -# else - timeval t; - t.tv_sec = 0; - t.tv_usec = 0; - fd_set fdt[3]; - fdt[0] = fdsets[0]; - fdt[1] = fdsets[1]; - fdt[2] = fdsets[2]; - return ::select(maxfd+1,&fdt[0],&fdt[1],&fdt[2],&t); -# endif + return Fl_Unix_System_Driver::poll_or_select(); } // replace \r\n by \n @@ -883,28 +684,6 @@ static void read_int(uchar *c, int& i) { i |= (*(++c))<<24; } -// turn BMP image FLTK produced by create_bmp() back to Fl_RGB_Image -static Fl_RGB_Image *own_bmp_to_RGB(char *bmp) { - int w, h; - read_int((uchar*)bmp + 18, w); - read_int((uchar*)bmp + 22, h); - int R=(3*w+3)/4 * 4; // the number of bytes per row, rounded up to multiple of 4 - bmp += 54; - uchar *data = new uchar[w*h*3]; - uchar *p = data; - for (int i = h-1; i >= 0; i--) { - char *s = bmp + i * R; - for (int j = 0; j < w; j++) { - *p++=s[2]; - *p++=s[1]; - *p++=s[0]; - s+=3; - } - } - Fl_RGB_Image *img = new Fl_RGB_Image(data, w, h, 3); - img->alloc_array = 1; - return img; -} // Call this when a "paste" operation happens: void Fl_X11_Screen_Driver::paste(Fl_Widget &receiver, int clipboard, const char *type) { @@ -918,7 +697,7 @@ void Fl_X11_Screen_Driver::paste(Fl_Widget &receiver, int clipboard, const char Fl::e_length = fl_selection_length[clipboard]; if (!Fl::e_text) Fl::e_text = (char *)""; } else if (clipboard == 1 && type == Fl::clipboard_image && fl_selection_type[1] == type) { - Fl::e_clipboard_data = own_bmp_to_RGB(fl_selection_buffer[1]); + 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; int retval = receiver.handle(FL_PASTE); @@ -1074,53 +853,12 @@ static void write_int(unsigned char **cp, int i) { *cp = c; } -static unsigned char *create_bmp(const unsigned char *data, int W, int H, int *return_size){ - int R=(3*W+3)/4 * 4; // the number of bytes per row, rounded up to multiple of 4 - int s=H*R; - int fs=14+40+s; - unsigned char *b=new unsigned char[fs]; - unsigned char *c=b; - // BMP header - *c++='B'; - *c++='M'; - write_int(&c,fs); - write_int(&c,0); - write_int(&c,14+40); - // DIB header: - write_int(&c,40); - write_int(&c,W); - write_int(&c,H); - write_short(&c,1); - write_short(&c,24);//bits ber pixel - write_int(&c,0);//RGB - write_int(&c,s); - write_int(&c,0);// horizontal resolution - write_int(&c,0);// vertical resolution - write_int(&c,0);//number of colors. 0 -> 1<<bits_per_pixel - write_int(&c,0); - // Pixel data - data+=3*W*H; - for (int y=0;y<H;++y){ - data-=3*W; - const unsigned char *s=data; - unsigned char *p=c; - for (int x=0;x<W;++x){ - *p++=s[2]; - *p++=s[1]; - *p++=s[0]; - s+=3; - } - c+=R; - } - *return_size = fs; - return b; -} // takes a raw RGB image and puts it in the copy/paste buffer void Fl_X11_Screen_Driver::copy_image(const unsigned char *data, int W, int H, int clipboard){ if (!data || W <= 0 || H <= 0) return; delete[] fl_selection_buffer[clipboard]; - fl_selection_buffer[clipboard] = (char *) create_bmp(data,W,H,&fl_selection_length[clipboard]); + fl_selection_buffer[clipboard] = (char *) Fl_Unix_System_Driver::create_bmp(data,W,H,&fl_selection_length[clipboard]); fl_selection_buffer_length[clipboard] = fl_selection_length[clipboard]; fl_i_own_selection[clipboard] = 1; fl_selection_type[clipboard] = Fl::clipboard_image; @@ -3182,13 +2920,6 @@ int Fl_X11_Window_Driver::set_cursor(const Fl_RGB_Image *image, int hotx, int ho //////////////////////////////////////////////////////////////// -// returns pointer to the filename, or null if name ends with '/' -const char *Fl_X11_System_Driver::filename_name(const char *name) { - const char *p,*q; - if (!name) return (0); - for (p=q=name; *p;) if (*p++ == '/') q = p; - return q; -} void Fl_X11_Window_Driver::label(const char *name, const char *iname) { if (shown() && !parent()) { diff --git a/src/Makefile b/src/Makefile index f98ca0279..457aa4b59 100644 --- a/src/Makefile +++ b/src/Makefile @@ -206,6 +206,8 @@ GLCPPFILES_X11 = drivers/X11/Fl_X11_Gl_Window_Driver.cxx GLCPPFILES_XFT = $(GLCPPFILES_X11) GLCPPFILES_WIN = drivers/WinAPI/Fl_WinAPI_Gl_Window_Driver.cxx +GLCPPFILES_WAYLAND = drivers/Wayland/Fl_Wayland_Gl_Window_Driver.cxx + GLCPPFILES += $(GLCPPFILES_$(BUILD)) # the following file currently doesn't contribute code to GLCPPFILES @@ -263,6 +265,7 @@ XLIBCPPFILES = \ drivers/X11/Fl_X11_Window_Driver.cxx \ drivers/X11/Fl_X11_Screen_Driver.cxx \ drivers/Posix/Fl_Posix_System_Driver.cxx \ + drivers/Unix/Fl_Unix_System_Driver.cxx \ drivers/X11/Fl_X11_System_Driver.cxx \ drivers/Posix/Fl_Posix_Printer_Driver.cxx \ Fl_x.cxx \ @@ -271,6 +274,25 @@ XLIBCPPFILES = \ Fl_Native_File_Chooser_GTK.cxx\ Fl_Native_File_Chooser_Kdialog.cxx \ Fl_get_key.cxx + +# These C++ files are used under condition: BUILD_WAYLAND +WLCPPFILES = \ + drivers/Posix/Fl_Posix_Printer_Driver.cxx \ + Fl_Native_File_Chooser_FLTK.cxx \ + Fl_Native_File_Chooser_GTK.cxx \ + Fl_Native_File_Chooser_Kdialog.cxx \ + drivers/Posix/Fl_Posix_System_Driver.cxx \ + drivers/Unix/Fl_Unix_System_Driver.cxx \ + drivers/Wayland/Fl_Wayland_System_Driver.cxx \ + drivers/Wayland/Fl_Wayland_Screen_Driver.cxx \ + drivers/Wayland/Fl_Wayland_Window_Driver.cxx \ + drivers/Wayland/Fl_Wayland_Image_Surface_Driver.cxx \ + drivers/Wayland/Fl_Wayland_Copy_Surface_Driver.cxx \ + drivers/Wayland/Fl_Wayland_Graphics_Driver.cxx \ + drivers/Wayland/Fl_wayland.cxx + + +# fl_dnd_x.cxx Fl_Native_File_Chooser_GTK.cxx # This C file is used under condition: BUILD_X11 XLIBCFILES = \ @@ -290,6 +312,15 @@ XLIBFONTFILES = \ XLIBXFTFILES = \ drivers/Xlib/Fl_Xlib_Graphics_Driver_font_xft.cxx \ drivers/Cairo/Fl_Cairo_Graphics_Driver.cxx + +# This C file is used under condition: BUILD_WAYLAND +WLCFILES = \ + xutf8/keysym2Ucs.c \ + scandir_posix.c + +# These C++ files are used under condition: BUILD_WAYLAND +WLXFTFILES = \ + drivers/Cairo/Fl_Cairo_Graphics_Driver.cxx # These C++ files are used under condition: BUILD_GDI GDICPPFILES = \ @@ -325,7 +356,7 @@ FLTKFLAGS = -DFL_LIBRARY include ../makeinclude # makeinclude has set this variable: -# BUILD = {WIN|X11|XFT|OSX} +# BUILD = {WIN|X11|XFT|OSX|WAYLAND} MMFILES_OSX = $(OBJCPPFILES) MMFILES = $(MMFILES_$(BUILD)) @@ -336,6 +367,8 @@ CPPFILES_OSX = $(QUARTZCPPFILES) CPPFILES_XFT = $(XLIBCPPFILES) $(XLIBXFTFILES) CPPFILES_X11 = $(XLIBCPPFILES) $(XLIBFONTFILES) +CPPFILES_WAYLAND = $(WLCPPFILES) $(WLXFTFILES) + CPPFILES_WIN = $(GDICPPFILES) CPPFILES += $(CPPFILES_$(BUILD)) @@ -344,12 +377,21 @@ CPPFILES += $(CPPFILES_$(BUILD)) CFILES_X11 = $(XLIBCFILES) $(XLIBXCFILES) CFILES_XFT = $(XLIBCFILES) +CFILES_WAYLAND = $(WLCFILES) +EXTRA_OBJECTS_WAYLAND = ../libdecor/build/fl_libdecor.o ../libdecor/build/libdecor-cairo-blur.o \ + ../libdecor/build/fl_libdecor-plugins.o \ + xdg-decoration-protocol.o xdg-shell-protocol.o text-input-protocol.o \ + ../libdecor/build/cursor-settings.o ../libdecor/build/os-compatibility.o +EXTRA_CXXFLAGS_WAYLAND = -I. + CFILES_WIN = $(GDICFILES) CFILES += $(CFILES_$(BUILD)) +CXXFLAGS += $(EXTRA_CXXFLAGS_$(BUILD)) OBJECTS = $(MMFILES:.mm=.o) $(CPPFILES:.cxx=.o) $(CFILES:.c=.o) $(UTF8CFILES:.c=.o) +OBJECTS += $(EXTRA_OBJECTS_$(BUILD)) GLOBJECTS = $(GLCPPFILES:.cxx=.o) FLOBJECTS = $(FLCPPFILES:.cxx=.o) IMGOBJECTS = $(IMGCPPFILES:.cxx=.o) @@ -619,6 +661,7 @@ clean: -$(RM) drivers/WinAPI/*.o -$(RM) drivers/X11/*.o -$(RM) drivers/Xlib/*.o + -$(RM) drivers/Wayland/*.o -$(RM) $(DSONAME) $(FLDSONAME) $(GLDSONAME) $(IMGDSONAME) \ $(LIBNAME) $(FLLIBNAME) $(GLLIBNAME) \ $(IMGLIBNAME) \ diff --git a/src/drivers/Unix/Fl_Unix_System_Driver.H b/src/drivers/Unix/Fl_Unix_System_Driver.H new file mode 100644 index 000000000..9b1268b57 --- /dev/null +++ b/src/drivers/Unix/Fl_Unix_System_Driver.H @@ -0,0 +1,57 @@ +// +// Definition of Wayland system driver +// 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_NIX_SYSTEM_DRIVER_H +#define FL_NIX_SYSTEM_DRIVER_H + +#include "../Posix/Fl_Posix_System_Driver.H" +class Fl_RGB_Image; + +class FL_EXPORT Fl_Unix_System_Driver : public Fl_Posix_System_Driver { +public: + Fl_Unix_System_Driver() : Fl_Posix_System_Driver() { + } + virtual int clocale_snprintf(char *output, size_t output_size, const char *format, va_list args); + virtual int clocale_sscanf(const char *input, const char *format, va_list args); + virtual int clocale_printf(FILE *output, const char *format, va_list args); + virtual int filename_list(const char *d, dirent ***list, + int (*sort)(struct dirent **, struct dirent **), + char *errmsg=NULL, int errmsg_sz=0); + virtual int open_uri(const char *uri, char *msg, int msglen); + virtual int use_tooltip_timeout_condition() {return 1;} + virtual int file_browser_load_filesystem(Fl_File_Browser *browser, char *filename, int lname, Fl_File_Icon *icon); + virtual void newUUID(char *uuidBuffer); + virtual char *preference_rootnode(Fl_Preferences *prefs, Fl_Preferences::Root root, const char *vendor, + const char *application); + virtual int preferences_need_protection_check() {return 1;} + virtual int utf8locale(); + virtual const char *filename_name(const char *buf); + virtual void add_fd(int fd, int when, Fl_FD_Handler cb, void* = 0); + virtual void add_fd(int fd, Fl_FD_Handler cb, void* = 0); + virtual void remove_fd(int, int when); + virtual void remove_fd(int); + double wait(double time_to_wait); + int ready(); + // 3 additional virtual members + virtual int poll_or_select_with_delay(double time_to_wait); + virtual int poll_or_select(); + virtual void *control_maximize_button(void *data); + static unsigned char *create_bmp(const unsigned char *data, int W, int H, int *return_size); + static Fl_RGB_Image *own_bmp_to_RGB(char *bmp); +}; + +#endif /* FL_NIX_SYSTEM_DRIVER_H */ diff --git a/src/drivers/Unix/Fl_Unix_System_Driver.cxx b/src/drivers/Unix/Fl_Unix_System_Driver.cxx new file mode 100644 index 000000000..b564df5a5 --- /dev/null +++ b/src/drivers/Unix/Fl_Unix_System_Driver.cxx @@ -0,0 +1,960 @@ +// +// Definition of Unix/Linux 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_Unix_System_Driver.H" +#include <FL/Fl_File_Browser.H> +#include <FL/fl_string_functions.h> // fl_strdup +#include <FL/platform.H> +#include "../../flstring.h" +#include "../../Fl_Timeout.h" + +#include <locale.h> +#include <time.h> +#include <unistd.h> +#include <sys/types.h> +#include <pwd.h> +#include <string.h> // strerror(errno) +#include <errno.h> // errno +#if HAVE_DLSYM && HAVE_DLFCN_H +#include <dlfcn.h> // for dlsym +#endif + + +#if defined(_AIX) +extern "C" { +# include <sys/vmount.h> +# include <sys/mntctl.h> + // Older AIX versions don't expose this prototype + int mntctl(int, int, char *); +} +#endif // _AIX + +#if defined(__NetBSD__) +extern "C" { +# include <sys/param.h> // For '__NetBSD_Version__' definition +# if defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 300000000) +# include <sys/types.h> +# include <sys/statvfs.h> +# if defined(HAVE_PTHREAD) && defined(HAVE_PTHREAD_H) +# include <pthread.h> +# endif // HAVE_PTHREAD && HAVE_PTHREAD_H +# ifdef HAVE_PTHREAD + static pthread_mutex_t getvfsstat_mutex = PTHREAD_MUTEX_INITIALIZER; +# endif // HAVE_PTHREAD/ +# endif // __NetBSD_Version__ +} +#endif // __NetBSD__ + +#ifndef HAVE_SCANDIR +extern "C" { + int fl_scandir(const char *dirname, struct dirent ***namelist, + int (*select)(struct dirent *), + int (*compar)(struct dirent **, struct dirent **), + char *errmsg, int errmsg_sz); +} +#endif + +#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 +static locale_t c_locale = NULL; +#endif + + +int Fl_Unix_System_Driver::clocale_printf(FILE *output, const char *format, va_list args) { +#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 + if (!c_locale) + c_locale = newlocale(LC_NUMERIC_MASK, "C", duplocale(LC_GLOBAL_LOCALE)); + locale_t previous_locale = uselocale(c_locale); + int retval = vfprintf(output, format, args); + uselocale(previous_locale); +#else + char *saved_locale = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + int retval = vfprintf(output, format, args); + setlocale(LC_NUMERIC, saved_locale); +#endif + return retval; +} + +int Fl_Unix_System_Driver::clocale_snprintf(char *output, size_t output_size, const char *format, va_list args) { +#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 + if (!c_locale) + c_locale = newlocale(LC_NUMERIC_MASK, "C", duplocale(LC_GLOBAL_LOCALE)); + locale_t previous_locale = uselocale(c_locale); + int retval = vsnprintf(output, output_size, format, args); + uselocale(previous_locale); +#else + char *saved_locale = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + int retval = vsnprintf(output, output_size, format, args); + setlocale(LC_NUMERIC, saved_locale); +#endif + return retval; +} + +int Fl_Unix_System_Driver::clocale_sscanf(const char *input, const char *format, va_list args) { +#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 + if (!c_locale) + c_locale = newlocale(LC_NUMERIC_MASK, "C", duplocale(LC_GLOBAL_LOCALE)); + locale_t previous_locale = uselocale(c_locale); + int retval = vsscanf(input, format, args); + uselocale(previous_locale); +#else + char *saved_locale = setlocale(LC_NUMERIC, NULL); + setlocale(LC_NUMERIC, "C"); + int retval = vsscanf(input, format, args); + setlocale(LC_NUMERIC, saved_locale); +#endif + return retval; +} + + +// Find a program in the path... +static char *path_find(const char *program, char *filename, int filesize) { + const char *path; // Search path + char *ptr, // Pointer into filename + *end; // End of filename buffer + + + if ((path = fl_getenv("PATH")) == NULL) path = "/bin:/usr/bin"; + + for (ptr = filename, end = filename + filesize - 1; *path; path ++) { + if (*path == ':') { + if (ptr > filename && ptr[-1] != '/' && ptr < end) *ptr++ = '/'; + + strlcpy(ptr, program, end - ptr + 1); + + if (!access(filename, X_OK)) return filename; + + ptr = filename; + } else if (ptr < end) *ptr++ = *path; + } + + if (ptr > filename) { + if (ptr[-1] != '/' && ptr < end) *ptr++ = '/'; + + strlcpy(ptr, program, end - ptr + 1); + + if (!access(filename, X_OK)) return filename; + } + + return 0; +} + + +int Fl_Unix_System_Driver::open_uri(const char *uri, char *msg, int msglen) +{ + // Run any of several well-known commands to open the URI. + // + // We give preference to the Portland group's xdg-utils + // programs which run the user's preferred web browser, etc. + // based on the current desktop environment in use. We fall + // back on older standards and then finally test popular programs + // until we find one we can use. + // + // Note that we specifically do not support the MAILER and + // BROWSER environment variables because we have no idea whether + // we need to run the listed commands in a terminal program. + char command[FL_PATH_MAX], // Command to run... + *argv[4], // Command-line arguments + remote[1024]; // Remote-mode command... + const char * const *commands; // Array of commands to check... + int i; + static const char * const browsers[] = { + "xdg-open", // Portland + "htmlview", // Freedesktop.org + "firefox", + "mozilla", + "netscape", + "konqueror", // KDE + "opera", + "hotjava", // Solaris + "mosaic", + NULL + }; + static const char * const readers[] = { + "xdg-email", // Portland + "thunderbird", + "mozilla", + "netscape", + "evolution", // GNOME + "kmailservice", // KDE + NULL + }; + static const char * const managers[] = { + "xdg-open", // Portland + "fm", // IRIX + "dtaction", // CDE + "nautilus", // GNOME + "konqueror", // KDE + NULL + }; + + // Figure out which commands to check for... + if (!strncmp(uri, "file://", 7)) commands = managers; + else if (!strncmp(uri, "mailto:", 7) || + !strncmp(uri, "news:", 5)) commands = readers; + else commands = browsers; + + // Find the command to run... + for (i = 0; commands[i]; i ++) + if (path_find(commands[i], command, sizeof(command))) break; + + if (!commands[i]) { + if (msg) { + snprintf(msg, msglen, "No helper application found for \"%s\"", uri); + } + + return 0; + } + + // Handle command-specific arguments... + argv[0] = (char *)commands[i]; + + if (!strcmp(commands[i], "firefox") || + !strcmp(commands[i], "mozilla") || + !strcmp(commands[i], "netscape") || + !strcmp(commands[i], "thunderbird")) { + // program -remote openURL(uri) + snprintf(remote, sizeof(remote), "openURL(%s)", uri); + + argv[1] = (char *)"-remote"; + argv[2] = remote; + argv[3] = 0; + } else if (!strcmp(commands[i], "dtaction")) { + // dtaction open uri + argv[1] = (char *)"open"; + argv[2] = (char *)uri; + argv[3] = 0; + } else { + // program uri + argv[1] = (char *)uri; + argv[2] = 0; + } + + if (msg) { + strlcpy(msg, argv[0], msglen); + + for (i = 1; argv[i]; i ++) { + strlcat(msg, " ", msglen); + strlcat(msg, argv[i], msglen); + } + } + + return run_program(command, argv, msg, msglen) != 0; +} + + +int Fl_Unix_System_Driver::file_browser_load_filesystem(Fl_File_Browser *browser, char *filename, int lname, Fl_File_Icon *icon) +{ + int num_files = 0; +#if defined(_AIX) + // AIX don't write the mounted filesystems to a file like '/etc/mnttab'. + // But reading the list of mounted filesystems from the kernel is possible: + // http://publib.boulder.ibm.com/infocenter/pseries/v5r3/topic/com.ibm.aix.basetechref/doc/basetrf1/mntctl.htm + int res = -1, len; + char *list = NULL, *name; + struct vmount *vp; + + // We always have the root filesystem + add("/", icon); + // Get the required buffer size for the vmount structures + res = mntctl(MCTL_QUERY, sizeof(len), (char *) &len); + if (!res) { + // Allocate buffer ... + list = (char *) malloc((size_t) len); + if (NULL == list) { + res = -1; + } else { + // ... and read vmount structures from kernel + res = mntctl(MCTL_QUERY, len, list); + if (0 >= res) { + res = -1; + } else { + for (int i = 0, vp = (struct vmount *) list; i < res; ++i) { + name = (char *) vp + vp->vmt_data[VMT_STUB].vmt_off; + strlcpy(filename, name, lname); + // Skip the already added root filesystem + if (strcmp("/", filename) != 0) { + strlcat(filename, "/", lname); + browser->add(filename, icon); + } + vp = (struct vmount *) ((char *) vp + vp->vmt_length); + } + } + } + } + // Note: Executing 'free(NULL)' is allowed and simply do nothing + free((void *) list); +#elif defined(__NetBSD__) && defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 300000000) + // NetBSD don't write the mounted filesystems to a file like '/etc/mnttab'. + // Since NetBSD 3.0 the system call getvfsstat(2) has replaced getfsstat(2) + // that is used by getmntinfo(3): + // http://www.daemon-systems.org/man/getmntinfo.3.html + int res = -1; + struct statvfs *list; + + // We always have the root filesystem + browser->add("/", icon); +# ifdef HAVE_PTHREAD + // Lock mutex for thread safety + if (!pthread_mutex_lock(&getvfsstat_mutex)) { +# endif // HAVE_PTHREAD + // Get list of statvfs structures + res = getmntinfo(&list, ST_WAIT); + if (0 < res) { + for (int i = 0; i < res; ++i) { + strlcpy(filename, list[i].f_mntonname, lname); + // Skip the already added root filesystem + if (strcmp("/", filename) != 0) { + strlcat(filename, "/", lname); + browser->add(filename, icon); + } + } + } else { + res = -1; + } +# ifdef HAVE_PTHREAD + pthread_mutex_unlock(&getvfsstat_mutex); + } +# endif // HAVE_PTHREAD +#else + // + // UNIX code uses /etc/fstab or similar... + // + FILE *mtab; // /etc/mtab or /etc/mnttab file + char line[FL_PATH_MAX]; // Input line + + // Every Unix has a root filesystem '/'. + // This ensures that the user don't get an empty + // window after requesting filesystem list. + browser->add("/", icon); + num_files ++; + + // + // Open the file that contains a list of mounted filesystems... + // + // Note: this misses automounted filesystems on FreeBSD if absent from /etc/fstab + // + + mtab = fopen("/etc/mnttab", "r"); // Fairly standard + if (mtab == NULL) + mtab = fopen("/etc/mtab", "r"); // More standard + if (mtab == NULL) + mtab = fopen("/etc/fstab", "r"); // Otherwise fallback to full list + if (mtab == NULL) + mtab = fopen("/etc/vfstab", "r"); // Alternate full list file + + if (mtab != NULL) + { + while (fgets(line, sizeof(line), mtab) != NULL) + { + if (line[0] == '#' || line[0] == '\n') + continue; + if (sscanf(line, "%*s%4095s", filename) != 1) + continue; + if (strcmp("/", filename) == 0) + continue; // "/" was added before + + // Add a trailing slash (except for the root filesystem) + strlcat(filename, "/", lname); + + // printf("Fl_File_Browser::load() - adding \"%s\" to list...\n", filename); + browser->add(filename, icon); + num_files ++; + } + + fclose(mtab); + } +#endif // _AIX || ... + return num_files; +} + +void Fl_Unix_System_Driver::newUUID(char *uuidBuffer) +{ + unsigned char b[16]; +#if HAVE_DLSYM && HAVE_DLFCN_H + typedef void (*gener_f_type)(uchar*); + static bool looked_for_uuid_generate = false; + static gener_f_type uuid_generate_f = NULL; + if (!looked_for_uuid_generate) { + looked_for_uuid_generate = true; + uuid_generate_f = (gener_f_type)dlopen_or_dlsym("libuuid", "uuid_generate"); + } + if (uuid_generate_f) { + uuid_generate_f(b); + } else +#endif + { + time_t t = time(0); // first 4 byte + b[0] = (unsigned char)t; + b[1] = (unsigned char)(t>>8); + b[2] = (unsigned char)(t>>16); + b[3] = (unsigned char)(t>>24); + int r = rand(); // four more bytes + b[4] = (unsigned char)r; + b[5] = (unsigned char)(r>>8); + b[6] = (unsigned char)(r>>16); + b[7] = (unsigned char)(r>>24); + unsigned long a = (unsigned long)&t; // four more bytes + b[8] = (unsigned char)a; + b[9] = (unsigned char)(a>>8); + b[10] = (unsigned char)(a>>16); + b[11] = (unsigned char)(a>>24); + // Now we try to find 4 more "random" bytes. We extract the + // lower 4 bytes from the address of t - it is created on the + // stack so *might* be in a different place each time... + // This is now done via a union to make it compile OK on 64-bit systems. + union { void *pv; unsigned char a[sizeof(void*)]; } v; + v.pv = (void *)(&t); + // NOTE: May need to handle big- or little-endian systems here +# if WORDS_BIGENDIAN + b[8] = v.a[sizeof(void*) - 1]; + b[9] = v.a[sizeof(void*) - 2]; + b[10] = v.a[sizeof(void*) - 3]; + b[11] = v.a[sizeof(void*) - 4]; +# else // data ordered for a little-endian system + b[8] = v.a[0]; + b[9] = v.a[1]; + b[10] = v.a[2]; + b[11] = v.a[3]; +# endif + char name[80]; // last four bytes + gethostname(name, 79); + memcpy(b+12, name, 4); + } + sprintf(uuidBuffer, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], + b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); +} + +/* + Note: `prefs` can be NULL! + */ +char *Fl_Unix_System_Driver::preference_rootnode(Fl_Preferences * /*prefs*/, + Fl_Preferences::Root root, + const char *vendor, + const char *application) +{ + static char *filename = 0L; + if (!filename) filename = (char*)::calloc(1, FL_PATH_MAX); + const char *home = ""; + int pref_type = root & Fl_Preferences::ROOT_MASK; + switch (pref_type) { + case Fl_Preferences::USER: + home = getenv("HOME"); + // make sure that $HOME is set to an existing directory + if ((home == NULL) || (home[0] == 0) || (::access(home, F_OK) == -1)) { + struct passwd *pw = getpwuid(getuid()); + if (pw) + home = pw->pw_dir; + } + if ((home == 0L) || (home[0] == 0) || (::access(home, F_OK) == -1)) + return NULL; + strlcpy(filename, home, FL_PATH_MAX); + if (filename[strlen(filename) - 1] != '/') + strlcat(filename, "/", FL_PATH_MAX); + strlcat(filename, ".fltk/", FL_PATH_MAX); + break; + case Fl_Preferences::SYSTEM: + strcpy(filename, "/etc/fltk/"); + break; + default: // MEMORY + filename[0] = '\0'; // empty string + break; + } + + // Make sure that the parameters are not NULL + if ( (vendor==NULL) || (vendor[0]==0) ) + vendor = "unknown"; + if ( (application==NULL) || (application[0]==0) ) + application = "unknown"; + + snprintf(filename + strlen(filename), FL_PATH_MAX - strlen(filename), + "%s/%s.prefs", vendor, application); + + // If this is not the USER path (i.e. SYSTEM or MEMORY), we are done + if ((pref_type) != Fl_Preferences::USER) + return filename; + + // If the legacy file exists, we are also done + if (::access(filename, F_OK)==0) + return filename; + + // This is USER mode, and there is no legacy file. Create an XDG conforming path. + // Check $XDG_CONFIG_HOME, and if it isn't set, default to $HOME/.config + const char *xdg = getenv("XDG_CONFIG_HOME"); + if (xdg==NULL) { + xdg = "~/.config"; + } + filename[0] = 0; + if (strncmp(xdg, "~/", 2)==0) { + strlcpy(filename, home, FL_PATH_MAX); + strlcat(filename, "/", FL_PATH_MAX); + strlcat(filename, xdg+2, FL_PATH_MAX); + } else if (strncmp(xdg, "$HOME/", 6)==0) { + strlcpy(filename, home, FL_PATH_MAX); + strlcat(filename, "/", FL_PATH_MAX); + strlcat(filename, xdg+6, FL_PATH_MAX); + } else if (strncmp(xdg, "${HOME}/", 8)==0) { + strlcpy(filename, home, FL_PATH_MAX); + strlcat(filename, "/", FL_PATH_MAX); + strlcat(filename, xdg+8, FL_PATH_MAX); + } else { + strlcpy(filename, xdg, FL_PATH_MAX); + } + strlcat(filename, "/", FL_PATH_MAX); + strlcat(filename, vendor, FL_PATH_MAX); + strlcat(filename, "/", FL_PATH_MAX); + strlcat(filename, application, FL_PATH_MAX); + strlcat(filename, ".prefs", FL_PATH_MAX); + + return filename; +} + +// +// Needs some docs +// Returns -1 on error, errmsg will contain OS error if non-NULL. +// +int Fl_Unix_System_Driver::filename_list(const char *d, + dirent ***list, + int (*sort)(struct dirent **, struct dirent **), + char *errmsg, int errmsg_sz) { + int dirlen; + char *dirloc; + + if (errmsg && errmsg_sz>0) errmsg[0] = '\0'; + + // Assume that locale encoding is no less dense than UTF-8 + dirlen = strlen(d); + dirloc = (char *)malloc(dirlen + 1); + fl_utf8to_mb(d, dirlen, dirloc, dirlen + 1); + +#ifndef HAVE_SCANDIR + // This version is when we define our own scandir. Note it updates errmsg on errors. + int n = fl_scandir(dirloc, list, 0, sort, errmsg, errmsg_sz); +#elif defined(HAVE_SCANDIR_POSIX) + // POSIX (2008) defines the comparison function like this: + int n = scandir(dirloc, list, 0, (int(*)(const dirent **, const dirent **))sort); +#elif defined(__osf__) + // OSF, DU 4.0x + int n = scandir(dirloc, list, 0, (int(*)(dirent **, dirent **))sort); +#elif defined(_AIX) + // AIX is almost standard... + int n = scandir(dirloc, list, 0, (int(*)(void*, void*))sort); +#elif defined(__sgi) + int n = scandir(dirloc, list, 0, sort); +#else + // The vast majority of UNIX systems want the sort function to have this + // prototype, most likely so that it can be passed to qsort without any + // changes: + int n = scandir(dirloc, list, 0, (int(*)(const void*,const void*))sort); +#endif + + free(dirloc); + + if (n==-1) { + // Don't write to errmsg if FLTK's fl_scandir() already set it. + // If OS's scandir() was used (HAVE_SCANDIR), we return its error in errmsg here.. +#ifdef HAVE_SCANDIR + if (errmsg) fl_snprintf(errmsg, errmsg_sz, "%s", strerror(errno)); +#endif + return -1; + } + + // convert every filename to UTF-8, and append a '/' to all + // filenames that are directories + int i; + char *fullname = (char*)malloc(dirlen+FL_PATH_MAX+3); // Add enough extra for two /'s and a nul + // Use memcpy for speed since we already know the length of the string... + memcpy(fullname, d, dirlen+1); + + char *name = fullname + dirlen; + if (name!=fullname && name[-1]!='/') + *name++ = '/'; + + for (i=0; i<n; i++) { + int newlen; + dirent *de = (*list)[i]; + int len = strlen(de->d_name); + newlen = fl_utf8from_mb(NULL, 0, de->d_name, len); + dirent *newde = (dirent*)malloc(de->d_name - (char*)de + newlen + 2); // Add space for a / and a nul + + // Conversion to UTF-8 + memcpy(newde, de, de->d_name - (char*)de); + fl_utf8from_mb(newde->d_name, newlen + 1, de->d_name, len); + + // Check if dir (checks done on "old" name as we need to interact with + // the underlying OS) + if (de->d_name[len-1]!='/' && len<=FL_PATH_MAX) { + // Use memcpy for speed since we already know the length of the string... + memcpy(name, de->d_name, len+1); + if (fl_filename_isdir(fullname)) { + char *dst = newde->d_name + newlen; + *dst++ = '/'; + *dst = 0; + } + } + + free(de); + (*list)[i] = newde; + } + free(fullname); + + return n; +} + +int Fl_Unix_System_Driver::utf8locale() { + static int ret = 2; + if (ret == 2) { + char* s; + ret = 1; /* assume UTF-8 if no locale */ + if (((s = getenv("LC_CTYPE")) && *s) || + ((s = getenv("LC_ALL")) && *s) || + ((s = getenv("LANG")) && *s)) { + ret = (strstr(s,"utf") || strstr(s,"UTF")); + } + } + return ret; +} + + +// returns pointer to the filename, or null if name ends with '/' +const char *Fl_Unix_System_Driver::filename_name(const char *name) { + const char *p,*q; + if (!name) return (0); + for (p=q=name; *p;) if (*p++ == '/') q = p; + return q; +} + + +//////////////////////////////////////////////////////////////// +// interface to poll/select call: + +# if USE_POLL + +# include <poll.h> +static pollfd *pollfds = 0; + +# else +# if HAVE_SYS_SELECT_H +# include <sys/select.h> +# endif /* HAVE_SYS_SELECT_H */ + +// The following #define is only needed for HP-UX 9.x and earlier: +//#define select(a,b,c,d,e) select((a),(int *)(b),(int *)(c),(int *)(d),(e)) + +static fd_set fdsets[3]; +static int maxfd; +# define POLLIN 1 +# define POLLOUT 4 +# define POLLERR 8 + +# endif /* USE_POLL */ + +static int nfds = 0; +static int fd_array_size = 0; +struct FD { +# if !USE_POLL + int fd; + short events; +# endif + void (*cb)(int, void*); + void* arg; +}; + +static FD *fd = 0; + +void Fl_Unix_System_Driver::add_fd(int n, int events, void (*cb)(int, void*), void *v) { + remove_fd(n,events); + int i = nfds++; + if (i >= fd_array_size) { + FD *temp; + fd_array_size = 2*fd_array_size+1; + + if (!fd) temp = (FD*)malloc(fd_array_size*sizeof(FD)); + else temp = (FD*)realloc(fd, fd_array_size*sizeof(FD)); + + if (!temp) return; + fd = temp; + +# if USE_POLL + pollfd *tpoll; + + if (!pollfds) tpoll = (pollfd*)malloc(fd_array_size*sizeof(pollfd)); + else tpoll = (pollfd*)realloc(pollfds, fd_array_size*sizeof(pollfd)); + + if (!tpoll) return; + pollfds = tpoll; +# endif + } + fd[i].cb = cb; + fd[i].arg = v; +# if USE_POLL + pollfds[i].fd = n; + pollfds[i].events = events; +# else + fd[i].fd = n; + fd[i].events = events; + if (events & POLLIN) FD_SET(n, &fdsets[0]); + if (events & POLLOUT) FD_SET(n, &fdsets[1]); + if (events & POLLERR) FD_SET(n, &fdsets[2]); + if (n > maxfd) maxfd = n; +# endif +} + +void Fl_Unix_System_Driver::add_fd(int n, void (*cb)(int, void*), void* v) { + add_fd(n, POLLIN, cb, v); +} + +void Fl_Unix_System_Driver::remove_fd(int n, int events) { + int i,j; +# if !USE_POLL + maxfd = -1; // recalculate maxfd on the fly +# endif + for (i=j=0; i<nfds; i++) { +# if USE_POLL + if (pollfds[i].fd == n) { + int e = pollfds[i].events & ~events; + if (!e) continue; // if no events left, delete this fd + pollfds[j].events = e; + } +# else + if (fd[i].fd == n) { + int e = fd[i].events & ~events; + if (!e) continue; // if no events left, delete this fd + fd[i].events = e; + } + if (fd[i].fd > maxfd) maxfd = fd[i].fd; +# endif + // move it down in the array if necessary: + if (j<i) { + fd[j] = fd[i]; +# if USE_POLL + pollfds[j] = pollfds[i]; +# endif + } + j++; + } + nfds = j; +# if !USE_POLL + if (events & POLLIN) FD_CLR(n, &fdsets[0]); + if (events & POLLOUT) FD_CLR(n, &fdsets[1]); + if (events & POLLERR) FD_CLR(n, &fdsets[2]); +# endif +} + +void Fl_Unix_System_Driver::remove_fd(int n) { + remove_fd(n, -1); +} + + +// these pointers are set by the Fl::lock() function: +static void nothing() {} +void (*fl_lock_function)() = nothing; +void (*fl_unlock_function)() = nothing; + + +// This is never called with time_to_wait < 0.0: +// It should return negative on error, 0 if nothing happens before +// timeout, and >0 if any callbacks were done. +int Fl_Unix_System_Driver::poll_or_select_with_delay(double time_to_wait) { +# if !USE_POLL + fd_set fdt[3]; + fdt[0] = fdsets[0]; + fdt[1] = fdsets[1]; + fdt[2] = fdsets[2]; +# endif + int n; + + fl_unlock_function(); + + if (time_to_wait < 2147483.648) { +# if USE_POLL + n = ::poll(pollfds, nfds, int(time_to_wait*1000 + .5)); +# else + timeval t; + t.tv_sec = int(time_to_wait); + t.tv_usec = int(1000000 * (time_to_wait-t.tv_sec)); + n = ::select(maxfd+1,&fdt[0],&fdt[1],&fdt[2],&t); +# endif + } else { +# if USE_POLL + n = ::poll(pollfds, nfds, -1); +# else + n = ::select(maxfd+1,&fdt[0],&fdt[1],&fdt[2],0); +# endif + } + + fl_lock_function(); + + if (n > 0) { + for (int i=0; i<nfds; i++) { +# if USE_POLL + if (pollfds[i].revents) fd[i].cb(pollfds[i].fd, fd[i].arg); +# else + int f = fd[i].fd; + short revents = 0; + if (FD_ISSET(f,&fdt[0])) revents |= POLLIN; + if (FD_ISSET(f,&fdt[1])) revents |= POLLOUT; + if (FD_ISSET(f,&fdt[2])) revents |= POLLERR; + if (fd[i].events & revents) fd[i].cb(f, fd[i].arg); +# endif + } + } + return n; +} + +int Fl_Unix_System_Driver::poll_or_select() { + if (!nfds) return 0; // nothing to select or poll +# if USE_POLL + return ::poll(pollfds, nfds, 0); +# else + timeval t; + t.tv_sec = 0; + t.tv_usec = 0; + fd_set fdt[3]; + fdt[0] = fdsets[0]; + fdt[1] = fdsets[1]; + fdt[2] = fdsets[2]; + return ::select(maxfd+1,&fdt[0],&fdt[1],&fdt[2],&t); +# endif +} + + +double Fl_Unix_System_Driver::wait(double time_to_wait) +{ + if (time_to_wait <= 0.0) { + // do flush second so that the results of events are visible: + int ret = this->poll_or_select_with_delay(0.0); + Fl::flush(); + return ret; + } else { + // do flush first so that user sees the display: + Fl::flush(); + if (Fl::idle) // 'idle' may have been set within flush() + time_to_wait = 0.0; + else { + Fl_Timeout::elapse_timeouts(); + time_to_wait = Fl_Timeout::time_to_wait(time_to_wait); + } + return this->poll_or_select_with_delay(time_to_wait); + } +} + +int Fl_Unix_System_Driver::ready() +{ + Fl_Timeout::elapse_timeouts(); + if (Fl_Timeout::time_to_wait(1.0) <= 0.0) return 1; + return this->poll_or_select(); +} + + +static void write_short(unsigned char **cp, short i) { + unsigned char *c = *cp; + *c++ = i & 0xFF; i >>= 8; + *c++ = i & 0xFF; + *cp = c; +} + +static void write_int(unsigned char **cp, int i) { + unsigned char *c = *cp; + *c++ = i & 0xFF; i >>= 8; + *c++ = i & 0xFF; i >>= 8; + *c++ = i & 0xFF; i >>= 8; + *c++ = i & 0xFF; + *cp = c; +} + + +unsigned char *Fl_Unix_System_Driver::create_bmp(const unsigned char *data, int W, int H, int *return_size) { + int R = ((3*W+3)/4) * 4; // the number of bytes per row, rounded up to multiple of 4 + int s=H*R; + int fs=14+40+s; + unsigned char *b=new unsigned char[fs]; + unsigned char *c=b; + // BMP header + *c++='B'; + *c++='M'; + write_int(&c,fs); + write_int(&c,0); + write_int(&c,14+40); + // DIB header: + write_int(&c,40); + write_int(&c,W); + write_int(&c,H); + write_short(&c,1); + write_short(&c,24);//bits ber pixel + write_int(&c,0);//RGB + write_int(&c,s); + write_int(&c,0);// horizontal resolution + write_int(&c,0);// vertical resolution + write_int(&c,0);//number of colors. 0 -> 1<<bits_per_pixel + write_int(&c,0); + // Pixel data + data+=3*W*H; + for (int y=0;y<H;++y){ + data-=3*W; + const unsigned char *s=data; + unsigned char *p=c; + for (int x=0;x<W;++x){ + *p++=s[2]; + *p++=s[1]; + *p++=s[0]; + s+=3; + } + c+=R; + } + *return_size = fs; + return b; +} + + +static void read_int(uchar *c, int& i) { + i = *c; + i |= (*(++c))<<8; + i |= (*(++c))<<16; + i |= (*(++c))<<24; +} + + +// turn BMP image FLTK produced by create_bmp() back to Fl_RGB_Image +Fl_RGB_Image *Fl_Unix_System_Driver::own_bmp_to_RGB(char *bmp) { + int w, h; + read_int((uchar*)bmp + 18, w); + read_int((uchar*)bmp + 22, h); + int R=((3*w+3)/4) * 4; // the number of bytes per row, rounded up to multiple of 4 + bmp += 54; + uchar *data = new uchar[w*h*3]; + uchar *p = data; + for (int i = h-1; i >= 0; i--) { + char *s = bmp + i * R; + for (int j = 0; j < w; j++) { + *p++=s[2]; + *p++=s[1]; + *p++=s[0]; + s+=3; + } + } + Fl_RGB_Image *img = new Fl_RGB_Image(data, w, h, 3); + img->alloc_array = 1; + return img; +} + + +void *Fl_Unix_System_Driver::control_maximize_button(void *data) { + return NULL; +} 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) diff --git a/src/drivers/X11/Fl_X11_System_Driver.H b/src/drivers/X11/Fl_X11_System_Driver.H index e39f40706..a5347d74d 100644 --- a/src/drivers/X11/Fl_X11_System_Driver.H +++ b/src/drivers/X11/Fl_X11_System_Driver.H @@ -19,47 +19,23 @@ #define FL_X11_SYSTEM_DRIVER_H #include <config.h> -#include "../Posix/Fl_Posix_System_Driver.H" +#include "../Unix/Fl_Unix_System_Driver.H" -class Fl_X11_System_Driver : public Fl_Posix_System_Driver { +class Fl_X11_System_Driver : public Fl_Unix_System_Driver { public: - Fl_X11_System_Driver() : Fl_Posix_System_Driver() { + Fl_X11_System_Driver() : Fl_Unix_System_Driver() { // X11 system driver does not use a key table key_table = NULL; key_table_size = 0; } virtual void display_arg(const char *arg); virtual int XParseGeometry(const char*, int*, int*, unsigned int*, unsigned int*); - virtual int clocale_printf(FILE *output, const char *format, va_list args); - virtual int clocale_snprintf(char *output, size_t output_size, const char *format, va_list args); - virtual int clocale_sscanf(const char *input, const char *format, va_list args); // these 2 are in Fl_get_key.cxx virtual int event_key(int k); virtual int get_key(int k); - virtual int filename_list(const char *d, dirent ***list, - int (*sort)(struct dirent **, struct dirent **), - char *errmsg=NULL, int errmsg_sz=0); virtual int need_menu_handle_part1_extra() {return 1;} - virtual int open_uri(const char *uri, char *msg, int msglen); - virtual int use_tooltip_timeout_condition() {return 1;} - // this one is in fl_shortcut.cxx virtual const char *shortcut_add_key_name(unsigned key, char *p, char *buf, const char **); - virtual int file_browser_load_filesystem(Fl_File_Browser *browser, char *filename, int lname, Fl_File_Icon *icon); - virtual void newUUID(char *uuidBuffer); - virtual char *preference_rootnode(Fl_Preferences *prefs, Fl_Preferences::Root root, const char *vendor, - const char *application); - virtual int preferences_need_protection_check() {return 1;} - virtual int utf8locale(); - // this one is in Fl_own_colormap.cxx virtual void own_colormap(); - // this one is in Fl_x.cxx - virtual const char *filename_name(const char *buf); - virtual void add_fd(int fd, int when, Fl_FD_Handler cb, void* = 0); - virtual void add_fd(int fd, Fl_FD_Handler cb, void* = 0); - virtual void remove_fd(int, int when); - virtual void remove_fd(int); - virtual double wait(double time_to_wait); - virtual int ready(); // 2 additional virtual members virtual int poll_or_select(); virtual int poll_or_select_with_delay(double time_to_wait); diff --git a/src/drivers/X11/Fl_X11_System_Driver.cxx b/src/drivers/X11/Fl_X11_System_Driver.cxx index e62fdf8b8..40a98d32e 100644 --- a/src/drivers/X11/Fl_X11_System_Driver.cxx +++ b/src/drivers/X11/Fl_X11_System_Driver.cxx @@ -80,58 +80,6 @@ Fl_System_Driver *Fl_System_Driver::newSystemDriver() return new Fl_X11_System_Driver(); } -#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 -static locale_t c_locale = NULL; -#endif - - -int Fl_X11_System_Driver::clocale_printf(FILE *output, const char *format, va_list args) { -#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 - if (!c_locale) - c_locale = newlocale(LC_NUMERIC_MASK, "C", duplocale(LC_GLOBAL_LOCALE)); - locale_t previous_locale = uselocale(c_locale); - int retval = vfprintf(output, format, args); - uselocale(previous_locale); -#else - char *saved_locale = setlocale(LC_NUMERIC, NULL); - setlocale(LC_NUMERIC, "C"); - int retval = vfprintf(output, format, args); - setlocale(LC_NUMERIC, saved_locale); -#endif - return retval; -} - -int Fl_X11_System_Driver::clocale_snprintf(char *output, size_t output_size, const char *format, va_list args) { -#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 - if (!c_locale) - c_locale = newlocale(LC_NUMERIC_MASK, "C", duplocale(LC_GLOBAL_LOCALE)); - locale_t previous_locale = uselocale(c_locale); - int retval = vsnprintf(output, output_size, format, args); - uselocale(previous_locale); -#else - char *saved_locale = setlocale(LC_NUMERIC, NULL); - setlocale(LC_NUMERIC, "C"); - int retval = vsnprintf(output, output_size, format, args); - setlocale(LC_NUMERIC, saved_locale); -#endif - return retval; -} - -int Fl_X11_System_Driver::clocale_sscanf(const char *input, const char *format, va_list args) { -#if defined(__linux__) && defined(_XOPEN_SOURCE) && _XOPEN_SOURCE >= 700 - if (!c_locale) - c_locale = newlocale(LC_NUMERIC_MASK, "C", duplocale(LC_GLOBAL_LOCALE)); - locale_t previous_locale = uselocale(c_locale); - int retval = vsscanf(input, format, args); - uselocale(previous_locale); -#else - char *saved_locale = setlocale(LC_NUMERIC, NULL); - setlocale(LC_NUMERIC, "C"); - int retval = vsscanf(input, format, args); - setlocale(LC_NUMERIC, saved_locale); -#endif - return retval; -} // Find a program in the path... static char *path_find(const char *program, char *filename, int filesize) { @@ -166,376 +114,6 @@ static char *path_find(const char *program, char *filename, int filesize) { } -int Fl_X11_System_Driver::open_uri(const char *uri, char *msg, int msglen) -{ - // Run any of several well-known commands to open the URI. - // - // We give preference to the Portland group's xdg-utils - // programs which run the user's preferred web browser, etc. - // based on the current desktop environment in use. We fall - // back on older standards and then finally test popular programs - // until we find one we can use. - // - // Note that we specifically do not support the MAILER and - // BROWSER environment variables because we have no idea whether - // we need to run the listed commands in a terminal program. - char command[FL_PATH_MAX], // Command to run... - *argv[4], // Command-line arguments - remote[1024]; // Remote-mode command... - const char * const *commands; // Array of commands to check... - int i; - static const char * const browsers[] = { - "xdg-open", // Portland - "htmlview", // Freedesktop.org - "firefox", - "mozilla", - "netscape", - "konqueror", // KDE - "opera", - "hotjava", // Solaris - "mosaic", - NULL - }; - static const char * const readers[] = { - "xdg-email", // Portland - "thunderbird", - "mozilla", - "netscape", - "evolution", // GNOME - "kmailservice", // KDE - NULL - }; - static const char * const managers[] = { - "xdg-open", // Portland - "fm", // IRIX - "dtaction", // CDE - "nautilus", // GNOME - "konqueror", // KDE - NULL - }; - - // Figure out which commands to check for... - if (!strncmp(uri, "file://", 7)) commands = managers; - else if (!strncmp(uri, "mailto:", 7) || - !strncmp(uri, "news:", 5)) commands = readers; - else commands = browsers; - - // Find the command to run... - for (i = 0; commands[i]; i ++) - if (path_find(commands[i], command, sizeof(command))) break; - - if (!commands[i]) { - if (msg) { - snprintf(msg, msglen, "No helper application found for \"%s\"", uri); - } - - return 0; - } - - // Handle command-specific arguments... - argv[0] = (char *)commands[i]; - - if (!strcmp(commands[i], "firefox") || - !strcmp(commands[i], "mozilla") || - !strcmp(commands[i], "netscape") || - !strcmp(commands[i], "thunderbird")) { - // program -remote openURL(uri) - snprintf(remote, sizeof(remote), "openURL(%s)", uri); - - argv[1] = (char *)"-remote"; - argv[2] = remote; - argv[3] = 0; - } else if (!strcmp(commands[i], "dtaction")) { - // dtaction open uri - argv[1] = (char *)"open"; - argv[2] = (char *)uri; - argv[3] = 0; - } else { - // program uri - argv[1] = (char *)uri; - argv[2] = 0; - } - - if (msg) { - strlcpy(msg, argv[0], msglen); - - for (i = 1; argv[i]; i ++) { - strlcat(msg, " ", msglen); - strlcat(msg, argv[i], msglen); - } - } - - return run_program(command, argv, msg, msglen) != 0; -} - - -int Fl_X11_System_Driver::file_browser_load_filesystem(Fl_File_Browser *browser, char *filename, int lname, Fl_File_Icon *icon) -{ - int num_files = 0; -#if defined(_AIX) - // AIX don't write the mounted filesystems to a file like '/etc/mnttab'. - // But reading the list of mounted filesystems from the kernel is possible: - // http://publib.boulder.ibm.com/infocenter/pseries/v5r3/topic/com.ibm.aix.basetechref/doc/basetrf1/mntctl.htm - int res = -1, len; - char *list = NULL, *name; - struct vmount *vp; - - // We always have the root filesystem - add("/", icon); - // Get the required buffer size for the vmount structures - res = mntctl(MCTL_QUERY, sizeof(len), (char *) &len); - if (!res) { - // Allocate buffer ... - list = (char *) malloc((size_t) len); - if (NULL == list) { - res = -1; - } else { - // ... and read vmount structures from kernel - res = mntctl(MCTL_QUERY, len, list); - if (0 >= res) { - res = -1; - } else { - for (int i = 0, vp = (struct vmount *) list; i < res; ++i) { - name = (char *) vp + vp->vmt_data[VMT_STUB].vmt_off; - strlcpy(filename, name, lname); - // Skip the already added root filesystem - if (strcmp("/", filename) != 0) { - strlcat(filename, "/", lname); - browser->add(filename, icon); - } - vp = (struct vmount *) ((char *) vp + vp->vmt_length); - } - } - } - } - // Note: Executing 'free(NULL)' is allowed and simply do nothing - free((void *) list); -#elif defined(__NetBSD__) && defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 300000000) - // NetBSD don't write the mounted filesystems to a file like '/etc/mnttab'. - // Since NetBSD 3.0 the system call getvfsstat(2) has replaced getfsstat(2) - // that is used by getmntinfo(3): - // http://www.daemon-systems.org/man/getmntinfo.3.html - int res = -1; - struct statvfs *list; - - // We always have the root filesystem - browser->add("/", icon); -# ifdef HAVE_PTHREAD - // Lock mutex for thread safety - if (!pthread_mutex_lock(&getvfsstat_mutex)) { -# endif // HAVE_PTHREAD - // Get list of statvfs structures - res = getmntinfo(&list, ST_WAIT); - if (0 < res) { - for (int i = 0; i < res; ++i) { - strlcpy(filename, list[i].f_mntonname, lname); - // Skip the already added root filesystem - if (strcmp("/", filename) != 0) { - strlcat(filename, "/", lname); - browser->add(filename, icon); - } - } - } else { - res = -1; - } -# ifdef HAVE_PTHREAD - pthread_mutex_unlock(&getvfsstat_mutex); - } -# endif // HAVE_PTHREAD -#else - // - // UNIX code uses /etc/fstab or similar... - // - FILE *mtab; // /etc/mtab or /etc/mnttab file - char line[FL_PATH_MAX]; // Input line - - // Every Unix has a root filesystem '/'. - // This ensures that the user don't get an empty - // window after requesting filesystem list. - browser->add("/", icon); - num_files ++; - - // - // Open the file that contains a list of mounted filesystems... - // - // Note: this misses automounted filesystems on FreeBSD if absent from /etc/fstab - // - - mtab = fopen("/etc/mnttab", "r"); // Fairly standard - if (mtab == NULL) - mtab = fopen("/etc/mtab", "r"); // More standard - if (mtab == NULL) - mtab = fopen("/etc/fstab", "r"); // Otherwise fallback to full list - if (mtab == NULL) - mtab = fopen("/etc/vfstab", "r"); // Alternate full list file - - if (mtab != NULL) - { - while (fgets(line, sizeof(line), mtab) != NULL) - { - if (line[0] == '#' || line[0] == '\n') - continue; - if (sscanf(line, "%*s%4095s", filename) != 1) - continue; - if (strcmp("/", filename) == 0) - continue; // "/" was added before - - // Add a trailing slash (except for the root filesystem) - strlcat(filename, "/", lname); - - // printf("Fl_File_Browser::load() - adding \"%s\" to list...\n", filename); - browser->add(filename, icon); - num_files ++; - } - - fclose(mtab); - } -#endif // _AIX || ... - return num_files; -} - -void Fl_X11_System_Driver::newUUID(char *uuidBuffer) -{ - unsigned char b[16]; -#if HAVE_DLSYM && HAVE_DLFCN_H - typedef void (*gener_f_type)(uchar*); - static bool looked_for_uuid_generate = false; - static gener_f_type uuid_generate_f = NULL; - if (!looked_for_uuid_generate) { - looked_for_uuid_generate = true; - uuid_generate_f = (gener_f_type)Fl_Posix_System_Driver::dlopen_or_dlsym("libuuid", "uuid_generate"); - } - if (uuid_generate_f) { - uuid_generate_f(b); - } else -#endif - { - time_t t = time(0); // first 4 byte - b[0] = (unsigned char)t; - b[1] = (unsigned char)(t>>8); - b[2] = (unsigned char)(t>>16); - b[3] = (unsigned char)(t>>24); - int r = rand(); // four more bytes - b[4] = (unsigned char)r; - b[5] = (unsigned char)(r>>8); - b[6] = (unsigned char)(r>>16); - b[7] = (unsigned char)(r>>24); - unsigned long a = (unsigned long)&t; // four more bytes - b[8] = (unsigned char)a; - b[9] = (unsigned char)(a>>8); - b[10] = (unsigned char)(a>>16); - b[11] = (unsigned char)(a>>24); - // Now we try to find 4 more "random" bytes. We extract the - // lower 4 bytes from the address of t - it is created on the - // stack so *might* be in a different place each time... - // This is now done via a union to make it compile OK on 64-bit systems. - union { void *pv; unsigned char a[sizeof(void*)]; } v; - v.pv = (void *)(&t); - // NOTE: May need to handle big- or little-endian systems here -# if WORDS_BIGENDIAN - b[8] = v.a[sizeof(void*) - 1]; - b[9] = v.a[sizeof(void*) - 2]; - b[10] = v.a[sizeof(void*) - 3]; - b[11] = v.a[sizeof(void*) - 4]; -# else // data ordered for a little-endian system - b[8] = v.a[0]; - b[9] = v.a[1]; - b[10] = v.a[2]; - b[11] = v.a[3]; -# endif - char name[80]; // last four bytes - gethostname(name, 79); - memcpy(b+12, name, 4); - } - sprintf(uuidBuffer, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", - b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], - b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]); -} - -/* - Note: `prefs` can be NULL! - */ -char *Fl_X11_System_Driver::preference_rootnode(Fl_Preferences * /*prefs*/, - Fl_Preferences::Root root, - const char *vendor, - const char *application) -{ - static char *filename = 0L; - if (!filename) filename = (char*)::calloc(1, FL_PATH_MAX); - const char *home = ""; - int pref_type = root & Fl_Preferences::ROOT_MASK; - switch (pref_type) { - case Fl_Preferences::USER: - home = getenv("HOME"); - // make sure that $HOME is set to an existing directory - if ((home == NULL) || (home[0] == 0) || (::access(home, F_OK) == -1)) { - struct passwd *pw = getpwuid(getuid()); - if (pw) - home = pw->pw_dir; - } - if ((home == 0L) || (home[0] == 0) || (::access(home, F_OK) == -1)) - return NULL; - strlcpy(filename, home, FL_PATH_MAX); - if (filename[strlen(filename) - 1] != '/') - strlcat(filename, "/", FL_PATH_MAX); - strlcat(filename, ".fltk/", FL_PATH_MAX); - break; - case Fl_Preferences::SYSTEM: - strcpy(filename, "/etc/fltk/"); - break; - default: // MEMORY - filename[0] = '\0'; // empty string - break; - } - - // Make sure that the parameters are not NULL - if ( (vendor==NULL) || (vendor[0]==0) ) - vendor = "unknown"; - if ( (application==NULL) || (application[0]==0) ) - application = "unknown"; - - snprintf(filename + strlen(filename), FL_PATH_MAX - strlen(filename), - "%s/%s.prefs", vendor, application); - - // If this is not the USER path (i.e. SYSTEM or MEMORY), we are done - if ((pref_type) != Fl_Preferences::USER) - return filename; - - // If the legacy file exists, we are also done - if (::access(filename, F_OK)==0) - return filename; - - // This is USER mode, and there is no legacy file. Create an XDG conforming path. - // Check $XDG_CONFIG_HOME, and if it isn't set, default to $HOME/.config - const char *xdg = getenv("XDG_CONFIG_HOME"); - if (xdg==NULL) { - xdg = "~/.config"; - } - filename[0] = 0; - if (strncmp(xdg, "~/", 2)==0) { - strlcpy(filename, home, FL_PATH_MAX); - strlcat(filename, "/", FL_PATH_MAX); - strlcat(filename, xdg+2, FL_PATH_MAX); - } else if (strncmp(xdg, "$HOME/", 6)==0) { - strlcpy(filename, home, FL_PATH_MAX); - strlcat(filename, "/", FL_PATH_MAX); - strlcat(filename, xdg+6, FL_PATH_MAX); - } else if (strncmp(xdg, "${HOME}/", 8)==0) { - strlcpy(filename, home, FL_PATH_MAX); - strlcat(filename, "/", FL_PATH_MAX); - strlcat(filename, xdg+8, FL_PATH_MAX); - } else { - strlcpy(filename, xdg, FL_PATH_MAX); - } - strlcat(filename, "/", FL_PATH_MAX); - strlcat(filename, vendor, FL_PATH_MAX); - strlcat(filename, "/", FL_PATH_MAX); - strlcat(filename, application, FL_PATH_MAX); - strlcat(filename, ".prefs", FL_PATH_MAX); - - return filename; -} - void Fl_X11_System_Driver::display_arg(const char *arg) { Fl::display(arg); } @@ -545,112 +123,6 @@ int Fl_X11_System_Driver::XParseGeometry(const char* string, int* x, int* y, return ::XParseGeometry(string, x, y, width, height); } -// -// Needs some docs -// Returns -1 on error, errmsg will contain OS error if non-NULL. -// -int Fl_X11_System_Driver::filename_list(const char *d, - dirent ***list, - int (*sort)(struct dirent **, struct dirent **), - char *errmsg, int errmsg_sz) { - int dirlen; - char *dirloc; - - if (errmsg && errmsg_sz>0) errmsg[0] = '\0'; - - // Assume that locale encoding is no less dense than UTF-8 - dirlen = strlen(d); - dirloc = (char *)malloc(dirlen + 1); - fl_utf8to_mb(d, dirlen, dirloc, dirlen + 1); - -#ifndef HAVE_SCANDIR - // This version is when we define our own scandir. Note it updates errmsg on errors. - int n = fl_scandir(dirloc, list, 0, sort, errmsg, errmsg_sz); -#elif defined(HAVE_SCANDIR_POSIX) - // POSIX (2008) defines the comparison function like this: - int n = scandir(dirloc, list, 0, (int(*)(const dirent **, const dirent **))sort); -#elif defined(__osf__) - // OSF, DU 4.0x - int n = scandir(dirloc, list, 0, (int(*)(dirent **, dirent **))sort); -#elif defined(_AIX) - // AIX is almost standard... - int n = scandir(dirloc, list, 0, (int(*)(void*, void*))sort); -#elif defined(__sgi) - int n = scandir(dirloc, list, 0, sort); -#else - // The vast majority of UNIX systems want the sort function to have this - // prototype, most likely so that it can be passed to qsort without any - // changes: - int n = scandir(dirloc, list, 0, (int(*)(const void*,const void*))sort); -#endif - - free(dirloc); - - if (n==-1) { - // Don't write to errmsg if FLTK's fl_scandir() already set it. - // If OS's scandir() was used (HAVE_SCANDIR), we return its error in errmsg here.. -#ifdef HAVE_SCANDIR - if (errmsg) fl_snprintf(errmsg, errmsg_sz, "%s", strerror(errno)); -#endif - return -1; - } - - // convert every filename to UTF-8, and append a '/' to all - // filenames that are directories - int i; - char *fullname = (char*)malloc(dirlen+FL_PATH_MAX+3); // Add enough extra for two /'s and a nul - // Use memcpy for speed since we already know the length of the string... - memcpy(fullname, d, dirlen+1); - - char *name = fullname + dirlen; - if (name!=fullname && name[-1]!='/') - *name++ = '/'; - - for (i=0; i<n; i++) { - int newlen; - dirent *de = (*list)[i]; - int len = strlen(de->d_name); - newlen = fl_utf8from_mb(NULL, 0, de->d_name, len); - dirent *newde = (dirent*)malloc(de->d_name - (char*)de + newlen + 2); // Add space for a / and a nul - - // Conversion to UTF-8 - memcpy(newde, de, de->d_name - (char*)de); - fl_utf8from_mb(newde->d_name, newlen + 1, de->d_name, len); - - // Check if dir (checks done on "old" name as we need to interact with - // the underlying OS) - if (de->d_name[len-1]!='/' && len<=FL_PATH_MAX) { - // Use memcpy for speed since we already know the length of the string... - memcpy(name, de->d_name, len+1); - if (fl_filename_isdir(fullname)) { - char *dst = newde->d_name + newlen; - *dst++ = '/'; - *dst = 0; - } - } - - free(de); - (*list)[i] = newde; - } - free(fullname); - - return n; -} - -int Fl_X11_System_Driver::utf8locale() { - static int ret = 2; - if (ret == 2) { - char* s; - ret = 1; /* assume UTF-8 if no locale */ - if (((s = getenv("LC_CTYPE")) && *s) || - ((s = getenv("LC_ALL")) && *s) || - ((s = getenv("LANG")) && *s)) { - ret = (strstr(s,"utf") || strstr(s,"UTF")); - } - } - return ret; -} - #if !defined(FL_DOXYGEN) @@ -700,30 +172,4 @@ void Fl_X11_System_Driver::own_colormap() { #endif // USE_COLORMAP } -int Fl_X11_System_Driver::ready() { - Fl_Timeout::elapse_timeouts(); - if (Fl_Timeout::time_to_wait(1.0) <= 0.0) return 1; - return this->poll_or_select(); -} - -double Fl_X11_System_Driver::wait(double time_to_wait) -{ - if (time_to_wait <= 0.0) { - // do flush second so that the results of events are visible: - int ret = this->poll_or_select_with_delay(0.0); - Fl::flush(); - return ret; - } else { - // do flush first so that user sees the display: - Fl::flush(); - if (Fl::idle) // 'idle' may have been set within flush() - time_to_wait = 0.0; - else { - Fl_Timeout::elapse_timeouts(); - time_to_wait = Fl_Timeout::time_to_wait(time_to_wait); - } - return this->poll_or_select_with_delay(time_to_wait); - } -} - #endif // !defined(FL_DOXYGEN) |
