[enable_multimon="$enableval"],
[enable_multimon="yes"])
+AC_ARG_WITH(
+ gtk4,
+ AS_HELP_STRING(
+ [--without-gtk4],
+ [compiles without Gtk 4.0]),
+ [with_gtk4="$withval"],
+ [with_gtk4="auto"])
+
AC_ARG_WITH(
gtk3,
AS_HELP_STRING(
[with_gtk2="auto"])
+# only one "with_gtkX" option allowed
+num_gtk_args=0
+if test "$with_gtk4" = "yes" ; then
+ (( num_gtk_args += 1 ))
+fi
+if test "$with_gtk3" = "yes" ; then
+ (( num_gtk_args += 1 ))
+fi
+if test "$with_gtk2" = "yes" ; then
+ (( num_gtk_args += 1 ))
+fi
+if (( $num_gtk_args > 1 )); then
+ AC_MSG_ERROR('cannot set multiple "with-gtkX" option')
+fi
+
+# any "with_gtkX option is ignored if "no_x" is set
if test "$no_x" = "yes" ; then
with_gtk2="no"
with_gtk3="no"
-else
- if test "$with_gtk2" = "auto" ; then
- if test "$with_gtk3" = "auto" ; then
- with_gtk2="no"
- with_gtk3="yes"
- elif test "$with_gtk3" = "no" ; then
- with_gtk2="yes"
- elif test "$with_gtk3" = "yes" ; then
- with_gtk2="no"
- fi
- elif test "$with_gtk2" = "no" ; then
- if test "$with_gtk3" = "auto" ; then
- with_gtk3="yes"
- elif test "$with_gtk3" = "no" ; then
- AC_MSG_ERROR('need either gtk2 or gtk3 or build with --without-x to disable building desktop plugins')
- fi
- elif test "$with_gtk2" = "yes" ; then
- if test "$with_gtk3" = "auto" ; then
- with_gtk3="no"
- elif test "$with_gtk3" = "yes" ; then
- AC_MSG_ERROR('cannot set both --with-gtk2 and --with-gtk3')
- fi
+ with_gtk4="no"
+ if (( $num_gtk_args > 0 )); then
+ AC_MSG_WARN('"with-gtkX" option is ignored when "no_x" option is set')
fi
fi
-if test "$with_gtk2" = "no" ; then
- with_gtkmm="no"
-fi
-
-if test "$with_gtk3" = "no" ; then
- with_gtkmm3="no"
-fi
-
-if test "$with_gtk3" = "yes"; then
- AC_ARG_WITH(
- gtkmm3,
- AS_HELP_STRING(
- [--without-gtkmm3],
- [compiles without Gtkmm 3, sigc++, and related libs]),
- [with_gtkmm3="$withval"],
- [with_gtkmm3="yes"])
- with_gtkmm="no"
-elif test "$with_gtk2" = "yes"; then
- AC_ARG_WITH(
- gtkmm,
- AS_HELP_STRING(
- [--without-gtkmm],
- [compiles without Gtkmm, sigc++, and related libs]),
- [with_gtkmm="$withval"],
- [with_gtkmm="yes"])
- with_gtkmm3="no"
+# use the gtk version if the option is set with "yes" explicitly
+if test "$with_gtk4" = "yes" ; then
+ with_gtk2="no"
+ with_gtk3="no"
+elif test "$with_gtk3" = "yes" ; then
+ with_gtk2="no"
+ with_gtk4="no"
+elif test "$with_gtk2" = "yes" ; then
+ with_gtk3="no"
+ with_gtk4="no"
fi
AC_ARG_ENABLE(
have_xcomposite="yes"
fi
+ # Check if the gtk lib installed matches with gtkN option
+ have_gtk="no"
+ # Check whether we have gtk+ 4.0
+ if test "$with_gtk4" != "no"; then
+ # gdk_x11_display_get_default_group (added in gtk+ 4.0) is function currently
+ # needed by vmware-user.
+ AC_VMW_CHECK_LIB([gtk-4],
+ [GTK],
+ [gtk4],
+ [],
+ [4.0.0],
+ [gtk/x11/gtkx.h],
+ [gdk_x11_display_get_default_group],
+ [with_gtk4="yes"
+ have_gtk="yes"
+ GTK_CPPFLAGS="$GTK_CPPFLAGS -DGTK4 -DGDK_DISABLE_DEPRECATED -DGTK_DISABLE_DEPRECATED"
+ with_gtk3="no"
+ with_gtk2="no"],
+ [AS_IF([test $with_gtk4 = "auto"],
+ [AC_MSG_WARN([Gtk+ 4.0 library not found or devel package not found. Please configure without Gtk+ support (using --without-gtk4) or install the Gtk+ 4.0 devel package.])
+ with_gtk4="no"],
+ [AC_MSG_ERROR([Gtk+ 4.0 library not found or devel package not found. Please configure without Gtk+ support (using --without-gtk4) or install the Gtk+ 4.0 devel package.])]
+ )]
+ )
+ fi
+
# Check whether we have gtk+ 3.0.
if test "$with_gtk3" != "no"; then
# gdk_display_get_default_group (added in gtk+ 2.4.0) is function currently
[3.0.0],
[gtk/gtk.h],
[gdk_display_get_default_group],
- [GTK_CPPFLAGS="$GTK_CPPFLAGS -DGTK3"],
- [AC_MSG_ERROR([Gtk+ 3.0 library not found or too old. Please configure without Gtk+ support (using --without-gtk3) or install the Gtk+ 3.0 devel package.])])
+ [with_gtk3="yes"
+ have_gtk="yes"
+ GTK_CPPFLAGS="$GTK_CPPFLAGS -DGTK3"
+ with_gtk2="no"],
+ [AS_IF([test $with_gtk3 = "auto"],
+ [AC_MSG_WARN([Gtk+ 3.0 library not found or devel package not found. Please configure without Gtk+ support (using --without-gtk3) or install the Gtk+ 3.0 devel package.])
+ with_gtk3="no"],
+ [AC_MSG_ERROR([Gtk+ 3.0 library not found or devel package not found. Please configure without Gtk+ support (using --without-gtk3) or install the Gtk+ 3.0 devel package.])]
+ )]
+ )
+ fi
# Check whether we have gtk+ 2.0.
- elif test "$with_gtk2" != "no"; then
+ if test "$with_gtk2" != "no"; then
AC_VMW_CHECK_LIB([gtk-x11-2.0],
[GTK],
[gtk+-2.0],
[2.4.0],
[gtk/gtk.h],
[gdk_display_get_default_group],
- [GTK_CPPFLAGS="$GTK_CPPFLAGS -DGTK2"],
- [AC_MSG_ERROR([Gtk+ 2.0 library not found or too old. Please configure without Gtk+ support (using --without-gtk2) or install the Gtk+ 2.0 devel package.])])
+ [with_gtk2="yes"
+ have_gtk="yes"
+ GTK_CPPFLAGS="$GTK_CPPFLAGS -DGTK2"],
+ [AS_IF([test $with_gtk2 = "auto"],
+ [AC_MSG_WARN([Gtk+ 2.0 library not found or devel package not found. Please configure without Gtk+ support (using --without-gtk2) or install the Gtk+ 2.0 devel package.])
+ with_gtk2="no"],
+ [AC_MSG_ERROR([Gtk+ 2.0 library not found or devel package not found. Please configure without Gtk+ support (using --without-gtk2) or install the Gtk+ 2.0 devel package.])]
+ )]
+ )
+ fi
+
+ if test "$have_gtk" != "yes" && test "$no_x" != "yes"; then
+ AC_MSG_ERROR('need at least one of gtk2/gtk3/gtk4 available or build with --without-x to disable building desktop plugins')
+ fi
+
+ # Now set gtkmm version after gtkN version is determined
+ if test "$with_gtk4" = "yes"; then
+ AC_ARG_WITH(
+ gtkmm4,
+ AS_HELP_STRING(
+ [--without-gtkmm4],
+ [compiles without Gtkmm 4, sigc++, and related libs]),
+ [with_gtkmm4="$withval"],
+ [with_gtkmm4="yes"])
+ with_gtkmm="no"
+ with_gtkmm3="no"
+ elif test "$with_gtk3" = "yes"; then
+ AC_ARG_WITH(
+ gtkmm3,
+ AS_HELP_STRING(
+ [--without-gtkmm3],
+ [compiles without Gtkmm 3, sigc++, and related libs]),
+ [with_gtkmm3="$withval"],
+ [with_gtkmm3="yes"])
+ with_gtkmm="no"
+ with_gtkmm4="no"
+ elif test "$with_gtk2" = "yes"; then
+ AC_ARG_WITH(
+ gtkmm,
+ AS_HELP_STRING(
+ [--without-gtkmm],
+ [compiles without Gtkmm, sigc++, and related libs]),
+ [with_gtkmm="$withval"],
+ [with_gtkmm="yes"])
+ with_gtkmm3="no"
+ with_gtkmm4="no"
fi
#
# Check for gtkmm 2.4.0 or greater.
#
- if test "$with_gtkmm" != "no" -o "$with_gtkmm3" != "no"; then
+ if test "$with_gtkmm4" != "no"; then
+ AC_VMW_CHECK_LIBXX([gtkmm-4.0],
+ [GTKMM],
+ [gtkmm-4.0],
+ [],
+ [4.0.0],
+ [gtkmm.h],
+ [],
+ [GTKMM_CPPFLAGS="$GTKMM_CPPFLAGS -DHAVE_GTKMM"],
+ [AC_MSG_ERROR([gtkmm4 library not found. Please install the libgtkmm4 devel package(s), or re-configure using --without-gtkmm4.])])
+
+ #
+ # libsigc++-3.0 >= 3.0.0 requires C++17 support
+ #
+ AC_VMW_CHECK_LIBXX([sigc++-3.0],
+ [SIGCXX],
+ [sigc++-3.0],
+ [],
+ [3.0.0],
+ [sigc++.h],
+ [],
+ [SIGCXX_CPPFLAGS="$SIGCXX_CPPFLAGS -std=c++17"],
+ [SIGCXX_CPPFLAGS="$SIGCXX_CPPFLAGS"])
+ elif test "$with_gtkmm" != "no" -o "$with_gtkmm3" != "no"; then
if test "$with_gtkmm3" != "no"; then
AC_VMW_CHECK_LIBXX([gtkmm-3.0],
[GTKMM],
enable_vmwgfxctrl="no"
fi
+if test "$with_gtkmm" = "yes" || test "$with_gtkmm3" = "yes" || test "$with_gtkmm4" = "yes"; then
+ have_gtkmm="yes"
+else
+ have_gtkmm="no"
+ AC_MSG_WARN('dndcp plugin will not build without gtkmm')
+fi
AM_CONDITIONAL(LINUX, test "$os" = "linux")
AM_CONDITIONAL(SOLARIS, test "$os" = "solaris")
AM_CONDITIONAL(FREEBSD, test "$os" = "freebsd" -o "$os" = "kfreebsd-gnu")
AM_CONDITIONAL(HAVE_FUSE, test "$have_fuse" = "yes" || test "$have_fuse3" = "yes")
AM_CONDITIONAL(HAVE_FUSE3, test "$have_fuse3" = "yes")
AM_CONDITIONAL(HAVE_GNU_LD, test "$with_gnu_ld" = "yes")
-AM_CONDITIONAL(HAVE_GTKMM, test "$have_x" = "yes" -a \( "$with_gtkmm" = "yes" -o "$with_gtkmm3" = "yes" \) )
+AM_CONDITIONAL(HAVE_GTK4, test "$have_x" = "yes" && test "$with_gtk4" = "yes")
+AM_CONDITIONAL(HAVE_GTKMM, test "$have_x" = "yes" && test "$have_gtkmm" = "yes")
AM_CONDITIONAL(HAVE_PAM, test "$with_pam" = "yes")
AM_CONDITIONAL(USE_SLASH_PROC, test "$os" = "linux")
AM_CONDITIONAL(ENABLE_CONTAINERINFO, test "$enable_containerinfo" = "yes")
#include <stdlib.h>
#include <string.h>
-#include <gdk/gdkx.h>
#include <gtk/gtk.h>
#include <X11/Xlib.h>
+#if GTK_MAJOR_VERSION == 4
+#include <gdk/x11/gdkx.h>
+#else
+#include <gdk/gdkx.h>
+#endif
#define LOCK_ATOM_NAME "vmware-user-lock"
+#if GTK_MAJOR_VERSION == 4
+#define GDK_GET_XDISPLAY(X) gdk_x11_display_get_xdisplay(X)
+#else
+#define GDK_GET_XDISPLAY(X) gdk_x11_get_default_xdisplay()
+#endif
/*
Window myRootWindow;
XSetWindowAttributes attr;
GdkDisplay *gdkDisplay;
+#if GTK_MAJOR_VERSION == 4
+ GdkSurface *gdkLeader;
+#else
GdkWindow *gdkLeader;
+#endif
attr.override_redirect = True;
ASSERT(rootWindow);
gdkDisplay = gdk_display_get_default();
+#if GTK_MAJOR_VERSION == 4
+ gdkLeader = gdk_x11_display_get_default_group(gdkDisplay);
+ myGroupLeader = gdk_x11_surface_get_xid(gdkLeader);
+ /*
+ * GTK4 deprecated the idea of root windows
+ * https://docs.gtk.org/gtk4/migrating-3to4.html#stop-using-the-root-window
+ */
+ myRootWindow = gdk_x11_display_get_xrootwindow(gdkDisplay);
+#else
gdkLeader = gdk_display_get_default_group(gdkDisplay);
myGroupLeader = GDK_WINDOW_XID(gdkLeader);
myRootWindow = GDK_ROOT_WINDOW();
+#endif
ASSERT(myGroupLeader);
ASSERT(myRootWindow);
/* XXX: With g_set_prgname() being called, this can probably go away. */
- XStoreName(gdk_x11_get_default_xdisplay(), myGroupLeader, VMUSER_TITLE);
+ XStoreName(GDK_GET_XDISPLAY(gdkDisplay), myGroupLeader, VMUSER_TITLE);
/*
* Confidence check: Set the override redirect property on our group leader
* This makes sure that (a) a window manager can't re-parent our window,
* and (b) that we remain a top-level window.
*/
- XChangeWindowAttributes(gdk_x11_get_default_xdisplay(), myGroupLeader, CWOverrideRedirect,
- &attr);
- XReparentWindow(gdk_x11_get_default_xdisplay(), myGroupLeader, myRootWindow, 10, 10);
- XSync(gdk_x11_get_default_xdisplay(), FALSE);
+ XChangeWindowAttributes(GDK_GET_XDISPLAY(gdkDisplay), myGroupLeader,
+ CWOverrideRedirect, &attr);
+ XReparentWindow(GDK_GET_XDISPLAY(gdkDisplay), myGroupLeader, myRootWindow,
+ 10, 10);
+ XSync(GDK_GET_XDISPLAY(gdkDisplay), FALSE);
*groupLeader = myGroupLeader;
*rootWindow = myRootWindow;
unsigned int index;
Bool alreadyLocked = FALSE; // Set to TRUE if we discover lock is held.
Bool retval = FALSE;
+ GdkDisplay *gdkDisplay;
- defaultDisplay = gdk_x11_get_default_xdisplay();
+#if GTK_MAJOR_VERSION == 4
+ gdkDisplay = gdk_display_get_default();
+#else
+ gdkDisplay = NULL;
+#endif
+ defaultDisplay = GDK_GET_XDISPLAY(gdkDisplay);
/*
* Reset some of our main window's settings & fetch Xlib handles for
X11Lock_Init(ToolsAppCtx *ctx,
ToolsPluginData *pdata)
{
- int argc = 0;
char *argv[] = { NULL, NULL };
if (!TOOLS_IS_USER_SERVICE(ctx)) {
gdk_set_allowed_backends("x11");
#endif
/* XXX: is calling gtk_init() multiple times safe? */
+#if GTK_MAJOR_VERSION == 4
+ gtk_init();
+#else
+ int argc = 0;
gtk_init(&argc, (char ***) &argv);
+#endif
if (!AcquireDisplayLock()) {
g_warning("Another instance of vmware-user already running. Exiting.\n");
libdndcp_la_SOURCES += dndGuest/dndCPTransportGuestRpc.cpp
libdndcp_la_SOURCES += stringxx/string.cc
+if HAVE_GTK4
+libdndcp_la_SOURCES += xutils/xutilsGTK4.cc
+else
libdndcp_la_SOURCES += xutils/xutils.cc
+endif
libdndcp_la_SOURCES += dndcp.cpp
libdndcp_la_SOURCES += pointer.cpp
libdndcp_la_SOURCES += vmCopyPasteDnDWrapper.cpp
libdndcp_la_SOURCES += copyPasteDnDX11.cpp
libdndcp_la_SOURCES += copyPasteDnDUtil.cpp
+if HAVE_GTK4
+libdndcp_la_SOURCES += copyPasteCompatX11GTK4.c
+libdndcp_la_SOURCES += copyPasteUIX11GTK4.cpp
+libdndcp_la_SOURCES += dndUIX11GTK4.cpp
+libdndcp_la_SOURCES += dragDetWndX11GTK4.cpp
+else
libdndcp_la_SOURCES += copyPasteCompatX11.c
libdndcp_la_SOURCES += copyPasteUIX11.cpp
libdndcp_la_SOURCES += dndUIX11.cpp
libdndcp_la_SOURCES += dragDetWndX11.cpp
+endif
if LINUX
libdndcp_la_CPPFLAGS += -I$(top_srcdir)/services/plugins/dndcp/fakeMouse
* selection text.
*/
+# if !defined(GTK2) && !defined(GTK3)
+# error "This should only build with GTK2 or GTK3"
+# endif
#define G_LOG_DOMAIN "dndcp"
#include "dndPluginIntX11.h"
--- /dev/null
+/*********************************************************
+ * Copyright (c) 2025 Broadcom. All Rights Reserved.
+ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *********************************************************/
+
+/*
+ * copyPasteCompatX11GTK4.c --
+ *
+ * Like copyPasteCompatX11.c, this is the GTK4 version implementation for
+ * Linux OS for guest<-->host text copy/paste. We need separate
+ * implementation for GTK4 as its API changed dramaticly compared to GTK3.
+ *
+ * Set of functions in this file are for guest side text copy/paste between
+ * host and guest. Currently there are 4 versions copy/paste.
+ * ** Version 1 is based on backdoor, supports only text copy/paste,
+ * available with GTK2/GTK3/GTK4.
+ * ** Version 2/3/4 are all based on tools guestRPC.
+ * *** Version 2 only existed for a certain period for compatibility with
+ * pre-2000 windows guest and is deprecated.
+ * *** Version 3 also existed for compatibility with some old tools with
+ * Fusion 3, WS 7 etc and is considered deprecated now.
+ * *** Version 4 is the current version, which supports both text and file
+ * copy/paste.
+ * Refer to "bora/vmx/tools/dndController" for more details.
+ *
+ * ### G->H Text Copy/Paste (version 1) ###
+ * --------------------
+ * When Ungrab, CopyPaste_RequestSelection got called, which try to get
+ * selection text and send to backdoor.
+ *
+ * ### H->G Text Copy/Paste (version 1) ###
+ * --------------------
+ * When grab, CopyPaste_GetBackdoorSelections got called, which first
+ * get host selection text and set its content in gHostClipboardBuf.
+ * Then the gGdkDisplay default clipboard is set with its content.
+ */
+#ifndef GTK4
+#error "This should only build with GTK4"
+#endif
+#define G_LOG_DOMAIN "dndcp"
+
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "copyPasteCompat.h"
+#include "dndPluginIntX11.h"
+#include "util.h"
+#include "vm_assert.h"
+#include "vmware/guestrpc/tclodefs.h"
+#include "vmware/tools/plugin.h"
+
+/*
+ * Currently there are 2 active versions copy/paste: version 1 and version 4
+ * (version 2/3 are considered deprecated):
+ * Key points in copy/paste version 1:
+ * 1. Only text copy/paste
+ * 2. copy/paste is based on backdoor directly
+ *
+ * Key points in copy/paste version 4:
+ * 1. Support both file/text copy/paste
+ * 2. Both file/text copy/paste are based on guestRPC
+ */
+static int32 gVmxCopyPasteVersion = 1;
+
+/*
+ * Here gHostClipboardBuf is one byte lager than MAX_SELECTION_BUFFER_LENGTH,
+ * As the backdoor bit copy is 4-byte aligned, we could copy H->G with exactly
+ * MAX_SELECTION_BUFFER_LENGTH(65536 - 100) bytes at most as its value is 4-byte
+ * aligned, we need additional 1 termination char '\0'.
+ * While for the gGuestClipboardBuf, as we are copy G->H also with 4-byte
+ * aligned, we need to include the termination '\0' at the last char, thus it
+ * could be MAX_SELECTION_BUFFER_LENGTH at most.
+ */
+static char gGuestClipboardBuf[MAX_SELECTION_BUFFER_LENGTH];
+static char gHostClipboardBuf[MAX_SELECTION_BUFFER_LENGTH + 1];
+static unsigned int gGuestClipGenNum;
+static unsigned int gHostClipGenNum;
+static gint64 gGuestDefaultClipTime = 0;
+static gint64 gGuestPrimaryClipTime = 0;
+
+static ToolsAppCtx *gCtx = NULL;
+static GdkClipboard *gDefaultClipBd = NULL;
+static GdkClipboard *gPrimaryClipBd = NULL;
+
+/*
+ * Forward Declarations
+ */
+static void CopyPasteSetBackdoorSelections(void);
+static void CopyPasteRequestTextCb(GObject *source_object,
+ GAsyncResult *result,
+ gpointer user_data);
+static void GuestDefaultClipbdChangedCb(GdkClipboard *clipboard,
+ gpointer user_data);
+static void GuestPrimaryClipbdChangedCb(GdkClipboard *clipboard,
+ gpointer user_data);
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * GuestDefaultClipbdChangedCb --
+ *
+ * Callback when the guest Default clipboard changed.
+ * Either guest own app or the H-G copy could change the guest clipboard.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * If clipboard text changed, gGuestCpGenNum is increased by 1 and
+ * gGuestDefaultClipTime is updated.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static void
+GuestDefaultClipbdChangedCb(GdkClipboard *clipboard, // UNUSED
+ gpointer user_data) // UNUSED
+{
+ GdkContentFormats *clipFormats = gdk_clipboard_get_formats(gDefaultClipBd);
+
+ /*
+ * Only update when clipboard text content changed,
+ * as v1 copy/paste only supports plain text
+ */
+ if (gdk_content_formats_contain_gtype(clipFormats, G_TYPE_STRING)) {
+ ++gGuestClipGenNum;
+ gGuestDefaultClipTime = g_get_real_time();
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * GuestPrimaryClipbdChangedCb --
+ *
+ * Callback when the guest Primary clipboard changed.
+ * Either guest own app or the H-G copy could change the guest clipboard.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * If clipboard text changed, gGuestCpGenNum is increased by 1 and
+ * gGuestPrimaryClipTime is updated.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static void
+GuestPrimaryClipbdChangedCb(GdkClipboard *clipboard, // UNUSED
+ gpointer user_data) // UNUSED
+{
+ GdkContentFormats *clipFormats = gdk_clipboard_get_formats(gPrimaryClipBd);
+
+ /*
+ * Only update when clipboard text content changed,
+ * as v1 copy/paste only supports plain text
+ */
+ if (gdk_content_formats_contain_gtype(clipFormats, G_TYPE_STRING)) {
+ ++gGuestClipGenNum;
+ gGuestPrimaryClipTime = g_get_real_time();
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteRequestTextCb--
+ *
+ * Callback when finish reading the guest's text clipboard, and then send
+ * the content buffer to host.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * On Success:
+ * ** gGuestClipboardBuf is set with the guest clipboard text
+ * ** gGuestClipboardBuf content is sent to host with backdoor.
+ * ** gHostClipGenNum is set to gGuestClipGenNum.
+ *-----------------------------------------------------------------------------
+ */
+
+static void
+CopyPasteRequestTextCb(GObject *source_object, // IN/OUT: clipboard
+ GAsyncResult *result, // UNUSED
+ gpointer user_data) // UNUSED
+{
+ GdkClipboard *clipboard = GDK_CLIPBOARD(source_object);
+ GError *err = NULL;
+ size_t len;
+ /* gdk_clipboard_read_text_finish return NUL terminated UTF-8 string */
+ char *cpStr = gdk_clipboard_read_text_finish(clipboard, result, &err);
+
+ if (cpStr != NULL) {
+ len = strlen(cpStr);
+ if (len >= MAX_SELECTION_BUFFER_LENGTH) {
+ /*
+ * At most MAX_SELECTION_BUFFER_LENGTH-1 size string could be copied
+ * G->H clipboard, as backdoor copy is 4-byte aligned and we need to
+ * keep the last char as '\0' terminator
+ */
+ len = MAX_SELECTION_BUFFER_LENGTH - 1;
+ g_warning("%s: Guest clipboard selection exceeds [%zu] bytes, text truncated\n",
+ __FUNCTION__, len);
+ }
+ memcpy(gGuestClipboardBuf, cpStr, len);
+ gGuestClipboardBuf[len] = '\0';
+ g_free(cpStr);
+ g_debug("%s: Guest clipboard GenNum=%d, text is [%s]\n", __FUNCTION__,
+ gGuestClipGenNum, gGuestClipboardBuf);
+ CopyPasteSetBackdoorSelections();
+ gHostClipGenNum = gGuestClipGenNum;
+ }
+
+ if (err != NULL) {
+ /* clipboard failure is not critical one, warn but do not error exit */
+ g_warning("%s: Error to copy from guest clipboard: %s\n", __FUNCTION__,
+ err->message);
+ g_error_free(err);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPaste_RequestSelection --
+ *
+ * If gHostClipGenNum is different from gGuestClipGenNum, request the
+ * guest's text clipboard (asynchronously). Once the clipboard read request
+ * is completed the Callback function is invoked to send the buffer to
+ * the host.
+ *
+ * Results:
+ * TRUE on success, FALSE otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+Bool
+CopyPaste_RequestSelection(void)
+{
+ if (gVmxCopyPasteVersion > 1) {
+ return FALSE;
+ }
+
+ if (gHostClipGenNum == gGuestClipGenNum) {
+ g_debug("%s: Guest clipboard in sync with host with the same GenNum=%d, "
+ "skip send", __FUNCTION__, gGuestClipGenNum);
+ } else {
+ gGuestClipboardBuf[0] = '\0';
+ GdkClipboard *cpClipboard = (gGuestPrimaryClipTime > gGuestDefaultClipTime)
+ ? gPrimaryClipBd
+ : gDefaultClipBd;
+ gdk_clipboard_read_text_async(cpClipboard, NULL, CopyPasteRequestTextCb, NULL);
+ }
+ return TRUE;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteSetBackdoorSelections --
+ *
+ * Set the clipboard uses CopyPaste_SetSelLength and set backdoor selection
+ * with clipboard. If unavailable, set backdoor selection length to be 0.
+ * Unlike its GTK3/GTK2 implementation in copyPasteCompatX11.c, we could
+ * not compare with the timestamps so only the default clipboard is used.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * The VMX probably changes some string buffers.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteSetBackdoorSelections(void)
+{
+ uint32 const *p;
+ size_t len = strlen(gGuestClipboardBuf);
+
+ if (len > 0) {
+ p = (uint32 const *)gGuestClipboardBuf;
+ size_t alignedLen;
+ unsigned int i;
+
+ /* Here long string should already be truncated in caller function. */
+ alignedLen = (len + 4) & ~3;
+ ASSERT(alignedLen <= MAX_SELECTION_BUFFER_LENGTH);
+
+ CopyPaste_SetSelLength(len);
+ g_debug("%s: Set host clipboard with [%zu] text.\n", __FUNCTION__, len);
+ for (i = 0; i < len; i += 4, p++) {
+ CopyPaste_SetNextPiece(*p);
+ }
+ } else {
+ CopyPaste_SetSelLength(0);
+ g_debug("%s: Set host clipboard with empty text.\n", __FUNCTION__);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPaste_GetBackdoorSelections --
+ *
+ * Get the clipboard "the old way".
+ * The old way uses CopyPaste_GetHostSelectionLen and there's only one
+ * selection and its content is in gHostClipboardBuf.
+ *
+ * XXX: the "new way" isn't availble yet because the vmx doesn't
+ * implement separate clipboards. Even when it does this
+ * function will still exist for backward compatibility
+ *
+ * Results:
+ * TRUE if 0 <= selLength <= MAX_SELECTION_BUFFER_LENGTH, FALSE otherwise.
+ *
+ * Side effects:
+ * * guest clipboard is set with the host's clipboard content.
+ * * gHostClipGenNum is increased by 1.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+Bool
+CopyPaste_GetBackdoorSelections(void)
+{
+ int selLength;
+
+ if (gVmxCopyPasteVersion > 1) {
+ return TRUE;
+ }
+
+ selLength = CopyPaste_GetHostSelectionLen();
+ if (selLength < 0 || selLength > MAX_SELECTION_BUFFER_LENGTH) {
+ return FALSE;
+ } else if (selLength > 0) {
+ CopyPaste_GetHostSelection(selLength, gHostClipboardBuf);
+ gHostClipboardBuf[selLength] = 0;
+ ++gHostClipGenNum;
+ g_debug("%s: Host clipboard GenNum=%d, text is [%s].\n", __FUNCTION__,
+ gHostClipGenNum, gHostClipboardBuf);
+ /* Initialize a GValue with the contents of the gHostClipboardBuf */
+ GValue value = G_VALUE_INIT;
+
+ g_value_init(&value, G_TYPE_STRING);
+ g_value_set_string(&value, gHostClipboardBuf);
+ /* H->G copy paste, store the value in guest Default and Primary clipboard */
+ gdk_clipboard_set_value(gDefaultClipBd, &value);
+ gdk_clipboard_set_value(gPrimaryClipBd, &value);
+ g_value_unset(&value);
+ }
+ /* finally return TRUE if setLength>=0 */
+ return TRUE;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPaste_Register --
+ *
+ * Setup callbacks, initialize.
+ *
+ * Results:
+ * Always TRUE.
+ *
+ * Side effects:
+ * None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+Bool
+CopyPaste_Register(GtkWidget* mainWnd, // IN
+ ToolsAppCtx *ctx) // IN
+{
+ g_debug("%s: enter\n", __FUNCTION__);
+ ASSERT(mainWnd);
+ ASSERT(ctx);
+
+ gCtx = ctx;
+ gHostClipboardBuf[0] = '\0';
+ gGuestClipboardBuf[0] = '\0';
+ gGuestClipGenNum = 0;
+ gHostClipGenNum = 0;
+ gDefaultClipBd = gdk_display_get_clipboard(gGdkDisplay);
+ gPrimaryClipBd = gdk_display_get_primary_clipboard(gGdkDisplay);
+
+ g_signal_connect(gDefaultClipBd, "changed",
+ G_CALLBACK(GuestDefaultClipbdChangedCb), NULL);
+ g_signal_connect(gPrimaryClipBd, "changed",
+ G_CALLBACK(GuestPrimaryClipbdChangedCb), NULL);
+ return TRUE;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPaste_Unregister --
+ *
+ * Currently do nothing. Dummy function in GTK4.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPaste_Unregister(GtkWidget* mainWnd)
+{
+ return;
+}
+
+
+/*
+ *----------------------------------------------------------------------------
+ *
+ * CopyPaste_IsRpcCPSupported --
+ *
+ * Check if RPC copy/paste is supported by vmx or not.
+ *
+ * Results:
+ * FALSE always.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+Bool
+CopyPaste_IsRpcCPSupported(void)
+{
+ return gVmxCopyPasteVersion > 1;
+}
+
+
+/**
+ * Set the copy paste version.
+ *
+ * @param[in] version version to set.
+ */
+
+void
+CopyPaste_SetVersion(int version)
+{
+ g_debug("%s: enter version %d\n", __FUNCTION__, version);
+ gVmxCopyPasteVersion = version;
+}
#include "copyPasteDnDWrapper.h"
#include "copyPasteDnDX11.h"
+#ifdef GTK4
+#undef VMTOOLS_USE_LEGACY_GTK
+#include "dndCPMsgV4.h"
+#endif
#include "copyPasteUIX11.h"
#include "tracer.hh"
#include "dndPluginIntX11.h"
Window gXRoot;
Display *gXDisplay;
GtkWidget *gUserMainWidget;
+#ifdef GTK4
+GdkDisplay *gGdkDisplay;
+#endif
extern "C" {
CopyPasteDnDX11::Init(ToolsAppCtx *ctx)
{
TRACE_CALL();
+#if GTK_MAJOR_VERSION > 4
+ g_error("Unsupported GTK version!")
+#endif
#if GTK_MAJOR_VERSION > 3 || (GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION >= 10)
/*
ASSERT(ctx);
+#ifdef GTK4
+ Gtk::Application::create("com.tools.gtk4");
+#endif
#ifdef VMTOOLS_USE_LEGACY_GTK
int argc = 1;
if (wrapper) {
BlockService::GetInstance()->Init(ctx);
}
+#ifdef GTK4
+ gUserMainWidget = gtk_window_new();
+ gtk_widget_set_visible(gUserMainWidget, FALSE);
+ gGdkDisplay = gdk_display_get_default();
+ gXDisplay = gdk_x11_display_get_xdisplay(gGdkDisplay);
+ gXRoot = gdk_x11_display_get_xrootwindow(gGdkDisplay);
+#endif
#ifdef VMTOOLS_USE_LEGACY_GTK
gUserMainWidget = gtk_invisible_new();
#ifdef VMTOOLS_USE_LEGACY_GTK
gtk_widget_destroy(gUserMainWidget);
#endif
+#ifdef GTK4
+ gtk_window_destroy(GTK_WINDOW(gUserMainWidget));
+#endif
}
}
* ignore everything else.
*/
+# if !defined(GTK2) && !defined(GTK3)
+# error "This could only build with GTK2 or GTK3"
+# endif
#define G_LOG_DOMAIN "dndcp"
#define VMTOOLS_USE_LEGACY_GTK
+#ifdef GTK4
+#undef VMTOOLS_USE_LEGACY_GTK
+#include <gdk/x11/gdkx.h>
+#endif
#ifdef VMTOOLS_USE_LEGACY_GTK
#include <gdk/gdkx.h>
#endif
/* hg */
void GetRemoteClipboardCB(const CPClipboard *clip);
+#ifdef GTK4
+ void LocalGetFileRequest();
+ void BuildFileURIList(utf::string& uriList,
+ utf::string pre,
+ utf::string post,
+ utf::string stagingDirName);
+ void BuildFileContentURIList(utf::string& uriList,
+ utf::string pre,
+ utf::string post);
+ void LocalSetFileListToClipboard(const char* desktop);
+ void LocalGetFileContentsRequest();
+#endif
#ifdef VMTOOLS_USE_LEGACY_GTK
void LocalGetFileRequestCB(Gtk::SelectionData& selection_data, guint info);
/* gh */
void GetLocalClipboard(void);
+#ifdef GTK4
+ void CopyPasteSendText(const Glib::ustring& cpStr);
+ void CopyPasteSendImage(const Glib::RefPtr<Glib::Bytes>& imgData);
+ void CopyPasteSendRtfValue(const Glib::RefPtr<Gio::AsyncResult>& result);
+ void CopyPasteSendFile(const Glib::RefPtr<Gio::AsyncResult>& result);
+ void CopyPasteRequestStreamCb(const Glib::RefPtr<Gio::AsyncResult>& result,
+ const Glib::RefPtr<Gdk::Clipboard>& clipboard);
+ void CopyPasteRequestTextCb(const Glib::RefPtr<Gio::AsyncResult>& result,
+ const Glib::RefPtr<Gdk::Clipboard>& clipboard);
+ void CopyPasteRequestImageCb(const Glib::RefPtr<Gio::AsyncResult>& result,
+ const Glib::RefPtr<Gdk::Clipboard>& clipboard);
+ void GuestDefaultClipboardChangedCb();
+ void GuestPrimaryClipboardChangedCb();
+#endif
#ifdef VMTOOLS_USE_LEGACY_GTK
void LocalClipboardTimestampCB(const Gtk::SelectionData& sd);
void LocalPrimTimestampCB(const Gtk::SelectionData& sd);
GuestCopyPasteMgr *mCP;
bool mClipboardEmpty;
utf::string mHGStagingDir;
+#ifdef GTK4
+ Glib::RefPtr<Gdk::Clipboard> mDftClipboardPtr;
+ Glib::RefPtr<Gdk::Clipboard> mPrimClipboardPtr;
+ Glib::RefPtr<Gio::InputStream> mCpStream;
+#endif
#ifdef VMTOOLS_USE_LEGACY_GTK
std::vector<Gtk::TargetEntry> mListTargets;
GdkAtom mGHSelection;
/* File vars. */
VmTimeType mHGGetListTime;
utf::utf8string mHGFCPData;
+#ifdef GTK4
+ utf::string mHGCopiedUriListGnome;
+ utf::string mHGCopiedUriListKde;
+ utf::string mHGCopiedUriListNautilus;
+#endif
#ifdef VMTOOLS_USE_LEGACY_GTK
utf::string mHGCopiedUriList;
--- /dev/null
+/*********************************************************
+ * Copyright (c) 2025 Broadcom. All Rights Reserved.
+ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *********************************************************/
+
+/*
+ * copyPasteUIX11GTK4.cpp --
+ *
+ * Like copyPasteUIX11.c, this is the GTK4 version implementation
+ * of CopyPaste between host and guest for Linux GuestOS. GTK4 API's
+ * are completely different than GTK3 hence a seperate implementation
+ * is needed.
+ *
+ * Currently there are 2 versions for copy/paste.
+ * Version 1 is based on backdoor, supports only text copy/paste.
+ * Version 4 is based on guestRPC, supports text, image, rtf and
+ * file copy/paste.
+ *
+ * G->H Copy/Paste (version 4)
+ * --------------------
+ * When Ungrab, GetLocalClipboard got called, which fetches the
+ * text, image, rtf or file data from the Clipboard and send it to Host.
+ *
+ * H->G Copy/Paste (version 4)
+ * --------------------
+ * When grab, GetRemoteClipboardCB got called, which sets the text,
+ * image, rtf or file data received from the Host to the Linux Guest
+ * OS clipboard
+ *
+ */
+
+
+#ifndef GTK4
+#error "This should build with GTK4 only."
+#endif
+
+#define G_LOG_DOMAIN "dndcp"
+
+#include <cxxabi.h>
+#include "copyPasteDnDWrapper.h"
+#include "copyPasteUIX11.h"
+#include "dndFileList.hh"
+#include "guestDnDCPMgr.hh"
+#include "tracer.hh"
+#include "vmblock.h"
+#include "fcntl.h"
+#include "file.h"
+#include "dnd.h"
+#include "dndMsg.h"
+#include "dndPluginIntX11.h"
+#include "copyPasteDnDUtil.h"
+#include <gtk/gtk.h>
+#include <sys/time.h>
+
+extern "C" {
+ #include "dndClipboard.h"
+ #include "cpName.h"
+ #include "cpNameUtil.h"
+ #include "rpcout.h"
+ #include "vmware/guestrpc/tclodefs.h"
+}
+
+static unsigned int gGuestClipGenNum;
+static unsigned int gHostClipGenNum;
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::CopyPasteUIX11 --
+ *
+ * Constructor.
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+CopyPasteUIX11::CopyPasteUIX11()
+ : mClipboardEmpty(true),
+ mHGStagingDir(""),
+ mClipTime(0),
+ mPrimTime(0),
+ mLastTimestamp(0),
+ mThread(0),
+ mHGGetListTime(0),
+ mHGGetFileStatus(DND_FILE_TRANSFER_NOT_STARTED),
+ mBlockAdded(false),
+ mBlockCtrl(0),
+ mInited(false),
+ mDftClipboardPtr(nullptr),
+ mPrimClipboardPtr(nullptr),
+ mTotalFileSize(0)
+{
+ TRACE_CALL();
+ GuestDnDCPMgr *p = GuestDnDCPMgr::GetInstance();
+ ASSERT(p);
+ mCP = p->GetCopyPasteMgr();
+ ASSERT(mCP);
+ Glib::init();
+
+ mThreadParams.fileBlockCondExit = false;
+ pthread_mutex_init(&mThreadParams.fileBlockMutex, NULL);
+ pthread_cond_init(&mThreadParams.fileBlockCond, NULL);
+ mThreadParams.cp = this;
+ int ret = pthread_create(&mThread,
+ NULL,
+ FileBlockMonitorThread,
+ (void *)&(this->mThreadParams));
+ if (ret != 0) {
+ Warning("%s: Create thread failed, errno:%d.\n", __FUNCTION__, ret);
+ mThread = 0;
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::Init --
+ *
+ * Initialize copy paste UI class and register for V3 or greater copy
+ * paste.
+ *
+ * Results:
+ * Always true
+ *
+ * Side effects:
+ * None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool
+CopyPasteUIX11::Init()
+{
+ TRACE_CALL();
+ if (mInited) {
+ g_debug("%s: mInited is true\n", __FUNCTION__);
+ return true;
+ }
+
+ CPClipboard_Init(&mClipboard);
+
+ gGuestClipGenNum = 0;
+ gHostClipGenNum = 0;
+
+ mCP->srcRecvClipChanged.connect(
+ sigc::mem_fun(this, &CopyPasteUIX11::GetRemoteClipboardCB));
+ mCP->destRequestClipChanged.connect(
+ sigc::mem_fun(this, &CopyPasteUIX11::GetLocalClipboard));
+ mCP->getFilesDoneChanged.connect(
+ sigc::mem_fun(this, &CopyPasteUIX11::GetLocalFilesDone));
+
+
+ Glib::RefPtr<Gdk::Display> display = Gdk::Display::get_default();
+ mDftClipboardPtr = display->get_clipboard();
+ mPrimClipboardPtr = display->get_primary_clipboard();
+
+ mDftClipboardPtr->signal_changed().connect(
+ sigc::mem_fun(*this, &CopyPasteUIX11::GuestDefaultClipboardChangedCb));
+ mPrimClipboardPtr->signal_changed().connect(
+ sigc::mem_fun(*this, &CopyPasteUIX11::GuestPrimaryClipboardChangedCb));
+
+ mInited = true;
+ return true;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::~CopyPaste --
+ *
+ * Destructor.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+CopyPasteUIX11::~CopyPasteUIX11()
+{
+ TRACE_CALL();
+ CPClipboard_Destroy(&mClipboard);
+ /* Any files from last unfinished file transfer should be deleted. */
+ if (DND_FILE_TRANSFER_IN_PROGRESS == mHGGetFileStatus &&
+ !mHGStagingDir.empty()) {
+ uint64 totalSize = File_GetSizeEx(mHGStagingDir.c_str());
+ if (mTotalFileSize != totalSize) {
+ g_debug("%s: deleting %s, expecting %" FMT64 "u, finished %" FMT64 "u\n",
+ __FUNCTION__, mHGStagingDir.c_str(),
+ mTotalFileSize, totalSize);
+ DnD_DeleteStagingFiles(mHGStagingDir.c_str(), FALSE);
+ } else {
+ g_debug("%s: file size match %s\n",
+ __FUNCTION__, mHGStagingDir.c_str());
+ }
+ }
+ if (mBlockAdded) {
+ g_debug("%s: removing block for %s\n", __FUNCTION__, mHGStagingDir.c_str());
+ /* We need to make sure block subsystem has not been shut off. */
+ mBlockAdded = false;
+ if (DnD_BlockIsReady(mBlockCtrl)) {
+ mBlockCtrl->RemoveBlock(mBlockCtrl->fd, mHGStagingDir.c_str());
+ }
+ }
+
+ TerminateThread();
+ pthread_mutex_destroy(&mThreadParams.fileBlockMutex);
+ pthread_cond_destroy(&mThreadParams.fileBlockCond);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::VmxCopyPasteVersionChanged --
+ *
+ * Update version information in mCP.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::VmxCopyPasteVersionChanged(RpcChannel *chan, // IN
+ uint32 version) // IN
+{
+ ASSERT(mCP);
+ g_debug("%s: new version is %d\n", __FUNCTION__, version);
+ mCP->VmxCopyPasteVersionChanged(version);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::GuestDefaultClipboardChangedCb --
+ *
+ * Callback when the guest default clipboard changed.
+ * Either guest own app or the H-G copy could change the guest clipboard.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * gGuestCpGenNum is increased by 1.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::GuestDefaultClipboardChangedCb()
+{
+ ++gGuestClipGenNum;
+ mClipTime = g_get_real_time();
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::GuestPrimaryClipboardChangedCb --
+ *
+ * Callback when the guest primary clipboard changed.
+ * Either guest own app or the H-G copy could change the guest clipboard.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * gGuestCpGenNum is increased by 1.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::GuestPrimaryClipboardChangedCb()
+{
+ ++gGuestClipGenNum;
+ mPrimTime = g_get_real_time();
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::GetLocalFilesDone --
+ *
+ * Callback when CopyPasteUIX11::GetLocalFiles is done, which finishes the file
+ * copying from host to guest staging directory. This function notifies
+ * the Copy/Paste data object and end its waiting state in order to continue
+ * the file copying from local staging directory to local target directory.
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::GetLocalFilesDone(bool success)
+{
+ g_debug("%s: enter success %d\n", __FUNCTION__, success);
+
+ if (mBlockAdded) {
+ g_debug("%s: removing block for %s\n", __FUNCTION__, mHGStagingDir.c_str());
+ /* We need to make sure block subsystem has not been shut off. */
+ mBlockAdded = false;
+ if (DnD_BlockIsReady(mBlockCtrl)) {
+ mBlockCtrl->RemoveBlock(mBlockCtrl->fd, mHGStagingDir.c_str());
+ }
+ }
+
+ mHGGetFileStatus = DND_FILE_TRANSFER_FINISHED;
+ if (success) {
+ /*
+ * Mark current staging dir to be deleted on next reboot for FCP. The
+ * file will not be deleted after reboot if it is moved to another
+ * location by target application.
+ */
+ DnD_DeleteStagingFiles(mHGStagingDir.c_str(), TRUE);
+ } else {
+ /* Copied files are already removed in common layer. */
+ mHGStagingDir.clear();
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::GetLocalClipboard --
+ *
+ * Retrieves the data from local clipboard and sends it to host. Send empty
+ * data back if there is no data or can not get data successfully. For
+ * guest->host copy/paste.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::GetLocalClipboard(void)
+{
+ g_debug("%s: enter.\n", __FUNCTION__);
+ Glib::RefPtr<Gdk::Clipboard> clipboardPtr;
+
+ if (!mCP->IsCopyPasteAllowed()) {
+ g_debug("%s: copyPaste is not allowed\n", __FUNCTION__);
+ return;
+ }
+
+ if (gHostClipGenNum == gGuestClipGenNum) {
+ g_debug("%s: Guest clipboard in sync with host with the same GenNum=%d, "
+ "skip send", __FUNCTION__, gGuestClipGenNum);
+ SendClipNotChanged();
+ return;
+ }
+
+ CPClipboard_Clear(&mClipboard);
+
+ if (mClipTime > mPrimTime) {
+ if (mClipTime > 0 && mClipTime == mLastTimestamp) {
+ g_debug("%s: clip is not changed\n", __FUNCTION__);
+ SendClipNotChanged();
+ return;
+ }
+ mLastTimestamp = mClipTime;
+ clipboardPtr = mDftClipboardPtr;
+ } else {
+ if (mPrimTime > 0 && mPrimTime == mLastTimestamp) {
+ g_debug("%s: clip is not changed\n", __FUNCTION__);
+ SendClipNotChanged();
+ return;
+ }
+ mLastTimestamp = mPrimTime;
+ clipboardPtr = mPrimClipboardPtr;
+ }
+
+ Glib::RefPtr<Gdk::ContentFormats> formats = clipboardPtr->get_formats();
+
+ /*
+ * *** DEBUG ONLY log for dev to track clipboard mime formats ***
+ * TODO: Remove the below before turn FSS on
+ */
+ Glib::ustring clipFmtStr = formats->to_string();
+ const std::vector<Glib::ustring> clipFmtVector = formats->get_mime_types();
+
+ g_debug("%s: Guest clipboard formats all:'/%s/', ", __FUNCTION__, clipFmtStr.c_str());
+ for (Glib::ustring fmt : clipFmtVector) {
+ g_debug("%s: Guest clipboard format list:'/%s/', ", __FUNCTION__, fmt.c_str());
+
+ }
+ /* TODO: Remove the above before Turn FSS on */
+
+ /* Try to get URI's from clipboard if present. This must always be done first */
+ std::vector<Glib::ustring> fmtVec;
+ bool haveURIs = false;
+
+ if (formats->contain_mime_type(FCP_TARGET_NAME_GNOME_COPIED_FILES)) {
+ g_debug("%s: Gnome URI list is available in guest clipboard", __FUNCTION__);
+ fmtVec.push_back(FCP_TARGET_NAME_GNOME_COPIED_FILES);
+ haveURIs = true;
+ }
+ if (formats->contain_mime_type(FCP_TARGET_NAME_URI_LIST)) {
+ g_debug("%s: text uri list is available in guest clipboard", __FUNCTION__);
+ fmtVec.push_back(FCP_TARGET_NAME_URI_LIST);
+ haveURIs = true;
+ }
+ if (mCP->CheckCapability(DND_CP_CAP_FILE_CP) && haveURIs) {
+ g_debug("%s: Getting file URI from guest clipboard", __FUNCTION__);
+ clipboardPtr->read_async(fmtVec, G_PRIORITY_DEFAULT, sigc::bind(sigc::mem_fun(
+ *this, &CopyPasteUIX11::CopyPasteRequestStreamCb), clipboardPtr), nullptr);
+ return;
+ }
+
+#if (GTK_MAJOR_VERSION == 4 && GTK_MINOR_VERSION >= 6)
+ /* Image Copy/Paste from G->H is supported from GTK 4.6 and onwards.
+ * The dependency is due to usage of gdk_texture_save_to_png_bytes().
+ * This check can be removed once we move the Host code to
+ * GTK4 as well. GDKTexture is supported from GTK4 and we can
+ * directly set it to clipboard for Image.
+ */
+
+ /* Try to get Image data from clipboard. */
+ if (mCP->CheckCapability(DND_CP_CAP_IMAGE_CP) &&
+ formats->contain_gtype(GDK_TYPE_TEXTURE)) {
+ clipboardPtr->read_texture_async(sigc::bind(sigc::mem_fun(
+ *this, &CopyPasteUIX11::CopyPasteRequestImageCb), clipboardPtr), nullptr);
+ }
+#endif
+
+ /* Try to get rtf data from clipboard if present, then check for plain text */
+ bool haveRtf = false;
+ fmtVec.clear();
+
+ if (formats->contain_mime_type(TARGET_NAME_APPLICATION_RTF)) {
+ g_debug("%s: APP RTF is available in guest clipboard", __FUNCTION__);
+ fmtVec.push_back(TARGET_NAME_APPLICATION_RTF);
+ haveRtf = true;
+ }
+ if (formats->contain_mime_type(TARGET_NAME_TEXT_RICHTEXT)) {
+ g_debug("%s: RICHTEXT is available in guest clipboard",
+ __FUNCTION__);
+ fmtVec.push_back(TARGET_NAME_TEXT_RICHTEXT);
+ haveRtf = true;
+ }
+ if (formats->contain_mime_type(TARGET_NAME_TEXT_RTF)) {
+ g_debug("%s: TEXT RTF is available in guest clipboard", __FUNCTION__);
+ fmtVec.push_back(TARGET_NAME_TEXT_RTF);
+ haveRtf = true;
+ }
+ if (mCP->CheckCapability(DND_CP_CAP_RTF_CP) && haveRtf) {
+ g_debug("%s: Getting rtf text from guest clipboard", __FUNCTION__);
+ clipboardPtr->read_async(fmtVec, G_PRIORITY_DEFAULT, sigc::bind(sigc::mem_fun(
+ *this, &CopyPasteUIX11::CopyPasteRequestStreamCb), clipboardPtr), nullptr);
+ }
+
+ /* Try to get plain text from clipboard if present */
+ if (mCP->CheckCapability(DND_CP_CAP_PLAIN_TEXT_CP) &&
+ formats->contain_gtype(G_TYPE_STRING)) {
+ g_debug("%s: Getting plain text from guest clipboard", __FUNCTION__);
+ clipboardPtr->read_text_async(sigc::bind(sigc::mem_fun(
+ *this, &CopyPasteUIX11::CopyPasteRequestTextCb), clipboardPtr), nullptr);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::CopyPasteSendText--
+ *
+ * Send the text data to Host.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *-----------------------------------------------------------------------------
+ */
+void
+CopyPasteUIX11::CopyPasteSendText(const Glib::ustring& cpStr) // IN
+{
+ if (cpStr.empty()) {
+ return;
+ }
+
+ size_t bufSize = cpStr.size();
+ if (bufSize > 0 &&
+ bufSize <= CPCLIPITEM_MAX_SIZE_V3 &&
+ CPClipboard_SetItem(&mClipboard, CPFORMAT_TEXT,
+ cpStr.c_str(), bufSize + 1)) {
+ g_debug("%s: set TEXT: %" FMTSZ "u\n", __FUNCTION__, bufSize);
+ mCP->DestUISendClip(&mClipboard);
+ // Reset the host and guest clip gen number.
+ gHostClipGenNum = 0;
+ gGuestClipGenNum = 0;
+ } else {
+ /* clipboard failure is not critical one, warn but do not error exit */
+ g_warning("%s: Failed to copy text from guest clipboard,"
+ " either text is empty or too large\n", __FUNCTION__);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteSendImage--
+ *
+ * Send the Image data to Host
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::CopyPasteSendImage(const Glib::RefPtr<Glib::Bytes>& imgData) // IN
+{
+ if (imgData == nullptr) {
+ return;
+ }
+
+ gsize bufSize = imgData->get_size();
+ const void *buf = imgData->get_data(bufSize);
+ g_debug("%s: Got PNG byte: %" FMTSZ "u\n", __FUNCTION__, bufSize);
+
+ if (bufSize > 0 &&
+ bufSize <= CPCLIPITEM_MAX_SIZE_V3 &&
+ CPClipboard_SetItem(&mClipboard, CPFORMAT_IMG_PNG,
+ buf, bufSize)) {
+ mCP->DestUISendClip(&mClipboard);
+ // Reset the host and guest clip gen number.
+ gHostClipGenNum = 0;
+ gGuestClipGenNum = 0;
+ } else {
+ /* clipboard failure is not critical one, warn but do not error exit */
+ g_warning("%s: Failed to copy image from guest clipboard,"
+ " either image is empty or too large\n", __FUNCTION__);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::CopyPasteRequestTextCb--
+ *
+ * This Callback read's the guest's text data from clipboard. If succeeded,
+ * the data is sent to Host.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::CopyPasteRequestTextCb(const Glib::RefPtr<Gio::AsyncResult>& result, // IN
+ const Glib::RefPtr<Gdk::Clipboard>& clipboard) // IN
+{
+ try {
+ /* gdk_clipboard_read_text_finish return NUL terminated UTF-8 string */
+ Glib::ustring cpStr = clipboard->read_text_finish(result);
+ g_debug("%s: Clip is [%s]\n", __FUNCTION__, cpStr.c_str());
+ CopyPasteSendText(cpStr);
+ } catch (const Glib::Error& e) {
+ /* clipboard failure is not critical one, warn but do not error exit */
+ g_warning("%s: Error to copy from guest: %s\n", __FUNCTION__, e.what());
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::CopyPasteRequestImageCb--
+ *
+ * This Callback read's the guest's image data from clipboard. If succeeded,
+ * the data is sent to Host.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::CopyPasteRequestImageCb(const Glib::RefPtr<Gio::AsyncResult>& result, // IN
+ const Glib::RefPtr<Gdk::Clipboard>& clipboard) // IN
+{
+ try {
+ Glib::RefPtr<Gdk::Texture> image = clipboard->read_texture_finish(result);
+ if (image) {
+ Glib::RefPtr<Glib::Bytes> imgData = image->save_to_png_bytes();
+ CopyPasteSendImage(imgData);
+ }
+ } catch (const Glib::Error& e) {
+ /* clipboard failure is not critical one, warn but do not error exit */
+ g_warning("%s: Error to copy from guest: %s\n", __FUNCTION__, e.what());
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteSendRtfValue--
+ *
+ * Get GBytes from the Gio::InputStream, and sent the buffer to host.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Host clipboard is changed.
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::CopyPasteSendRtfValue(const Glib::RefPtr<Gio::AsyncResult>& result) // IN
+{
+ Glib::RefPtr<Glib::Bytes> rtfData = mCpStream->read_bytes_finish(result);
+ size_t readSize = rtfData->get_size();
+
+ g_debug("%s: Got Glib:Bytes from clipboard with size=%" FMTSZ "u\n",
+ __FUNCTION__, readSize);
+
+ if (readSize > 0) {
+ const void *buf = rtfData->get_data(readSize);
+
+ if (readSize > CPCLIPITEM_MAX_SIZE_V3) {
+ g_warning("%s: Failed to copy rtf from guest clipboard,"
+ " as rtf data is too large\n, sizeLimit=%" FMTSZ "u, actualSize=%" FMTSZ "u",
+ __FUNCTION__, CPCLIPITEM_MAX_SIZE_V3, readSize);
+ return;
+ }
+ if (CPClipboard_SetItem(&mClipboard, CPFORMAT_RTF,
+ buf, readSize)) {
+ mCP->DestUISendClip(&mClipboard);
+ // Reset the host and guest clip gen number.
+ gHostClipGenNum = 0;
+ gGuestClipGenNum = 0;
+ } else {
+ /* clipboard failure is not critical one, warn but do not error exit */
+ g_warning("%s: Failed to set rtf data to dest buffer from guest clipboard", __FUNCTION__);
+ }
+ } else {
+ g_warning("%s: Failed to copy rtf from guest clipboard,"
+ " as rtf data is empty\n", __FUNCTION__);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::CopyPasteSendFile --
+ *
+ * Construct local file list and remote file list and send it to Host.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::CopyPasteSendFile(const Glib::RefPtr<Gio::AsyncResult>& result) // IN
+{
+ char *newPath;
+ size_t newPathLen;
+ size_t index = 0;
+ DnDFileList fileList;
+ DynBuf buf;
+ uint64 totalSize = 0;
+ int64 size;
+
+ /*
+ * Turn the uri list into two \0 delimited lists. One for full paths and
+ * one for just the last path component.
+ */
+ Glib::RefPtr<Glib::Bytes> fileUriData = mCpStream->read_bytes_finish(result);
+ gsize readSize = fileUriData->get_size();
+
+ g_debug("%s: Got Glib:Bytes from clipboard with size=%" FMTSZ "u\n",
+ __FUNCTION__, readSize);
+
+ if (readSize <= 0) {
+ g_warning("%s: Failed to copy file from guest clipboard,"
+ " as file uri data is empty or not valid.\n", __FUNCTION__);
+ return;
+ }
+
+ const char *data = static_cast<const char*>(fileUriData->get_data(readSize));
+ /* Converting data to utf::string directly is leading to buffer overflow.
+ * Its because data is Byte data and not NULL terminated. utf::string
+ * has no implementation for string creation with data size.
+ * In order to avoid that, first converting it to std::string and then
+ * converting to utf::string.
+ */
+ std::string str(data, readSize);
+ utf::string source(str.c_str());
+ g_debug("%s: Got file list: [%s]\n", __FUNCTION__, source.c_str());
+
+ /*
+ * In gnome, before file list there may be a extra line indicating it
+ * is a copy or cut.
+ */
+ if (source.startsWith("copy\n")) {
+ source = source.erase(0, 5);
+ }
+
+ if (source.startsWith("cut\n")) {
+ source = source.erase(0, 4);
+ }
+
+ while (source.bytes() > 0 &&
+ (source[0] == '\n' || source[0] == '\r' || source[0] == ' ')) {
+ source = source.erase(0, 1);
+ }
+
+ while ((newPath = DnD_UriListGetNextFile(source.c_str(),
+ &index,
+ &newPathLen)) != NULL) {
+ char *newRelPath;
+
+#if defined(__linux__)
+ if (DnD_UriIsNonFileSchemes(newPath)) {
+ /* Try to get local file path for non file uri. */
+ GFile *file = g_file_new_for_uri(newPath);
+ free(newPath);
+ if (!file) {
+ g_debug("%s: g_file_new_for_uri failed\n", __FUNCTION__);
+ return;
+ }
+ newPath = g_file_get_path(file);
+ g_object_unref(file);
+ if (!newPath) {
+ g_debug("%s: g_file_get_path failed\n", __FUNCTION__);
+ return;
+ }
+ }
+#endif
+
+ /*
+ * Parse relative path.
+ */
+ newRelPath = Str_Strrchr(newPath, DIRSEPC) + 1; // Point to char after '/'
+
+ /* Keep track of how big the fcp files are. */
+ if ((size = File_GetSizeEx(newPath)) >= 0) {
+ totalSize += size;
+ } else {
+ g_debug("%s: Unable to get file size for %s\n", __FUNCTION__, newPath);
+ }
+
+ g_debug("%s: Adding newPath '%s' newRelPath '%s'\n", __FUNCTION__,
+ newPath, newRelPath);
+ fileList.AddFile(newPath, newRelPath);
+ free(newPath);
+ }
+
+ DynBuf_Init(&buf);
+ bool validDataInClip = false;
+ fileList.SetFileSize(totalSize);
+ g_debug("%s: totalSize is %" FMT64 "u\n", __FUNCTION__, totalSize);
+ fileList.ToCPClipboard(&buf, false);
+ gsize bufSize = DynBuf_GetSize(&buf);
+ if (bufSize > 0 &&
+ bufSize <= (int)CPCLIPITEM_MAX_SIZE_V3 &&
+ CPClipboard_SetItem(&mClipboard, CPFORMAT_FILELIST, DynBuf_Get(&buf),
+ bufSize)) {
+ validDataInClip = true;
+ g_debug("%s: Got File List. \n", __FUNCTION__);
+ // Reset the host and guest clip gen number.
+ gHostClipGenNum = 0;
+ gGuestClipGenNum = 0;
+ } else {
+ g_debug("%s: Failed to get File List\n", __FUNCTION__);
+ }
+
+ DynBuf_Destroy(&buf);
+
+ if (validDataInClip) {
+ /*
+ * File List in the clipboard.
+ */
+ mCP->DestUISendClip(&mClipboard);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteRequestStreamCb--
+ *
+ * Get Gio::InputStream from clipboard, need to read from the resulting
+ * InputStream Async.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * class member mCpStream is modified.
+ *-----------------------------------------------------------------------------
+ */
+
+ void
+ CopyPasteUIX11::CopyPasteRequestStreamCb(const Glib::RefPtr<Gio::AsyncResult>& result, // IN
+ const Glib::RefPtr<Gdk::Clipboard>& clipboard) // IN
+ {
+ Glib::ustring out_mime_type;
+
+ /* Get the InputStream from clipboard */
+ mCpStream = clipboard->read_finish(result, out_mime_type);
+ g_debug("%s: Got rtf format [%s] from clipboard\n", __FUNCTION__, out_mime_type.c_str());
+
+ /*
+ * get GBytes from the stream and CopyPasteSendValue callback when finish.
+ * Here, a max of CPCLIPITEM_MAX_SIZE_V3+1 size is set, if we really read that much
+ * then we could error out after read_bytes_finish is called.
+ */
+ if (out_mime_type == TARGET_NAME_APPLICATION_RTF ||
+ out_mime_type == TARGET_NAME_TEXT_RICHTEXT ||
+ out_mime_type == TARGET_NAME_TEXT_RTF) {
+ mCpStream->read_bytes_async(CPCLIPITEM_MAX_SIZE_V3 + 1,
+ sigc::mem_fun(*this, &CopyPasteUIX11::CopyPasteSendRtfValue), G_PRIORITY_DEFAULT);
+ } else if (out_mime_type == FCP_TARGET_NAME_GNOME_COPIED_FILES ||
+ out_mime_type == FCP_TARGET_NAME_URI_LIST) {
+ mCpStream->read_bytes_async(CPCLIPITEM_MAX_SIZE_V3 + 1,
+ sigc::mem_fun(*this, &CopyPasteUIX11::CopyPasteSendFile), G_PRIORITY_DEFAULT);
+ }
+ }
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::LocalSetFileListToClipboard --
+ *
+ * API to set the guest clipboard with file list.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * gHostClipGenNum is increased.
+ * Clipboard is set with File list.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::LocalSetFileListToClipboard(const char* desktop) // IN
+{
+ std::vector<Glib::RefPtr<Gdk::ContentProvider>> provider;
+ Glib::RefPtr<Gdk::ContentProvider> providerUnion;
+ Glib::RefPtr<Glib::Bytes> fileListGnome;
+ Glib::RefPtr<Glib::Bytes> fileListKde;
+ Glib::RefPtr<Glib::Bytes> fileListNautilus;
+
+ g_debug("%s: enter.\n", __FUNCTION__);
+ g_debug("%s: Desktop = %s\n", __FUNCTION__, desktop);
+
+ if (desktop && strstr(desktop, "GNOME")) {
+ // Set the Gnome file list to GDKContentProvider
+ fileListGnome = Glib::Bytes::create(mHGCopiedUriListGnome.c_str(), mHGCopiedUriListGnome.bytes());
+ fileListNautilus = Glib::Bytes::create(mHGCopiedUriListNautilus.c_str(), mHGCopiedUriListNautilus.bytes());
+ provider.push_back(Gdk::ContentProvider::create(FCP_TARGET_NAME_GNOME_COPIED_FILES, fileListGnome));
+ provider.push_back(Gdk::ContentProvider::create(FCP_TARGET_NAME_NAUTILUS_GTK4_UTF8, fileListNautilus));
+ } else if (desktop && strstr(desktop, "KDE")) {
+ // Set the Kde file list to GDKContentProvider
+ fileListKde = Glib::Bytes::create(mHGCopiedUriListKde.c_str(), mHGCopiedUriListKde.bytes());
+ provider.push_back(Gdk::ContentProvider::create(FCP_TARGET_NAME_URI_LIST, fileListKde));
+ } else {
+ g_warning("%s: Unsupported Desktop Manager %s. \n", __FUNCTION__, desktop);
+ return;
+ }
+
+ providerUnion = Gdk::ContentProvider::create(provider);
+ mDftClipboardPtr->set_content(providerUnion);
+ mPrimClipboardPtr->set_content(providerUnion);
+
+ /* Increase the Host Clip number by 2 as we are updating the
+ * Clipboard content for both default and Primary Clipboard.
+ */
+ gHostClipGenNum += 2;
+ return;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::BuildFileURIList --
+ *
+ * Used to populate the file URI lists for GNOME, Nautilus, and KDE.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::BuildFileURIList(utf::string& uriList, // IN/OUT
+ utf::string pre, // IN
+ utf::string post, // IN
+ utf::string stagingDirName) // IN
+{
+ size_t pos = 0;
+ utf::string str;
+
+ while ((str = GetNextPath(mHGFCPData, pos).c_str()).bytes() != 0) {
+ g_debug("%s: Path: %s", __FUNCTION__, str.c_str());
+ uriList += pre;
+ if (mBlockAdded) {
+ uriList += mBlockCtrl->blockRoot;
+ uriList += DIRSEPS + stagingDirName + DIRSEPS + str + post;
+ } else {
+ uriList += DIRSEPS + mHGStagingDir + DIRSEPS + str + post;
+ }
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::BuildFileContentURIList --
+ *
+ * Used to populate the file content URI lists for GNOME, Nautilus, and KDE.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::BuildFileContentURIList(utf::string& uriList, // IN/OUT
+ utf::string pre, // IN
+ utf::string post) // IN
+{
+ for (const auto& path : mHGFileContentsList) {
+ uriList += pre + path + post;
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::LocalGetFileRequest --
+ *
+ * Begins copying the files from host to guest and return the file list.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::LocalGetFileRequest()
+{
+ g_debug("%s: enter.\n", __FUNCTION__);
+
+ if (!mCP->IsCopyPasteAllowed()) {
+ g_debug("%s: copy paste not allowed, returning.\n",
+ __FUNCTION__);
+ return;
+ }
+
+ const char *desktop = getenv("XDG_CURRENT_DESKTOP");
+
+ if (mHGGetFileStatus != DND_FILE_TRANSFER_NOT_STARTED) {
+ /*
+ * On KDE (at least), we can see this multiple times, so ignore if
+ * we are already getting files.
+ */
+ g_debug("%s: GetFiles already started, returning uriList [%s]\n",
+ __FUNCTION__, mHGCopiedUriListGnome.c_str());
+ LocalSetFileListToClipboard(desktop);
+ return;
+ } else {
+ utf::string hgStagingDir;
+ utf::string stagingDirName;
+
+ hgStagingDir = utf::CopyAndFree(DnD_CreateStagingDirectory());
+ g_debug("%s: Getting files. Staging dir: %s", __FUNCTION__,
+ hgStagingDir.c_str());
+
+ if (0 == hgStagingDir.bytes()) {
+ g_debug("%s: Can not create staging directory\n", __FUNCTION__);
+ mDftClipboardPtr->unset_content();
+ mPrimClipboardPtr->unset_content();
+ return;
+ }
+ mHGGetFileStatus = DND_FILE_TRANSFER_IN_PROGRESS;
+
+ mBlockAdded = false;
+ if (DnD_BlockIsReady(mBlockCtrl) && mBlockCtrl->AddBlock(mBlockCtrl->fd, hgStagingDir.c_str())) {
+ g_debug("%s: add block for %s.\n",
+ __FUNCTION__, hgStagingDir.c_str());
+ mBlockAdded = true;
+ pthread_mutex_lock(&mThreadParams.fileBlockMutex);
+ mThreadParams.fileBlockCondExit = false;
+ mThreadParams.fileBlockName = VMBLOCK_FUSE_NOTIFY_ROOT;
+ mThreadParams.fileBlockName += DIRSEPS;
+ mThreadParams.fileBlockName += GetLastDirName(hgStagingDir);
+ pthread_cond_signal(&mThreadParams.fileBlockCond);
+ pthread_mutex_unlock(&mThreadParams.fileBlockMutex);
+ } else {
+ g_debug("%s: unable to add block for %s.\n",
+ __FUNCTION__, hgStagingDir.c_str());
+ }
+
+ mHGStagingDir = hgStagingDir;
+
+ /* Provide path within vmblock file system instead of actual path. */
+ stagingDirName = GetLastDirName(hgStagingDir);
+ if (stagingDirName.empty()) {
+ g_debug("%s: Can not get staging directory name\n", __FUNCTION__);
+ mDftClipboardPtr->unset_content();
+ mPrimClipboardPtr->unset_content();
+ return;
+ }
+
+ if (desktop && strstr(desktop, "GNOME")) {
+ /* Setting Gnome URIs for each path in the guest's file list. */
+ mHGCopiedUriListGnome = "copy\n";
+ BuildFileURIList(mHGCopiedUriListGnome, FCP_GNOME_LIST_PRE, FCP_GNOME_LIST_POST, stagingDirName);
+
+ /* Nautilus on Gnome does not expect FCP_GNOME_LIST_POST after the last uri. See bug 143147. */
+ mHGCopiedUriListGnome.erase(mHGCopiedUriListGnome.size() - strlen(FCP_GNOME_LIST_POST));
+ g_debug("%s: providing file list [%s]\n", __FUNCTION__,
+ mHGCopiedUriListGnome.c_str());
+
+ /* Setting Nautilus URIs for each path in the guest's file list. */
+ mHGCopiedUriListNautilus = utf::string(FCP_TARGET_MIME_NAUTILUS_FILES) + "\ncopy\n";
+ BuildFileURIList(mHGCopiedUriListNautilus, FCP_GNOME_LIST_PRE, FCP_GNOME_LIST_POST, stagingDirName);
+ } else if (desktop && strstr(desktop, "KDE")) {
+ /* Setting KDE URIs for each path in the guest's file list. */
+ BuildFileURIList(mHGCopiedUriListKde, DND_URI_LIST_PRE_KDE, DND_URI_LIST_POST, stagingDirName);
+ g_debug("%s: providing file list [%s]\n", __FUNCTION__,
+ mHGCopiedUriListKde.c_str());
+ } else {
+ g_warning("%s: Unsupported Desktop Manager %s. \n", __FUNCTION__, desktop);
+ return;
+ }
+ }
+
+ if (!mBlockAdded) {
+ /*
+ * If there is no blocking driver, wait here till file copy is done.
+ * 2 reasons to keep this:
+ * 1. If run vmware-user stand-alone as non-root, blocking driver can
+ * not be opened. g_debug purpose only.
+ * 2. Other platforms (Solaris, etc) may also use this code,
+ * and there is no blocking driver yet.
+ *
+ * Polling here will not be sufficient for large files (experiments
+ * showed it was sufficient for a 256MB file, and failed for a 1GB
+ * file, but those numbers are of course context-sensitive and so YMMV).
+ * The reason is we are executing in the context of gtkmm callback, and
+ * apparently it only has so much patience regarding how quickly we
+ * return.
+ */
+ CopyPasteDnDWrapper *wrapper = CopyPasteDnDWrapper::GetInstance();
+ ToolsAppCtx *ctx = wrapper->GetToolsAppCtx();
+ while (mHGGetFileStatus == DND_FILE_TRANSFER_IN_PROGRESS) {
+ struct timeval tv {};
+
+ tv.tv_sec = 0;
+ g_main_context_iteration(g_main_loop_get_context(ctx->mainLoop),
+ FALSE);
+ if (select(0, NULL, NULL, NULL, &tv) == -1) {
+ g_debug("%s: error in select (%s).\n", __FUNCTION__,
+ strerror(errno));
+ mDftClipboardPtr->unset_content();
+ mPrimClipboardPtr->unset_content();
+ return;
+ }
+ }
+ g_debug("%s: file transfer done!\n", __FUNCTION__);
+ }
+
+ /* Set the content of clipboard with the File URI's. */
+ LocalSetFileListToClipboard(desktop);
+ return;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::GetLastDirName --
+ *
+ * Try to get last directory name from a full path name.
+ *
+ * Results:
+ * Last dir name in the full path name if success, empty str otherwise
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+utf::string
+CopyPasteUIX11::GetLastDirName(const utf::string &str) // IN
+{
+ /* This is a common function with DNDUIX11 class.
+ * When we implement Drag and Drop with GTK4, we can
+ * evaluate and see if this needs to be moved to a common
+ * util file.
+ */
+
+ char *baseName;
+ File_GetPathName(str.c_str(), NULL, &baseName);
+ return utf::CopyAndFree(baseName);
+}
+
+
+/*
+ *----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::GetNextPath --
+ *
+ * Provides a substring containing the next path from the provided
+ * NUL-delimited string starting at the provided index.
+ *
+ * Results:
+ * A string with the next path or "" if there are no more paths.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------------
+ */
+
+utf::utf8string
+CopyPasteUIX11::GetNextPath(utf::utf8string& str, // IN: NUL-delimited path list
+ size_t& index) // IN/OUT: current index into string
+{
+ /* This is a common function with DNDUIX11 class.
+ * When we implement Drag and Drop with GTK4, we can
+ * evaluate and see if this needs to be moved to a common
+ * util file.
+ */
+
+ utf::utf8string ret;
+ size_t start;
+
+ if (index >= str.length()) {
+ return "";
+ }
+
+ for (start = index; str[index] != '\0' && index < str.length(); index++) {
+ /*
+ * Escape reserved characters according to RFC 1630. We'd use
+ * Escape_Do() if this wasn't a utf::string, but let's use the same table
+ * replacement approach.
+ */
+ static char const Dec2Hex[] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+ };
+
+ unsigned char ubyte = str[index];
+
+ if (ubyte == '#' || /* Fragment identifier delimiter */
+ ubyte == '?' || /* Query string delimiter */
+ ubyte == '*' || /* "Special significance within specific schemes" */
+ ubyte == '!' || /* "Special significance within specific schemes" */
+ ubyte == '%' || /* Escape character */
+ ubyte >= 0x80) { /* UTF-8 encoding bytes */
+ str.replace(index, 1, "%");
+ str.insert(index + 1, 1, Dec2Hex[ubyte >> 4]);
+ str.insert(index + 2, 1, Dec2Hex[ubyte & 0xF]);
+ index += 2;
+ }
+ }
+
+ ret = str.substr(start, index - start);
+ g_debug("%s: nextpath: %s", __FUNCTION__, ret.c_str());
+ index++;
+ return ret;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::GetCurrentTime --
+ *
+ * Get current time in microseconds.
+ *
+ * Results:
+ * Time in microseconds.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+VmTimeType
+CopyPasteUIX11::GetCurrentTime(void)
+{
+ struct timeval tv;
+ VmTimeType curTime;
+
+ if (gettimeofday(&tv, NULL) != 0) {
+ g_debug("%s: gettimeofday failed!\n", __FUNCTION__);
+ return static_cast<VmTimeType>(0);
+ }
+ curTime = (tv.tv_sec * 1000000 + tv.tv_usec);
+ return curTime;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::LocalGetFileContentsRequest --
+ *
+ * Sets the files content uri list to clipboard.
+ *
+ * H->G only.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::LocalGetFileContentsRequest()
+{
+ if (!mCP->CheckCapability(DND_CP_CAP_FILE_CONTENT_CP)) {
+ /*
+ * Disallowed based on caps settings, return.
+ */
+ return;
+ }
+
+ const char *desktop = getenv("XDG_CURRENT_DESKTOP");
+
+ if (desktop && strstr(desktop, "GNOME")) {
+ /* Setting Gnome URIs for each path in the guest's file list. */
+ mHGCopiedUriListGnome = "copy\n";
+ BuildFileContentURIList(mHGCopiedUriListGnome, FCP_GNOME_LIST_PRE, FCP_GNOME_LIST_POST);
+
+ /* Nautilus on Gnome does not expect FCP_GNOME_LIST_POST after the last uri. See bug 143147. */
+ mHGCopiedUriListGnome.erase(mHGCopiedUriListGnome.size() - strlen(FCP_GNOME_LIST_POST));
+ g_debug("%s: providing file list [%s]\n", __FUNCTION__, mHGCopiedUriListGnome.c_str());
+
+ /* Setting Nautilus URIs for each path in the guest's file list. */
+ mHGCopiedUriListNautilus = utf::string(FCP_TARGET_MIME_NAUTILUS_FILES) + "\ncopy\n";
+ BuildFileContentURIList(mHGCopiedUriListNautilus, FCP_GNOME_LIST_PRE, FCP_GNOME_LIST_POST);
+ } else if (desktop && strstr(desktop, "KDE")) {
+ /* Setting KDE URIs for each path in the guest's file list. */
+ BuildFileContentURIList(mHGCopiedUriListKde, DND_URI_LIST_PRE_KDE, DND_URI_LIST_POST);
+ g_debug("%s: providing file list [%s]\n", __FUNCTION__, mHGCopiedUriListKde.c_str());
+ } else {
+ g_warning("%s: Unsupported Desktop Manager %s. \n", __FUNCTION__, desktop);
+ return;
+ }
+
+ /* Set the content of clipboard with the File URI's. */
+ LocalSetFileListToClipboard(desktop);
+ return;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::GetRemoteClipboardCB --
+ *
+ * Invoked when got data from host. Update the internal data to get the file
+ * names or the text that needs to be transferred.
+ *
+ * Method for copy and paste from host to guest.
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::GetRemoteClipboardCB(const CPClipboard *clip) // IN
+{
+ g_debug("%s: enter.", __FUNCTION__);
+ void *buf;
+ size_t sz;
+ std::vector<Glib::RefPtr<Gdk::ContentProvider>> provider;
+
+
+ TRACE_CALL();
+ if (!clip) {
+ g_debug("%s: No clipboard contents.", __FUNCTION__);
+ return;
+ }
+
+ if (mBlockAdded) {
+ mBlockAdded = false;
+ if (DnD_BlockIsReady(mBlockCtrl)) {
+ mBlockCtrl->RemoveBlock(mBlockCtrl->fd, mHGStagingDir.c_str());
+ }
+ }
+
+ mHGFCPData.clear();
+
+ if (CPClipboard_ItemExists(clip, CPFORMAT_TEXT) ||
+ CPClipboard_ItemExists(clip, CPFORMAT_RTF)) {
+ Glib::RefPtr<Gdk::ContentProvider> providerUnion;
+ Glib::RefPtr<Glib::Bytes> textData;
+ Glib::RefPtr<Glib::Bytes> rtfData;
+
+ if (CPClipboard_GetItem(clip, CPFORMAT_RTF, &buf, &sz)) {
+ g_debug("%s: RTF data, size %" FMTSZ "u.\n", __FUNCTION__, sz);
+ rtfData = Glib::Bytes::create(buf, sz);
+ provider.push_back(Gdk::ContentProvider::create(TARGET_NAME_APPLICATION_RTF, rtfData));
+ provider.push_back(Gdk::ContentProvider::create(TARGET_NAME_TEXT_RICHTEXT, rtfData));
+ provider.push_back(Gdk::ContentProvider::create(TARGET_NAME_TEXT_RTF, rtfData));
+ }
+
+ if (CPClipboard_GetItem(clip, CPFORMAT_TEXT, &buf, &sz)) {
+ g_debug("%s: Text data, size %" FMTSZ "u.\n", __FUNCTION__, sz);
+ textData = Glib::Bytes::create(buf, sz);
+ provider.push_back(Gdk::ContentProvider::create(TARGET_NAME_TEXT_PLAIN, textData));
+ provider.push_back(Gdk::ContentProvider::create(TARGET_NAME_TEXT_PLAIN_UTF8, textData));
+ }
+
+ providerUnion = Gdk::ContentProvider::create(provider);
+
+ if (providerUnion) {
+ mDftClipboardPtr->set_content(providerUnion);
+ mPrimClipboardPtr->set_content(providerUnion);
+ g_debug("%s: RTF/Text data is set to Clipboard. \n", __FUNCTION__);
+
+ /* Increase the Host Clip number by 2 as we are updating the
+ * Clipboard content for both default and Primary Clipboard.
+ */
+ gHostClipGenNum += 2;
+ } else {
+ g_debug("%s: Error in setting RTF/Text data to clipboard. \n", __FUNCTION__);
+ }
+
+ return;
+ }
+
+ if (CPClipboard_GetItem(clip, CPFORMAT_IMG_PNG, &buf, &sz)) {
+ g_debug("%s: PNG data, size %" FMTSZ "u.\n", __FUNCTION__, sz);
+ /* Try to load buf into gdk texture, and write to local clipboard. */
+ Glib::RefPtr<Glib::Bytes> imgData = Glib::Bytes::create(buf, sz);
+
+ try {
+ Glib::RefPtr<Gdk::Texture> image = Gdk::Texture::create_from_bytes(imgData);
+ if (image) {
+ mDftClipboardPtr->set_texture(image);
+ mPrimClipboardPtr->set_texture(image);
+ g_debug("%s: Image data is set to Clipboard. \n", __FUNCTION__);
+
+ /* Increase the Host Clip number by 2 as we are updating the
+ * Clipboard content for both default and Primary Clipboard.
+ */
+ gHostClipGenNum += 2;
+ }
+ } catch (const Glib::Error& e) {
+ /* clipboard failure is not critical one, warn but do not error exit */
+ g_warning("%s: Error in setting Image data to clipboard: %s\n", __FUNCTION__, e.what());
+ }
+
+ return;
+ }
+
+ if (CPClipboard_GetItem(clip, CPFORMAT_FILELIST, &buf, &sz)) {
+ g_debug("%s: File data.\n", __FUNCTION__);
+ DnDFileList flist;
+ flist.FromCPClipboard(buf, sz);
+ mTotalFileSize = flist.GetFileSize();
+ mHGFCPData = flist.GetRelPathsStr();
+ mHGGetListTime = GetCurrentTime();
+ mHGGetFileStatus = DND_FILE_TRANSFER_NOT_STARTED;
+ mHGCopiedUriListGnome.clear();
+ mHGCopiedUriListKde.clear();
+ mHGCopiedUriListNautilus.clear();
+ LocalGetFileRequest();
+ }
+
+ if (CPClipboard_ItemExists(clip, CPFORMAT_FILECONTENTS)) {
+ g_debug("%s: File contents data\n", __FUNCTION__);
+ if (LocalPrepareFileContents(clip, mHGFileContentsList)) {
+ mHGCopiedUriListGnome.clear();
+ mHGCopiedUriListKde.clear();
+ mHGCopiedUriListNautilus.clear();
+ LocalGetFileContentsRequest();
+ }
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::SendClipNotChanged --
+ *
+ * Send a not-changed clip to host.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::SendClipNotChanged(void)
+{
+ CPClipboard clip;
+
+ g_debug("%s: enter.\n", __FUNCTION__);
+ CPClipboard_Init(&clip);
+ CPClipboard_SetChanged(&clip, FALSE);
+ mCP->DestUISendClip(&clip);
+ CPClipboard_Destroy(&clip);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::Reset --
+ *
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::Reset(void)
+{
+ TRACE_CALL();
+ /* Cancel any pending file transfer. */
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::FileBlockMonitorThread --
+ *
+ * This thread monitors the access to the files in the clipboard which owns
+ * by VMTools by using the notification mechanism of VMBlock.
+ * If any access to the file is detected, then this thread requests the file
+ * transfer from host to guest.
+ *
+ * Results:
+ * Always return NULL
+ *
+ * Side effects:
+ * None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void*
+CopyPasteUIX11::FileBlockMonitorThread(void *arg) // IN
+{
+ /* This is a common function with GTK3 implementation of CopyPasteUIX11 class.
+ * Keeping it seperate as this API modifies the class member variables
+ * mThread and mThreadParams.
+ */
+
+ TRACE_CALL();
+ ThreadParams *params = static_cast<ThreadParams*>(arg);
+ pthread_mutex_lock(¶ms->fileBlockMutex);
+ while (true) {
+ g_debug("%s: waiting signal\n", __FUNCTION__);
+ pthread_cond_wait(¶ms->fileBlockCond, ¶ms->fileBlockMutex);
+ g_debug("%s: received signal. Exit:%d\n",
+ __FUNCTION__,
+ params->fileBlockCondExit);
+ if (params->fileBlockCondExit) {
+ break;
+ }
+ if (params->fileBlockName.bytes() == 0) {
+ continue;
+ }
+
+ int fd = open(params->fileBlockName.c_str(), O_RDONLY);
+ if (fd < 0) {
+ g_debug("%s: Failed to open %s, errno is %d\n",
+ __FUNCTION__,
+ params->fileBlockName.c_str(),
+ errno);
+ continue;
+ }
+
+ char buf[sizeof VMBLOCK_FUSE_READ_RESPONSE];
+ ssize_t size;
+ size = read(fd, buf, sizeof buf);
+ g_debug("%s: Number of bytes read : %" FMTSZ "d\n", __FUNCTION__, size);
+ /*
+ * The current thread will block in read function until
+ * any other application accesses the file params->fileBlockName
+ * or the block on the file params->fileBlockName is removed.
+ * Currently we don't need to check the response in buf, so
+ * just ignore it.
+ */
+
+ if (params->cp->IsBlockAdded()) {
+ g_debug("%s: Request files\n", __FUNCTION__);
+ params->cp->RequestFiles();
+ } else {
+ g_debug("%s: Block is not added\n", __FUNCTION__);
+ }
+
+ if (close(fd) < 0) {
+ g_debug("%s: Failed to close %s, errno is %d\n",
+ __FUNCTION__,
+ params->fileBlockName.c_str(),
+ errno);
+ }
+ }
+ pthread_mutex_unlock(¶ms->fileBlockMutex);
+ return NULL;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * CopyPasteUIX11::TerminateThread --
+ *
+ * This is called when the monitor thread is asked to exit.
+ * Sets the global state and signals the monitor thread to exit and wait
+ * for the monitor thread to exit.
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * None
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+CopyPasteUIX11::TerminateThread()
+{
+ /* This is a common function with GTK3 implementation of CopyPasteUIX11 class.
+ * Keeping it seperate as this API modifies the class member variables
+ * mThread and mThreadParams.
+ */
+
+ TRACE_CALL();
+ if (mThread == 0) {
+ return;
+ }
+
+ pthread_mutex_lock(&mThreadParams.fileBlockMutex);
+ mThreadParams.fileBlockCondExit = true;
+ pthread_cond_signal(&mThreadParams.fileBlockCond);
+ pthread_mutex_unlock(&mThreadParams.fileBlockMutex);
+
+ pthread_join(mThread, NULL);
+
+ mThread = 0;
+}
+
#define DRAG_LEAVE_TIMEOUT 500
/* Guest detection window width and height. */
+#ifdef GTK4
+#define DRAG_DET_WINDOW_WIDTH 62
+#else
#define DRAG_DET_WINDOW_WIDTH 31
+#endif
/* Clipboard image size limit. */
#define CLIPBOARD_IMAGE_MAX_WIDTH 4000
#define VMTOOLS_USE_LEGACY_GTK
+#ifdef GTK4
+#undef VMTOOLS_USE_LEGACY_GTK
+#endif
#include "dnd.h" /* for DnDBlockControl */
+#ifdef GTK4
+#include <gtkmm/application.h>
+#include "dndUIX11GTK4.h"
+
+#endif
#ifdef VMTOOLS_USE_LEGACY_GTK
#include "dndUIX11.h"
--- /dev/null
+/*********************************************************
+ * Copyright (c) 2025 Broadcom. All Rights Reserved.
+ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *********************************************************/
+
+/**
+ * @file dndUIX11GTK4.h
+ *
+ * Implement the methods that allow DnD between host and guest for
+ * protocols V3 or greater on GTK4 lib.
+ *
+ */
+
+#ifndef __DND_UI_X11_GTK4_H__
+#define __DND_UI_X11_GTK4_H__
+
+#include "stringxx/string.hh"
+#include "dnd.h"
+#include "str.h"
+#include "util.h"
+#include "vmblock.h"
+#include "dynbuf.h"
+#include "dynxdr.h"
+#include "posix.h"
+
+extern "C" {
+#include "debug.h"
+#include "dndClipboard.h"
+#include "../dnd/dndFileContentsUtil.h"
+#include "cpNameUtil.h"
+#include "vmware/tools/guestrpc.h"
+#include "vmware/tools/plugin.h"
+}
+
+#include "guestDnD.hh"
+#include "dndFileList.hh"
+#include "dragDetWndX11GTK4.h"
+
+struct DblLnkLst_Links;
+
+/**
+ * The DnDUI class implements the UI portion of DnD V3 and greater
+ * versions of the protocol.
+ */
+class DnDUIX11
+ : public sigc::trackable
+{
+public:
+ DnDUIX11(ToolsAppCtx *ctx);
+ ~DnDUIX11();
+ bool Init();
+ void VmxDnDVersionChanged(RpcChannel *chan, uint32 version);
+ void SetDnDAllowed(bool isDnDAllowed)
+ {ASSERT(mDnD); mDnD->SetDnDAllowed(isDnDAllowed);}
+ void SetBlockControl(DnDBlockControl *blockCtrl);
+
+private:
+ /*
+ * Blocking FS Helper Functions.
+ */
+ void AddBlock();
+ void RemoveBlock();
+ void
+ BuildFileURIList(utf::string& uriList,
+ utf::string pre,
+ utf::string post,
+ utf::string stagingDirName);
+ void
+ BuildFileContentURIList(utf::string& uriList,
+ utf::string pre,
+ utf::string post);
+ void LocalGetFileRequest(utf::string stagingDirName, const char *desktop);
+ void LocalGetFileContentsRequest(const char *desktop);
+ void LocalSetContentProvider(const char* desktop);
+
+ /*
+ * Callbacks from Common DnD layer.
+ */
+
+ void ResetUI();
+ void OnMoveMouse(int32 x, int32 y);
+
+ /*
+ * Source functions for HG DnD.
+ */
+ void OnSrcDragBegin(const CPClipboard *clip, std::string stagingDir);
+ void OnSrcDrop();
+ void OnSrcCancel();
+
+ /*
+ * Called when GH DnD is completed.
+ */
+ void OnPrivateDrop(int32 x, int32 y);
+ void OnDestCancel();
+
+ /*
+ * Source functions for file transfer.
+ */
+ void OnGetFilesDone(bool success);
+
+ /*
+ * Callbacks for showing/hiding detection window.
+ */
+ void OnUpdateDetWnd(bool bShow, int32 x, int32 y);
+ /* TODO: Unity related functions are deprecated, remove it later */
+ void OnUpdateUnityDetWnd(bool bShow, uint32 unityWndId, bool bottom);
+ void OnDestMoveDetWndToMousePos();
+ void SourceDragStartDone();
+ void SourceUpdateFeedback(DND_DROPEFFECT effect);
+
+ /*
+ * Gtk+ Callbacks: Drag Source
+ */
+ static void OnGtkSrcDragBegin(const Glib::RefPtr< Gdk::Drag > &drag, void *ctx);
+ static void OnGtkSrcDragEnd(const Glib::RefPtr< Gdk::Drag > &drag, bool delete_data, void *ctx);
+ static bool OnGtkSrcDragCancel(const Glib::RefPtr< Gdk::Drag > &drag, Gdk::DragCancelReason reason, void *ctx);
+
+ /*
+ * Gtk+ Callbacks: Drag Destination.
+ */
+ static bool OnGtkDragAccept(const Glib::RefPtr< Gdk::Drop > &drop, void *ctx);
+ static Gdk::DragAction OnGtkDragEnter(double x, double y, void *ctx);
+ static Gdk::DragAction OnGtkDragMotion(double x, double y, void *ctx);
+ static bool OnGtkDragDrop(const Glib::ValueBase& value, double x, double y, void *ctx);
+ static void OnGtkDragLeave(void *ctx);
+ static void OnGtkDragDataReceived(void *ctx);
+ /*
+ * Target function for GH DnD. Makes call to common layer.
+ */
+ void TargetDragEnter();
+
+ /*
+ * Misc methods.
+ */
+ void InitGtk();
+
+ bool SetCPClipboardFromGtk_File(utf::string &source);
+ bool SetCPClipboardFromGtk_PlainText(utf::string &source);
+ bool SetCPClipboardFromGtk_RTF(utf::string &source);
+ bool SetCPClipboardFromGtk(std::string &data, std::string &drop_type);
+
+ utf::string GetLastDirName(const utf::string &str);
+ utf::utf8string GetNextPath(utf::utf8string &str, size_t& index);
+
+ static unsigned long GetTimeInMillis();
+
+ static inline bool TargetIsPlainText(const utf::string& target) {
+ return target == TARGET_NAME_STRING
+ || target == TARGET_NAME_TEXT_PLAIN
+ || target == TARGET_NAME_UTF8_STRING
+ || target == TARGET_NAME_COMPOUND_TEXT;
+ }
+
+ static inline bool TargetIsRichText(const utf::string& target) {
+ return target == TARGET_NAME_APPLICATION_RTF
+ || target == TARGET_NAME_TEXT_RICHTEXT
+ || target == TARGET_NAME_TEXT_RTF;
+ }
+
+ void UpdateWorkArea();
+
+ ToolsAppCtx *mCtx;
+ GuestDnDMgr *mDnD;
+ utf::string mHGStagingDir;
+ std::vector<utf::string> mHGFileContentsList;
+ DragDetWnd *mDetWnd;
+ CPClipboard mClipboard;
+ DnDBlockControl *mBlockCtrl;
+ DND_FILE_TRANSFER_STATUS mHGGetFileStatus;
+ bool mBlockAdded;
+
+ /* State to determine if drag motion is a drag enter. */
+ bool mGHDnDInProgress;
+ /* Icon updates from the guest. */
+ /* Only update mouse when we have clipboard contents from the host. */
+ bool mGHDnDDataReceived;
+ bool mInHGDrag;
+ // Flag to indicate if H->G drag has been triggered in guest by simulate mouse press
+ bool mHGDragStarted;
+ DND_DROPEFFECT mEffect;
+ int32 mMousePosX;
+ int32 mMousePosY;
+ int mNumPendingRequest;
+ unsigned long mDestDropTime;
+ uint64 mTotalFileSize;
+
+ /* GdkSurface used in dnd */
+ Glib::RefPtr< Gdk::Surface > mGdkSurface;
+ Glib::RefPtr<Gdk::ContentProvider> mProvider;
+ utf::string mHGDndUriList;
+ utf::utf8string mHGFCPData;
+
+ /*
+ * Upper left corner of our work area, a safe place for us to place
+ * our detection window without clashing with a windows parented to the
+ * composite overlay window.
+ */
+ int mOriginX;
+ int mOriginY;
+};
+
+#endif // __DND_UI_X11_H__
--- /dev/null
+/*********************************************************
+ * Copyright (c) 2025 Broadcom. All Rights Reserved.
+ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *********************************************************/
+
+/**
+ * @file dragDetWndGTK4.h
+ *
+ * Header for the DragDetWnd class.
+ */
+
+#ifndef DRAG_DET_WND_GTK4_H
+#define DRAG_DET_WND_GTK4_H
+
+#include <gtkmm.h>
+#include <gio/gio.h>
+#include <gdk/x11/gdkx.h>
+#include <X11/Xlib.h>
+
+#if !defined(DETWNDTEST)
+#include "dnd.h"
+#endif
+#include "fakeMouseWayland/fakeMouseWayland.h"
+
+using source_drag_begin_handler = void (const Glib::RefPtr< Gdk::Drag > &, void *);
+using source_drag_end_handler = void (const Glib::RefPtr< Gdk::Drag > &, bool, void *);
+using source_drag_cancel_handler = bool (const Glib::RefPtr< Gdk::Drag > &, Gdk::DragCancelReason, void *);
+using source_drag_prepare_handler = Glib::RefPtr< Gdk::ContentProvider > (double, double, void *);
+using target_drag_accept_handler = bool (const Glib::RefPtr< Gdk::Drop > &, void *);
+using target_drag_drop_handler = bool (const Glib::ValueBase &, double, double, void *);
+using target_drag_enter_handler = Gdk::DragAction (double, double, void *);
+using target_drag_leave_handler = void (void *);
+using target_drag_motion_handler = Gdk::DragAction (double, double, void *);
+using target_drag_value_handler = void (void *);
+
+class DragDetWnd
+{
+public:
+ DragDetWnd(int UseUInput_fd = -1);
+ virtual ~DragDetWnd();
+ GtkWindow * GetWnd_gobj();
+ Gtk::Widget *GetWnd();
+
+ bool register_source_drag_begin_handler(source_drag_begin_handler handler, void *ctx);
+ bool register_source_drag_end_handler(source_drag_end_handler handler, void *ctx);
+ bool register_source_drag_cancel_handler(source_drag_cancel_handler handler, void *ctx);
+ bool register_source_drag_prepare_handler(source_drag_prepare_handler handler, void *ctx);
+ bool register_target_drag_accept_handler(target_drag_accept_handler handler, void *ctx);
+ bool register_target_drag_enter_handler(target_drag_enter_handler handler, void *ctx);
+ bool register_target_drag_drop_handler(target_drag_drop_handler handler, void *ctx);
+ bool register_target_drag_leave_handler(target_drag_leave_handler handler, void *ctx);
+ bool register_target_drag_motion_handler(target_drag_motion_handler handler, void *ctx);
+ bool register_target_value_handler(target_drag_value_handler handler, void *ctx);
+ bool EnableDND(void);
+ bool DisableDND(void);
+ void UpdateDetWnd(bool Show, int32 x, int32 y);
+ bool SimulateXEvents(const bool showWidget, const bool buttonEvent,
+ const bool buttonPress, const bool moveWindow,
+ const bool coordsProvided,
+ const int xCoord, const int yCoord);
+ void StartDrag(Glib::RefPtr<Gdk::ContentProvider> provider,
+ int mouseX,
+ int mouseY);
+ bool Is_DropValue_Ready(void);
+ bool Get_Drop_Value(std::string &value, std::string &value_type);
+ bool Is_Drop_Supported(const Glib::RefPtr< Gdk::Drop > &drop);
+ bool Is_Current_Drop_Supported(void);
+ unsigned long Get_Current_Drag_ctx(void);
+ unsigned long Get_Current_Drop_ctx(void);
+ unsigned long Get_Detwnd_ctx(void);
+
+private:
+ void Show();
+ void Hide();
+ void Raise();
+ void Lower();
+ void Flush();
+ int GetScreenWidth();
+ int GetScreenHeight();
+ void SetGeometry(const int x, const int y,
+ const int width, const int height);
+ void GetGeometry(int &x, int &y, int &width, int &height);
+ void SetIsVisible(const bool isVisible) {m_isVisible = isVisible;};
+ bool GetIsVisible() {return m_isVisible;};
+ bool SimulateMouseMove(const int x, const int y);
+
+ bool m_isVisible;
+ Gtk::Window *mWnd;
+ Glib::RefPtr< Gtk::DragSource > mSource;
+ Glib::RefPtr< Gtk::DropTarget > mTarget;
+ bool mUseUInput;
+ int mScreenWidth;
+ int mScreenHeight;
+};
+
+#endif // DRAG_DET_WND_H
#include "vmware/tools/plugin.h"
}
+#ifdef GTK4
+/* Need to use higher timeout value in GTK4, otherwise G->H dnd will fail */
+#define UNGRAB_TIMEOUT 3000 // 3.0s
+#else
#define UNGRAB_TIMEOUT 500 // 0.5s
+#endif
#define HIDE_DET_WND_TIMER 500 // 0.5s
#define UNITY_DND_DET_TIMEOUT 500 // 0.5s
#define VMTOOLS_USE_LEGACY_GTK
+#ifdef GTK4
+#undef VMTOOLS_USE_LEGACY_GTK
+#endif
#include <X11/Xlib.h>
#include <gtk/gtk.h>
+#ifdef GTK4
+#include <gdk/x11/gdkx.h>
+#endif
#ifdef VMTOOLS_USE_LEGACY_GTK
#include <gdk/gdkx.h>
extern Display *gXDisplay;
extern Window gXRoot;
extern GtkWidget *gUserMainWidget;
+#ifdef GTK4
+extern GdkDisplay *gGdkDisplay;
+#endif
#endif
--- /dev/null
+/*********************************************************
+ * Copyright (c) 2025 Broadcom. All Rights Reserved.
+ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *********************************************************/
+
+/**
+ * @file dndUIX11GTK4.cpp --
+ *
+ * This class implements stubs for the methods that allow DnD between
+ * host and guest on GTK4 lib.
+ */
+
+#define G_LOG_DOMAIN "dndcp"
+
+#include "xutils/xutilsGTK4.hh"
+#include "copyPasteDnDUtil.h"
+#include "dndUIX11GTK4.h"
+#include "guestDnDCPMgr.hh"
+#include "tracer.hh"
+#include "fakeMouseWayland/fakeMouseWayland.h"
+
+extern "C" {
+#include "vmware/guestrpc/tclodefs.h"
+
+#include "copyPasteCompat.h"
+#include "cpName.h"
+#include "cpNameUtil.h"
+#include "dndClipboard.h"
+#include "hgfsUri.h"
+#include "rpcout.h"
+}
+
+#include "dnd.h"
+#include "dndMsg.h"
+#include "hostinfo.h"
+#include "file.h"
+#include "vmblock.h"
+
+/* IsXExtensionPointer may be not defined with old Xorg. */
+#ifndef IsXExtensionPointer
+#define IsXExtensionPointer 4
+#endif
+
+#include "copyPasteDnDWrapper.h"
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::DnDUIX11 --
+ *
+ * Constructor.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+DnDUIX11::DnDUIX11(ToolsAppCtx *ctx)
+ : mCtx(ctx),
+ mDnD(NULL),
+ mDetWnd(NULL),
+ mClipboard(),
+ mBlockCtrl(NULL),
+ mHGGetFileStatus(DND_FILE_TRANSFER_NOT_STARTED),
+ mBlockAdded(false),
+ mGHDnDInProgress(false),
+ mGHDnDDataReceived(false),
+ mInHGDrag(false),
+ mEffect(DROP_NONE),
+ mMousePosX(0),
+ mMousePosY(0),
+ mNumPendingRequest(0),
+ mDestDropTime(0),
+ mTotalFileSize(0),
+ mOriginX(0),
+ mOriginY(0),
+ mGdkSurface(NULL)
+{
+ TRACE_CALL();
+
+
+ /*
+ * XXX Hard coded use of default screen means this doesn't work in dual-
+ * headed setups (e.g. DISPLAY=:0.1). However, the number of people running
+ * such setups in VMs is expected to be, like, hella small, so I'mma cut
+ * corners for now.
+ */
+ Glib::RefPtr<Gdk::Display> display = Gdk::Display::get_default();
+ GdkSurface *gdkSurface = gdk_x11_display_get_default_group(display->gobj());
+ mGdkSurface = Glib::wrap(gdkSurface);
+ UpdateWorkArea();
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::~DnDUIX11 --
+ *
+ * Destructor.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+DnDUIX11::~DnDUIX11()
+{
+ TRACE_CALL();
+
+ if (mDetWnd) {
+ delete mDetWnd;
+ mDetWnd = NULL;
+ }
+ CPClipboard_Destroy(&mClipboard);
+ /* Any files from last unfinished file transfer should be deleted. */
+ if (DND_FILE_TRANSFER_IN_PROGRESS == mHGGetFileStatus
+ && !mHGStagingDir.empty()) {
+ uint64 totalSize = File_GetSizeEx(mHGStagingDir.c_str());
+ if (mTotalFileSize != totalSize) {
+ g_debug("%s: deleting %s, expecting %" FMT64 "u, finished %" FMT64 "u\n",
+ __FUNCTION__, mHGStagingDir.c_str(),
+ mTotalFileSize, totalSize);
+ DnD_DeleteStagingFiles(mHGStagingDir.c_str(), FALSE);
+ } else {
+ g_debug("%s: file size match %s\n",
+ __FUNCTION__, mHGStagingDir.c_str());
+ }
+ }
+ ResetUI();
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::Init --
+ *
+ * Initialize DnDUIX11 object.
+ *
+ * Results:
+ * Returns true on success and false on failure.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool
+DnDUIX11::Init()
+{
+ TRACE_CALL();
+ bool ret = true;
+
+ CPClipboard_Init(&mClipboard);
+
+ GuestDnDCPMgr *p = GuestDnDCPMgr::GetInstance();
+ ASSERT(p);
+ mDnD = p->GetDnDMgr();
+ ASSERT(mDnD);
+
+ mDetWnd = new DragDetWnd(mCtx->uinputFD);
+ if (!mDetWnd) {
+ g_debug("%s: unable to allocate DragDetWnd object\n", __FUNCTION__);
+ goto fail;
+ }
+
+ InitGtk();
+
+#define CONNECT_SIGNAL(_obj, _sig, _cb) \
+ _obj->_sig.connect(sigc::mem_fun(this, &DnDUIX11::_cb))
+ /* Set common layer callbacks. */
+ CONNECT_SIGNAL(mDnD, srcDragBeginChanged, OnSrcDragBegin);
+ CONNECT_SIGNAL(mDnD, srcCancelChanged, OnSrcCancel);
+ CONNECT_SIGNAL(mDnD, getFilesDoneChanged, OnGetFilesDone);
+ CONNECT_SIGNAL(mDnD, moveMouseChanged, OnMoveMouse);
+ CONNECT_SIGNAL(mDnD, srcDropChanged, OnSrcDrop);
+ CONNECT_SIGNAL(mDnD, destCancelChanged, OnDestCancel);
+ CONNECT_SIGNAL(mDnD, destMoveDetWndToMousePosChanged, OnDestMoveDetWndToMousePos);
+ CONNECT_SIGNAL(mDnD, privDropChanged, OnPrivateDrop);
+ CONNECT_SIGNAL(mDnD, updateDetWndChanged, OnUpdateDetWnd);
+ /* TODO: Unity related functions are deprecated, remove it later */
+ CONNECT_SIGNAL(mDnD, updateUnityDetWndChanged, OnUpdateUnityDetWnd);
+#undef CONNECT_SIGNAL
+
+ mDetWnd->UpdateDetWnd(false, 0, 0);
+ goto out;
+fail:
+ ret = false;
+ if (mDnD) {
+ delete mDnD;
+ mDnD = NULL;
+ }
+ if (mDetWnd) {
+ delete mDetWnd;
+ mDetWnd = NULL;
+ }
+out:
+ return ret;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::InitGtk --
+ *
+ * Register supported DND target types and signal handlers with GTK+.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::InitGtk()
+{
+ TRACE_CALL();
+
+ mDetWnd->register_source_drag_begin_handler(OnGtkSrcDragBegin, (void *)this);
+ mDetWnd->register_source_drag_end_handler(OnGtkSrcDragEnd, (void *)this);
+ mDetWnd->register_source_drag_cancel_handler(OnGtkSrcDragCancel, (void *)this);
+ mDetWnd->register_target_drag_accept_handler(OnGtkDragAccept, (void *)this);
+ mDetWnd->register_target_drag_enter_handler(OnGtkDragEnter, (void *)this);
+ mDetWnd->register_target_drag_drop_handler(OnGtkDragDrop, (void *)this);
+ mDetWnd->register_target_drag_leave_handler(OnGtkDragLeave, (void *)this);
+ mDetWnd->register_target_drag_motion_handler(OnGtkDragMotion, (void *)this);
+ mDetWnd->register_target_value_handler(OnGtkDragDataReceived, (void *)this);
+ mDetWnd->EnableDND();
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::ResetUI --
+ *
+ * Reset UI state variables.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May remove a vmblock blocking entry.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::ResetUI()
+{
+ TRACE_CALL();
+ mGHDnDDataReceived = false;
+ mHGGetFileStatus = DND_FILE_TRANSFER_NOT_STARTED;
+ mGHDnDInProgress = false;
+ mEffect = DROP_NONE;
+ mInHGDrag = false;
+ RemoveBlock();
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::AddBlock --
+ *
+ * Insert a vmblock blocking entry.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Caller must pair with RemoveBlock() upon dnd completion/cancellation.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::AddBlock()
+{
+ TRACE_CALL();
+
+ if (mBlockAdded) {
+ g_debug("%s: block already added\n", __FUNCTION__);
+ return;
+ }
+
+ g_debug("%s: DnDBlockIsReady %d fd %d\n", __FUNCTION__,
+ DnD_BlockIsReady(mBlockCtrl), mBlockCtrl->fd);
+
+ if ( DnD_BlockIsReady(mBlockCtrl)
+ && mBlockCtrl->AddBlock(mBlockCtrl->fd, mHGStagingDir.c_str())) {
+ mBlockAdded = true;
+ g_debug("%s: add block for %s.\n", __FUNCTION__, mHGStagingDir.c_str());
+ } else {
+ mBlockAdded = false;
+ g_debug("%s: unable to add block dir %s.\n", __FUNCTION__, mHGStagingDir.c_str());
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::RemoveBlock --
+ *
+ * Remove a vmblock blocking entry.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::RemoveBlock()
+{
+ TRACE_CALL();
+
+ if (mBlockAdded && (DND_FILE_TRANSFER_IN_PROGRESS != mHGGetFileStatus)) {
+ g_debug("%s: removing block for %s\n", __FUNCTION__, mHGStagingDir.c_str());
+ /* We need to make sure block subsystem has not been shut off. */
+ if (DnD_BlockIsReady(mBlockCtrl)) {
+ mBlockCtrl->RemoveBlock(mBlockCtrl->fd, mHGStagingDir.c_str());
+ }
+ mBlockAdded = false;
+ } else {
+ g_debug("%s: not removing block mBlockAdded %d mHGGetFileStatus %d\n",
+ __FUNCTION__, mBlockAdded, mHGGetFileStatus);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::LocalSetContentProvider --
+ *
+ * Set content provider based on different desktop type.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * mProvider is chanaged.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::LocalSetContentProvider(const char* desktop) // IN
+{
+ std::vector<Glib::RefPtr<Gdk::ContentProvider>> providerVec;
+ Glib::RefPtr<Glib::Bytes> fileListGnome;
+ Glib::RefPtr<Glib::Bytes> fileListKde;
+ void *buf;
+ size_t sz;
+
+ g_debug("%s: enter.\n", __FUNCTION__);
+
+ if (CPClipboard_ItemExists(&mClipboard, CPFORMAT_FILELIST) ||
+ CPClipboard_ItemExists(&mClipboard, CPFORMAT_FILECONTENTS)) {
+ if (desktop && strstr(desktop, "GNOME")) {
+ // Set the Gnome file list to GDKContentProvider
+ fileListGnome = Glib::Bytes::create(mHGDndUriList.c_str(), mHGDndUriList.bytes());
+ providerVec.push_back(Gdk::ContentProvider::create(DRAG_TARGET_NAME_URI_LIST, fileListGnome));
+ } else if (desktop && strstr(desktop, "KDE")) {
+ // Set the Kde file list to GDKContentProvider
+ fileListKde = Glib::Bytes::create(mHGDndUriList.c_str(), mHGDndUriList.bytes());
+ providerVec.push_back(Gdk::ContentProvider::create(DRAG_TARGET_NAME_URI_LIST, fileListKde));
+ } else {
+ g_warning("%s: Unsupported Desktop Manager %s. \n", __FUNCTION__, desktop);
+ return;
+ }
+ } else if (CPClipboard_ItemExists(&mClipboard, CPFORMAT_RTF)) {
+ if (CPClipboard_GetItem(&mClipboard, CPFORMAT_RTF, &buf, &sz)) {
+ g_debug("%s: RTF data, size %" FMTSZ "u.\n", __FUNCTION__, sz);
+ Glib::RefPtr<Glib::Bytes> rtfData = Glib::Bytes::create(buf, sz);
+ providerVec.push_back(Gdk::ContentProvider::create(TARGET_NAME_APPLICATION_RTF, rtfData));
+ providerVec.push_back(Gdk::ContentProvider::create(TARGET_NAME_TEXT_RICHTEXT, rtfData));
+ providerVec.push_back(Gdk::ContentProvider::create(TARGET_NAME_TEXT_RTF, rtfData));
+ }
+ } else if (CPClipboard_ItemExists(&mClipboard, CPFORMAT_TEXT)) {
+ if (CPClipboard_GetItem(&mClipboard, CPFORMAT_TEXT, &buf, &sz)) {
+ g_debug("%s: Text data, size %" FMTSZ "u.\n", __FUNCTION__, sz);
+ Glib::RefPtr<Glib::Bytes> textData = Glib::Bytes::create(buf, sz);
+ providerVec.push_back(Gdk::ContentProvider::create(TARGET_NAME_TEXT_PLAIN, textData));
+ providerVec.push_back(Gdk::ContentProvider::create(TARGET_NAME_TEXT_PLAIN_UTF8, textData));
+ }
+ }
+ mProvider = Gdk::ContentProvider::create(providerVec);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::BuildFileURIList --
+ *
+ * Build URI file list with proper pre/post str.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::BuildFileURIList(utf::string& uriList, // IN/OUT
+ utf::string pre, // IN
+ utf::string post, // IN
+ utf::string stagingDirName) // IN
+{
+ size_t pos = 0;
+ utf::string str;
+
+ while ((str = GetNextPath(mHGFCPData, pos).c_str()).bytes() != 0) {
+ g_debug("%s: Path: %s", __FUNCTION__, str.c_str());
+ uriList += pre;
+ if (mBlockAdded) {
+ uriList += mBlockCtrl->blockRoot;
+ uriList += DIRSEPS + stagingDirName + DIRSEPS + str + post;
+ } else {
+ uriList += DIRSEPS + mHGStagingDir + DIRSEPS + str + post;
+ }
+ }
+ g_debug("%s: providing uriList [%s]\n", __FUNCTION__, uriList.c_str());
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::BuildFileContentURIList --
+ *
+ * Used to populate the file content URI lists for GNOME, Nautilus, and KDE.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::BuildFileContentURIList(utf::string& uriList, // IN/OUT
+ utf::string pre, // IN
+ utf::string post) // IN
+{
+ for (const auto& path : mHGFileContentsList) {
+ uriList += pre + path + post;
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::LocalGetFileRequest --
+ *
+ * Build URI file list with proper pre/post str.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * mHGDndUriList may be changed.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::LocalGetFileRequest(utf::string stagingDirName,
+ const char *desktop)
+{
+ mHGDndUriList = "";
+ if (desktop && strstr(desktop, "GNOME")) {
+ /* Setting Gnome URIs for each path in the guest's file list. */
+ BuildFileURIList(mHGDndUriList, FCP_GNOME_LIST_PRE, FCP_GNOME_LIST_POST, stagingDirName);
+ /* Nautilus on Gnome does not expect FCP_GNOME_LIST_POST after the last uri. See bug 143147. */
+ mHGDndUriList.erase(mHGDndUriList.size() - strlen(FCP_GNOME_LIST_POST));
+ g_debug("%s: providing file list [%s]\n", __FUNCTION__, mHGDndUriList.c_str());
+ } else if (desktop && strstr(desktop, "KDE")) {
+ /* Setting KDE URIs for each path in the guest's file list. */
+ BuildFileURIList(mHGDndUriList, DND_URI_LIST_PRE_KDE, DND_URI_LIST_POST, stagingDirName);
+ g_debug("%s: providing file list [%s]\n", __FUNCTION__, mHGDndUriList.c_str());
+ } else {
+ g_warning("%s: Unsupported Desktop Manager %s. \n", __FUNCTION__, desktop);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DndUIX11::LocalGetFileContentsRequest --
+ *
+ * Build the files content uri list when host dnd with file content.
+ * H->G only.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * mHGDndUriList may be changed.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::LocalGetFileContentsRequest(const char *desktop)
+{
+ mHGDndUriList = "";
+ if (desktop && strstr(desktop, "GNOME")) {
+ /* Setting Gnome URIs for each path in the guest's file list. */
+ BuildFileContentURIList(mHGDndUriList, FCP_GNOME_LIST_PRE, FCP_GNOME_LIST_POST);
+ /* Nautilus on Gnome does not expect FCP_GNOME_LIST_POST after the last uri. See bug 143147. */
+ mHGDndUriList.erase(mHGDndUriList.size() - strlen(FCP_GNOME_LIST_POST));
+ g_debug("%s: providing file list [%s]\n", __FUNCTION__, mHGDndUriList.c_str());
+ } else if (desktop && strstr(desktop, "KDE")) {
+ /* Setting KDE URIs for each path in the guest's file list. */
+ BuildFileContentURIList(mHGDndUriList, DND_URI_LIST_PRE_KDE, DND_URI_LIST_POST);
+ g_debug("%s: providing file list [%s]\n", __FUNCTION__, mHGDndUriList.c_str());
+ } else {
+ g_warning("%s: Unsupported Desktop Manager %s. \n", __FUNCTION__, desktop);
+ }
+}
+
+
+/* Source functions for HG DnD. */
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnSrcDragBegin --
+ *
+ * Called when host successfully detected a pending HG drag.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Calls mDetWnd->drag_begin().
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnSrcDragBegin(const CPClipboard *clip, // IN
+ const std::string stagingDir) // IN
+{
+ int mouseX = mOriginX + DRAG_DET_WINDOW_WIDTH / 2;
+ int mouseY = mOriginY + DRAG_DET_WINDOW_WIDTH / 2;
+ const char *desktop = getenv("XDG_CURRENT_DESKTOP");
+ bool hasContent = false;
+ mHGDragStarted = false;
+
+ /* Move detect window to the mouse in position */
+ OnUpdateDetWnd(true, mOriginX, mOriginY);
+
+ TRACE_CALL();
+
+ CPClipboard_Clear(&mClipboard);
+ CPClipboard_Copy(&mClipboard, clip);
+
+ /*
+ * Construct the target and action list, as well as a fake motion notify
+ * event that's consistent with one that would typically start a drag.
+ */
+ utf::string stagingDirName;
+ DnDFileList fList;
+ void *buf;
+ size_t sz;
+
+ mHGFCPData.clear();
+
+ if (CPClipboard_GetItem(&mClipboard, CPFORMAT_FILELIST, &buf, &sz)) {
+ mHGStagingDir = utf::string(stagingDir.c_str());
+
+ /* Provide path within vmblock file system instead of actual path. */
+ stagingDirName = GetLastDirName(mHGStagingDir);
+ if (stagingDirName.length() == 0) {
+ g_debug("%s: Cannot get staging directory name, stagingDir: %s\n",
+ __FUNCTION__, mHGStagingDir.c_str());
+ return;
+ }
+
+ if (!fList.FromCPClipboard(buf, sz)) {
+ g_debug("%s: Can't get data from clipboard\n", __FUNCTION__);
+ return;
+ }
+ mTotalFileSize = fList.GetFileSize();
+ // Provide path within vmblock file system instead of actual path.
+ mHGFCPData = fList.GetRelPathsStr();
+
+ if (!mBlockAdded) {
+ AddBlock();
+ } else {
+ g_debug("%s: not calling AddBlock\n", __FUNCTION__);
+ }
+ if (mHGGetFileStatus != DND_FILE_TRANSFER_IN_PROGRESS) {
+ mHGGetFileStatus = DND_FILE_TRANSFER_IN_PROGRESS;
+ }
+
+ LocalGetFileRequest(stagingDirName, desktop);
+ hasContent = true;
+ }
+
+ if (CPClipboard_ItemExists(&mClipboard, CPFORMAT_FILECONTENTS)) {
+ g_debug("%s: Get file content from host clipboard", __FUNCTION__);
+ LocalGetFileContentsRequest(desktop);
+ hasContent = true;
+ }
+
+ if (CPClipboard_ItemExists(&mClipboard, CPFORMAT_TEXT) ||
+ CPClipboard_ItemExists(&mClipboard, CPFORMAT_RTF)) {
+ g_debug("%s: Get text/rtfText content from host clipboard", __FUNCTION__);
+ hasContent = true;
+ }
+
+ /* finally set the guest clipboard */
+ if (hasContent) {
+ LocalSetContentProvider(desktop);
+ }
+
+ SourceDragStartDone();
+ /* Initialize host hide feedback to DROP_NONE. */
+ mEffect = DROP_NONE;
+ SourceUpdateFeedback(mEffect);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnMoveMouse --
+ *
+ * simulate mouse move for h->g dnd.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnMoveMouse(int32 x, // IN: Pointer x-coord
+ int32 y) // IN: Pointer y-coord
+{
+ // Position the pointer, and record its position.
+ mDetWnd->SimulateXEvents(false, false, false, false, true, x, y);
+ mMousePosX = x;
+ mMousePosY = y;
+
+ if (mInHGDrag) {
+ if (!mHGDragStarted) {
+ /*
+ * We want to simulate the mouse press and hold to trigger drag_begin
+ * event for GtkDragSource, do it here rather than SrcDragBegin as
+ * we want to get the mouse position.
+ *
+ * Before the DnD, we should make sure that the mouse is released
+ * otherwise it may be another DnD, not ours. Send a release, then
+ * a press here to cover this case.
+ */
+ mDetWnd->SimulateXEvents(true, true, false, true, true, x, y);
+ // simulate drag mouse press
+ mDetWnd->SimulateXEvents(false, true, true, false, true, x, y);
+ // Tell Gtk that a drag should be started from this widget.
+ mDetWnd->StartDrag(mProvider, x, y);
+ mHGDragStarted = true;
+ }
+ if (mEffect != DROP_MOVE) {
+ mEffect = DROP_MOVE;
+ g_debug("%s: Updating feedback\n", __FUNCTION__);
+ SourceUpdateFeedback(mEffect);
+ }
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnSrcCancel --
+ *
+ * Handler for when host cancels HG drag.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Detection window and fake mouse events.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnSrcCancel()
+{
+ TRACE_CALL();
+ /*
+ * Force the window to show, position the mouse over it, and release.
+ * Seems like moving the window to 0, 0 eliminates frequently observed
+ * flybacks when we cancel as user moves mouse in and out of destination
+ * window in a H->G DnD.
+ */
+ OnUpdateDetWnd(true, mOriginX, mOriginY);
+ mDetWnd->SimulateXEvents(true, true, false, true, true,
+ mOriginX + DRAG_DET_WINDOW_WIDTH / 2,
+ mOriginY + DRAG_DET_WINDOW_WIDTH / 2);
+ OnUpdateDetWnd(false, 0, 0);
+ mDetWnd->SimulateXEvents(false, false, false, false, true,
+ mMousePosX, mMousePosY);
+ mInHGDrag = false;
+ mHGGetFileStatus = DND_FILE_TRANSFER_NOT_STARTED;
+ mEffect = DROP_NONE;
+ RemoveBlock();
+}
+
+
+/* Source functions for GH DnD. */
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnPrivateDrop --
+ *
+ * Handler for private drop event.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Releases mouse button at current position.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnPrivateDrop(int32 x, // UNUSED
+ int32 y) // UNUSED
+{
+ TRACE_CALL();
+
+ /* Unity manager in host side may already send the drop into guest. */
+ if (mGHDnDInProgress) {
+
+ /*
+ * Release the mouse button.
+ */
+ mDetWnd->SimulateXEvents(false, true, false, false, false, 0, 0);
+ }
+ ResetUI();
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnDestCancel --
+ *
+ * Handler for GH drag cancellation.
+ *
+ * Note: This event fires as part of the complete guest-to-host sequence,
+ * not just error or user cancellation.
+ *
+ * Results:
+ * Uses detection window and fake mouse events to intercept drop.
+ *
+ * Side effects:
+ * Reinitializes UI state.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnDestCancel()
+{
+ TRACE_CALL();
+
+ /* Unity manager in host side may already send the drop into guest. */
+ if (mGHDnDInProgress) {
+ Glib::RefPtr<Gdk::Display> display = Gdk::Display::get_default();
+ GdkSurface *gdkSurface = gdk_x11_display_get_default_group(display->gobj());
+ UpdateWorkArea();
+
+ /*
+ * Show the window, move it to the mouse position, and release the
+ * mouse button.
+ */
+ mDetWnd->SimulateXEvents(true, true, false, true, false,
+ mOriginX, mOriginY);
+ }
+ mDestDropTime = GetTimeInMillis();
+ ResetUI();
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnSrcDrop --
+ *
+ * Callback when host signals drop.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Release the mouse button in the detection window.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnSrcDrop()
+{
+ TRACE_CALL();
+ OnUpdateDetWnd(true, mOriginX, mOriginY);
+
+ /*
+ * Move the mouse to the saved coordinates, and release the mouse button.
+ */
+ mDetWnd->SimulateXEvents(false, true, false, false, true,
+ mMousePosX, mMousePosY);
+ OnUpdateDetWnd(false, 0, 0);
+ /* We've successful or failure for HG drag, set the flag to false anyway */
+ mInHGDrag = false;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnGetFilesDone --
+ *
+ * Callback when HG file transfer completes.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Releases vmblock blocking entry.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnGetFilesDone(bool success) // IN: true if transfer succeeded
+{
+ g_debug("%s: %s\n", __FUNCTION__, success ? "success" : "failed");
+
+ /*
+ * If H->G drag is not done yet, only remove block. Otherwise destination
+ * may miss the data because we are already reset.
+ */
+
+ mHGGetFileStatus = DND_FILE_TRANSFER_FINISHED;
+
+ if (!mInHGDrag) {
+ ResetUI();
+ } else {
+ RemoveBlock();
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnUpdateDetWnd --
+ *
+ * Callback to show/hide drag detection window.
+ *
+ * Results:
+ * Shows/hides and moves detection window.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnUpdateDetWnd(bool show, // IN: show (true) or hide (false)
+ int32 x, // IN: detection window's destination x-coord
+ int32 y) // IN: detection window's destination y-coord
+{
+ g_debug("%s: enter 0x%lx show %d x %d y %d\n",
+ __FUNCTION__,
+ mDetWnd->Get_Current_Drop_ctx(), show, x, y);
+
+ /* If the window is being shown, move it to the right place. */
+ if (show) {
+ UpdateWorkArea();
+ x = MAX(x - DRAG_DET_WINDOW_WIDTH / 2, mOriginX);
+ y = MAX(y - DRAG_DET_WINDOW_WIDTH / 2, mOriginY);
+ mDetWnd->UpdateDetWnd(show, x, y);
+ } else {
+ g_debug("%s: hide\n", __FUNCTION__);
+ mDetWnd->UpdateDetWnd(show, x, y);
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnUpdateUnityDetWnd --
+ *
+ * Callback to show/hide fullscreen Unity drag detection window.
+ *
+ * TODO: Unity related functions are deprecated, remove it later once
+ * backend RPC functions are removed.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Detection window shown, hidden.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnUpdateUnityDetWnd(bool show, // IN: show (true) or hide (false)
+ uint32 unityWndId, // IN: XXX ?
+ bool bottom) // IN: place window at bottom of stack?
+{
+ NOT_IMPLEMENTED();
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnDestMoveDetWndToMousePos --
+ *
+ * Callback to move detection window to current moue position.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Detection window is moved, shown.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnDestMoveDetWndToMousePos()
+{
+ mDetWnd->SimulateXEvents(true, false, true, true, false, 0, 0);
+}
+
+
+/*
+ ****************************************************************************
+ * BEGIN GTK+ Callbacks (dndcp as drag source: host-to-guest)
+ */
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnGtkSrcDragBegin --
+ *
+ * GTK4 h->g drag begin handler
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * Signals to drag source that drag is started.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnGtkSrcDragBegin(const Glib::RefPtr< Gdk::Drag > &drag, void *ctx)
+{
+ DnDUIX11 *this_instance = (DnDUIX11 *)ctx;
+ g_debug("%s: DND drag begin with ctx %ld\n", __FUNCTION__,
+ this_instance->mDetWnd->Get_Current_Drag_ctx());
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnGtkSrcDragEnd --
+ *
+ * GTK4 h->g drag end handler
+ *
+ * Results:
+ * None
+ *
+ * Side effects:
+ * Signals to drag source that drag is started.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnGtkSrcDragEnd(const Glib::RefPtr< Gdk::Drag > &drag,
+ bool delete_data,
+ void *ctx)
+{
+ DnDUIX11 *this_instance = (DnDUIX11 *)ctx;
+ g_debug("%s: DND drag done with ctx %ld\n", __FUNCTION__,
+ this_instance->mDetWnd->Get_Current_Drag_ctx());
+ if (this_instance->mHGGetFileStatus != DND_FILE_TRANSFER_IN_PROGRESS) {
+ g_debug("%s: reset UI\n", __FUNCTION__);
+ this_instance->ResetUI();
+ }
+ this_instance->mInHGDrag = false;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnGtkSrcDragCancel --
+ *
+ * GTK+ DragSource "drag_leave" signal handler.
+ *
+ * Logs event. Acknowledges, finishes outdated sequence if drag context
+ * is not the same as we're currently interested in (i.e. != mDragCtx).
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May cancel dnd associated with this drag context.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool
+DnDUIX11::OnGtkSrcDragCancel(const Glib::RefPtr< Gdk::Drag > &drag,
+ Gdk::DragCancelReason reason, void *ctx)
+{
+ DnDUIX11 *this_instance = (DnDUIX11 *)ctx;
+ g_debug("%s: DND cancel for drag %ld with reason %d\n", __FUNCTION__,
+ this_instance->mDetWnd->Get_Current_Drag_ctx(), (int)reason);
+ this_instance->ResetUI();
+ return true;
+}
+
+
+/*
+ * END GTK+ Callbacks (dndcp as drag source: host-to-guest)
+ ****************************************************************************
+ */
+
+
+/*
+ ****************************************************************************
+ * BEGIN GTK+ Callbacks (dndcp as drag destination: guest-to-host)
+ */
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnGtkDragAccept --
+ *
+ * GTK4 "signal_accept" signal handler for DropTarget.
+ *
+ * Results:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool
+DnDUIX11::OnGtkDragAccept(const Glib::RefPtr< Gdk::Drop > &drop, void *ctx)
+{
+ DnDUIX11 *this_instance = (DnDUIX11 *)ctx;
+ if (!this_instance->mDnD->IsDnDAllowed()) {
+ g_warning("%s: No dnd allowed!\n", __FUNCTION__);
+ return false;
+ }
+
+ if (!this_instance->mDetWnd->Is_Drop_Supported(drop)) {
+ g_warning("%s: No Unsupported DND type\n", __FUNCTION__);
+ return false;
+ }
+
+ if (this_instance->mInHGDrag) {
+ g_warning("%s: In H->G dnd process rather than G->H, exit\n", __FUNCTION__);
+ return false;
+ }
+
+ if (this_instance->mGHDnDInProgress) {
+ /*
+ * Errors in the GTK layer can lead to dropped information without
+ * notification. Therefore, a cleanup process is necessary.
+ */
+ g_warning("%s: Previous drag failed\n", __FUNCTION__);
+ this_instance->ResetUI();
+ }
+
+ g_debug("%s: new drag, need to get data for host\n", __FUNCTION__);
+ /*
+ * This is a new drag operation. We need to start a drag thru the
+ * RPC, and to the host. Before we can tell the host, we have to
+ * retrieve the drop data.
+ */
+ this_instance->mGHDnDInProgress = true;
+ /* only begin drag enter after we get the data */
+ /* Need to grab all of the data. */
+ CPClipboard_Clear(&this_instance->mClipboard);
+ this_instance->mNumPendingRequest = 0;
+ return true;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnGtkDragEnter --
+ *
+ * GTK4 "signal_enter" signal handler for DropTarget.
+ *
+ * Results:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+Gdk::DragAction
+DnDUIX11::OnGtkDragEnter(double x, // IN: drag motion x-coord
+ double y, // IN: drag motion y-coord
+ void *ctx)
+{
+ DnDUIX11 *this_instance = (DnDUIX11 *)ctx;
+ Gdk::DragAction action;
+
+ if (this_instance->mDetWnd->Is_Current_Drop_Supported()) {
+ action = Gdk::DragAction::COPY;
+ } else {
+ g_warning("%s: No dnd allowed @ (%f, %f)!\n", __FUNCTION__, x, y);
+ }
+
+ return action;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnGtkDragDrop --
+ *
+ * GTK+ "drag_drop" signal handler.
+ *
+ * Results:
+ * Returns true so long as drag target and data are (at one point)
+ * provided (i.e. not a spurious event).
+ *
+ * Side effects:
+ * Signals to drag source that drop is finished.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool
+DnDUIX11::OnGtkDragDrop(const Glib::ValueBase& value,
+ double x,
+ double y,
+ void *ctx)
+{
+ DnDUIX11 *this_instance = (DnDUIX11 *)ctx;
+ g_debug("%s: Ctx %ld, drop at x %f y %f\n", __FUNCTION__,
+ this_instance->mDetWnd->Get_Current_Drop_ctx(), x, y);
+
+ this_instance->ResetUI();
+
+ if (CPClipboard_IsEmpty(&this_instance->mClipboard)) {
+ g_debug("%s: No valid data on mClipboard.\n", __FUNCTION__);
+ return false;
+ }
+
+ return true;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnGtkDragLeave --
+ *
+ * GTK+ "drag_leave" signal handler.
+ *
+ * Logs event. Acknowledges, finishes outdated sequence if drag context
+ * is not the same as we're currently interested in (i.e. != mDragCtx).
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May cancel dnd associated with this drag context.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+ void
+ DnDUIX11::OnGtkDragLeave(void *ctx)
+ {
+ DnDUIX11 *this_instance = (DnDUIX11 *)ctx;
+
+ g_debug("%s: DND leave for drop %ld\n", __FUNCTION__,
+ this_instance->mDetWnd->Get_Current_Drop_ctx());
+
+ if (this_instance->mGHDnDInProgress) {
+ g_debug("%s: reset UI\n", __FUNCTION__);
+ this_instance->ResetUI();
+ }
+ }
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnGtkDragMotion --
+ *
+ * GTK "drag_motion" signal handler.
+ *
+ * We should respond by setting drag status. Note that there is no drag
+ * enter signal. We need to figure out if a new drag is happening on
+ * our own. Also, we don't respond with a "allowed" drag status right
+ * away, we start a new drag operation with the host (which tries to
+ * notify the host of the new operation). Once the host has responded),
+ * we respond with a proper drag status.
+ *
+ * Note: You may see this callback during DnD when detection window
+ * is acting as a source. In that case it will be ignored. In a future
+ * refactoring, we will try and avoid this.
+ *
+ * Results:
+ * Returns true unless we don't recognize the types offered.
+ *
+ * Side effects:
+ * Via RequestData issues a Gtk::Widget::drag_get_data.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+Gdk::DragAction
+DnDUIX11::OnGtkDragMotion(double x, // IN: drag motion x-coord
+ double y, // IN: drag motion y-coord
+ void *ctx)
+{
+ DnDUIX11 *this_instance = (DnDUIX11 *)ctx;
+ Gdk::DragAction action = Gdk::DragAction::COPY;
+ /*
+ * If this is a Host to Guest drag, we are done here, so return.
+ */
+ unsigned long curTime = GetTimeInMillis();
+ g_debug("%s: enter dnd ctx %ld\n", __FUNCTION__,
+ this_instance->mDetWnd->Get_Current_Drop_ctx());
+ if (curTime - this_instance->mDestDropTime <= 1000) {
+ g_debug("%s: ignored %ld %ld %ld\n", __FUNCTION__,
+ curTime, this_instance->mDestDropTime,
+ curTime - this_instance->mDestDropTime);
+ goto out;
+ }
+
+ g_debug("%s: not ignored %ld %ld %ld\n", __FUNCTION__,
+ curTime, this_instance->mDestDropTime,
+ curTime - this_instance->mDestDropTime);
+
+ if (this_instance->mInHGDrag ||
+ (this_instance->mHGGetFileStatus != DND_FILE_TRANSFER_NOT_STARTED)) {
+ g_debug("%s: ignored not in hg drag or not getting hg data\n",
+ __FUNCTION__);
+ goto out;
+ }
+ if (!this_instance->mGHDnDInProgress) {
+ g_debug("%s: new drag, need to get data for host\n", __FUNCTION__);
+ /*
+ * This is a new drag operation. We need to start a drag thru the
+ * backdoor, and to the host. Before we can tell the host, we have to
+ * retrieve the drop data.
+ */
+ this_instance->mGHDnDInProgress = true;
+ /* only begin drag enter after we get the data */
+ /* Need to grab all of the data. */
+ CPClipboard_Clear(&this_instance->mClipboard);
+ this_instance->mNumPendingRequest = 0;
+ } else {
+ g_debug("%s: Multiple drag motions before gh data has been received.\n",
+ __FUNCTION__);
+ }
+out:
+ return action;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::OnGtkDragDataReceived --
+ *
+ * GTK+ "drag_data_received" signal handler.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May signal to host beginning of guest-to-host DND.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::OnGtkDragDataReceived(void *ctx)
+{
+ DnDUIX11 *this_instance = (DnDUIX11 *)ctx;
+ std::string value;
+ std::string value_type;
+
+ g_debug("%s: enter dnd ctx %ld\n", __FUNCTION__,
+ this_instance->mDetWnd->Get_Current_Drop_ctx());
+ /* The GH DnD may already finish before we got response. */
+ if (!this_instance->mGHDnDInProgress) {
+ g_debug("%s: not valid\n", __FUNCTION__);
+ return;
+ }
+
+ if (this_instance->mDetWnd->Is_DropValue_Ready()) {
+ if (this_instance->mDetWnd->Get_Drop_Value(value, value_type)) {
+ if (this_instance->SetCPClipboardFromGtk(value, value_type)) {
+ this_instance->mNumPendingRequest--;
+ if (this_instance->mNumPendingRequest > 0) {
+ return;
+ }
+ if (CPClipboard_IsEmpty(&this_instance->mClipboard)) {
+ g_debug("%s: Failed getting item.\n", __FUNCTION__);
+ this_instance->ResetUI();
+ return;
+ }
+ if (!this_instance->mGHDnDDataReceived) {
+ g_debug("%s: Drag entering.\n", __FUNCTION__);
+ this_instance->mGHDnDDataReceived = true;
+ this_instance->TargetDragEnter();
+ }
+ } else {
+ g_debug("%s: Failed to set CP clipboard.\n", __FUNCTION__);
+ this_instance->ResetUI();
+ }
+ } else {
+ g_debug("%s: DND data is not ready, Could be a drag leave.\n",
+ __FUNCTION__);
+ }
+ }
+}
+
+
+/*
+ * END GTK+ Callbacks (dndcp as drag destination: guest-to-host)
+ ****************************************************************************
+ */
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::SetCPClipboardFromGtk_File --
+ *
+ * Extract file list from GTK4.
+ * And construct cross-platform clipboard.
+ *
+ * Results:
+ * Returns true if conversion succeeded, false otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool DnDUIX11::SetCPClipboardFromGtk_File(utf::string &source)
+{
+ DnDFileList fileList;
+ DynBuf buf;
+ size_t index = 0;
+ char *newPath;
+ size_t newPathLen;
+ uint64 totalSize;
+ int64 size;
+
+ g_debug("%s: Got file list: [%s]\n", __FUNCTION__, source.c_str());
+
+ if (source.length() == 0) {
+ g_debug("%s: empty file list!\n", __FUNCTION__);
+ return false;
+ }
+
+ /*
+ * In gnome, before file list there may be a extra line indicating it
+ * is a copy or cut.
+ */
+ if (source.length() >= 5 && source.compare(0, 5, "copy\n") == 0) {
+ source = source.erase(0, 5);
+ }
+
+ if (source.length() >= 4 && source.compare(0, 4, "cut\n") == 0) {
+ source = source.erase(0, 4);
+ }
+
+ while (source.length() > 0 &&
+ (source[0] == '\n' || source[0] == '\r' || source[0] == ' ')) {
+ source = source.erase(0, 1);
+ }
+
+ while ((newPath = DnD_UriListGetNextFile(source.c_str(),
+ &index,
+ &newPathLen)) != NULL) {
+ char *newRelPath;
+
+ /*TODO: refactor compiler option segments and merge them together*/
+#if defined(__linux__)
+ if (DnD_UriIsNonFileSchemes(newPath)) {
+ /* Try to get local file path for non file uri. */
+ GFile *file = g_file_new_for_uri(newPath);
+ free(newPath);
+ if (!file) {
+ g_debug("%s: g_file_new_for_uri failed\n", __FUNCTION__);
+ return false;
+ }
+ newPath = g_file_get_path(file);
+ g_object_unref(file);
+ if (!newPath) {
+ g_debug("%s: g_file_get_path failed\n", __FUNCTION__);
+ return false;
+ }
+ }
+#endif
+ /*
+ * Parse relative path.
+ */
+ newRelPath = Str_Strrchr(newPath, DIRSEPC) + 1; // Point to char after '/'
+
+ /* Keep track of how big the dnd files are. */
+ if ((size = File_GetSizeEx(newPath)) >= 0) {
+ totalSize += size;
+ } else {
+ g_debug("%s: unable to get file size for %s\n", __FUNCTION__, newPath);
+ }
+ g_debug("%s: Adding newPath '%s' newRelPath '%s'\n", __FUNCTION__,
+ newPath, newRelPath);
+ fileList.AddFile(newPath, newRelPath);
+#if defined(__linux__)
+ char *newUri = HgfsUri_ConvertFromPathToHgfsUri(newPath, false);
+ fileList.AddFileUri(newUri);
+ free(newUri);
+#endif
+ free(newPath);
+ }
+
+ DynBuf_Init(&buf);
+ fileList.SetFileSize(totalSize);
+ if (fileList.ToCPClipboard(&buf, false)) {
+ CPClipboard_SetItem(&mClipboard, CPFORMAT_FILELIST, DynBuf_Get(&buf),
+ DynBuf_GetSize(&buf));
+ }
+ DynBuf_Destroy(&buf);
+#if defined(__linux__)
+ if (fileList.ToUriClipboard(&buf)) {
+ CPClipboard_SetItem(&mClipboard, CPFORMAT_FILELIST_URI, DynBuf_Get(&buf),
+ DynBuf_GetSize(&buf));
+ }
+ DynBuf_Destroy(&buf);
+#endif
+ return true;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::SetCPClipboardFromGtk_PlainText --
+ *
+ * Extract text from GTK4. And construct cross-platform clipboard.
+ *
+ * Results:
+ * Returns true if conversion succeeded, false otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool DnDUIX11::SetCPClipboardFromGtk_PlainText(utf::string &source)
+{
+ if (source.size() > 0 &&
+ source.size() < DNDMSG_MAX_ARGSZ &&
+ CPClipboard_SetItem(&mClipboard, CPFORMAT_TEXT, source.c_str(), source.size() + 1)) {
+ g_debug("%s: Got text, size %" FMTSZ "u\n", __FUNCTION__, source.size());
+ } else {
+ g_warning("%s: Failed to get text\n", __FUNCTION__);
+ return false;
+ }
+ return true;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::SetCPClipboardFromGtk_RTF --
+ *
+ * Extract RTF from GTK4. And construct cross-platform clipboard.
+ *
+ * Results:
+ * Returns true if conversion succeeded, false otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool DnDUIX11::SetCPClipboardFromGtk_RTF(utf::string &source)
+{
+ if (source.size() > 0 &&
+ source.size() < DNDMSG_MAX_ARGSZ &&
+ CPClipboard_SetItem(&mClipboard, CPFORMAT_RTF, source.c_str(), source.size() + 1)) {
+ g_debug("%s: Got RTF, size %" FMTSZ "u\n", __FUNCTION__, source.size());
+ return true;
+ } else {
+ g_warning("%s: Failed to get RTF\n", __FUNCTION__);
+ }
+
+ return false;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::SetCPClipboardFromGtk --
+ *
+ * Construct cross-platform clipboard from GTK+ selection_data.
+ *
+ * Results:
+ * Returns true if conversion succeeded, false otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool
+DnDUIX11::SetCPClipboardFromGtk(std::string &data, std::string &drop_type) // IN
+{
+ DnDFileList fileList;
+ DynBuf buf;
+ uint64 totalSize = 0;
+ int64 size;
+ bool ret = false;
+
+ utf::string source = utf::string(data);
+ utf::string type = utf::string(drop_type);
+
+ /* Try to get file list. */
+ if (mDnD->CheckCapability(DND_CP_CAP_FILE_DND) && type == DRAG_TARGET_NAME_URI_LIST) {
+ ret = SetCPClipboardFromGtk_File(source);
+ }
+ /* Try to get plain text. */
+ if (mDnD->CheckCapability(DND_CP_CAP_PLAIN_TEXT_DND) && TargetIsPlainText(type)) {
+ ret = SetCPClipboardFromGtk_PlainText(source);
+ }
+ /* Try to get RTF string. */
+ if (mDnD->CheckCapability(DND_CP_CAP_RTF_DND) && TargetIsRichText(type)) {
+ ret = SetCPClipboardFromGtk_RTF(source);
+ }
+
+ return ret;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::GetLastDirName --
+ *
+ * Try to get last directory name from a full path name.
+ *
+ * What this really means is to get the basename of the parent's directory
+ * name, intended to isolate an individual DND operation's staging directory
+ * name.
+ *
+ * E.g. /tmp/VMwareDnD/abcd137 → abcd137
+ *
+ * Results:
+ * Returns session directory name on success, empty string otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+utf::string
+DnDUIX11::GetLastDirName(const utf::string &str)
+{
+ char *baseName;
+ utf::string stripSlash;
+ char *path = File_StripSlashes(str.c_str());
+ if (path) {
+ stripSlash = utf::CopyAndFree(path);
+ }
+
+ File_GetPathName(stripSlash.c_str(), NULL, &baseName);
+ if (baseName) {
+ return utf::CopyAndFree(baseName);
+ } else {
+ return utf::string();
+ }
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::GetNextPath --
+ *
+ * Convoluted somethingerother.
+ *
+ * XXX Something here involves URI parsing and encoding. Get to the bottom
+ * of this and use shared URI code.
+ *
+ * Original description:
+ * Provide a substring containing the next path from the provided
+ * NUL-delimited string starting at the provided index.
+ *
+ * Results:
+ * Returns "a string with the next path or empty string if there are no
+ * more paths".
+ *
+ * Side effects:
+ * Updates index.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+utf::utf8string
+DnDUIX11::GetNextPath(utf::utf8string& str, // IN: NUL-delimited path list
+ size_t& index) // IN/OUT: index into string
+{
+ utf::utf8string ret;
+ size_t start;
+
+ if (index >= str.length()) {
+ return "";
+ }
+
+ for (start = index; str[index] != '\0' && index < str.length(); index++) {
+ /*
+ * Escape reserved characters according to RFC 1630. We'd use
+ * Escape_Do() if this wasn't a utf::string, but let's use the same table
+ * replacement approach.
+ */
+ static char const Dec2Hex[] = {
+ '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+ };
+
+ unsigned char ubyte = str[index];
+
+ if (ubyte == '#' || /* Fragment identifier delimiter */
+ ubyte == '?' || /* Query string delimiter */
+ ubyte == '*' || /* "Special significance within specific schemes" */
+ ubyte == '!' || /* "Special significance within specific schemes" */
+ ubyte == '%' || /* Escape character */
+ ubyte >= 0x80) { /* UTF-8 encoding bytes */
+ str.replace(index, 1, "%");
+ str.insert(index + 1, 1, Dec2Hex[ubyte >> 4]);
+ str.insert(index + 2, 1, Dec2Hex[ubyte & 0xF]);
+ index += 2;
+ }
+ }
+
+ ret = str.substr(start, index - start);
+ g_debug("%s: nextpath: %s", __FUNCTION__, ret.c_str());
+ index++;
+ return ret;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::TargetDragEnter --
+ *
+ * With the source's drag selection data on the clipboard, signal to
+ * host to begin guest-to-host drag-and-drop.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::TargetDragEnter()
+{
+ TRACE_CALL();
+
+ /* Check if there is valid data with current detection window. */
+ if (!CPClipboard_IsEmpty(&mClipboard)) {
+ g_debug("%s: got valid data from detWnd.\n", __FUNCTION__);
+ mDnD->DestUIDragEnter(&mClipboard);
+ }
+
+ /*
+ * Show the window, and position it under the current mouse position.
+ * This is particularly important for KDE 3.5 guests.
+ */
+ mDetWnd->SimulateXEvents(true, false, true, true, false, 0, 0);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::SourceDragStartDone --
+ *
+ * Tell host that we're done with host-to-guest drag-and-drop
+ * initialization.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::SourceDragStartDone()
+{
+ TRACE_CALL();
+ mInHGDrag = true;
+ mDnD->SrcUIDragBeginDone();
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::SetBlockControl --
+ *
+ * Set block control member.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::SetBlockControl(DnDBlockControl *blockCtrl) // IN
+{
+ mBlockCtrl = blockCtrl;
+}
+
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::SourceUpdateFeedback --
+ *
+ * Forward feedback from our drop source to the host.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::SourceUpdateFeedback(DND_DROPEFFECT effect) // IN: feedback to send to the UI-independent DnD layer.
+{
+ TRACE_CALL();
+ mDnD->SrcUIUpdateFeedback(effect);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::GetTimeInMillis --
+ *
+ * Get Unix time in milliseconds.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+unsigned long
+DnDUIX11::GetTimeInMillis()
+{
+ VmTimeType atime;
+
+ Hostinfo_GetTimeOfDay(&atime);
+ return((unsigned long)(atime / 1000));
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::VmXDnDVersionChanged --
+ *
+ * Update version information in mDnD.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::VmxDnDVersionChanged(RpcChannel *chan, // IN
+ uint32 version) // IN
+{
+ ASSERT(mDnD);
+ mDnD->VmxDnDVersionChanged(version);
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DnDUIX11::UpdateWorkArea --
+ *
+ * Updates mOrigin in response to changes to _NET_workArea.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+void
+DnDUIX11::UpdateWorkArea()
+{
+ TRACE_CALL();
+
+ std::vector<unsigned long> values;
+ if (xutils::GetCardinalList(mGdkSurface, "_NET_WORKAREA", values)
+ && values.size() > 0
+ && values.size() % 4 == 0) {
+ /*
+ * wm-spec: _NET_WORKAREA, x, y, width, height CARDINAL[][4]/32
+ *
+ * For the purposes of drag-and-drop, we're okay with using the screen-
+ * agnostic _NET_WORKAREA atom, as the guest VM really deals with only
+ * one logical monitor.
+ */
+ unsigned long desktop = 0;
+ xutils::GetCardinal(mGdkSurface, "_NET_CURRENT_DESKTOP", desktop);
+
+ mOriginX = values[0 + 4 * desktop];
+ mOriginY = values[1 + 4 * desktop];
+ } else {
+ mOriginX = 0;
+ mOriginY = 0;
+ }
+
+ g_debug("%s: new origin at (%d, %d)\n", __FUNCTION__, mOriginX, mOriginY);
+}
--- /dev/null
+/*********************************************************
+ * Copyright (c) 2025 Broadcom. All Rights Reserved.
+ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *********************************************************/
+
+/**
+ * @file dragDetWndX11GTK4.cpp
+ *
+ * Detection window code for Linux/X11, based on Gtkmm-4.0.
+ */
+
+#define G_LOG_DOMAIN "dndcp"
+
+#include "tracer.hh"
+#include "dragDetWndX11GTK4.h"
+#include "dndUIX11GTK4.h"
+#include <list>
+
+extern "C" {
+#include <X11/extensions/XTest.h> /* for XTest*() */
+#include <gtk/gtk.h>
+#include <gdk/x11/gdkx.h>
+#include <X11/Xatom.h>
+}
+
+
+/**
+ *
+ * Constructor.
+ */
+
+DragDetWnd::DragDetWnd(int UseUInput_fd)
+ : m_isVisible(false),
+ mWnd(NULL),
+ mSource(NULL),
+ mTarget(NULL),
+ mUseUInput(false),
+ mScreenWidth(0),
+ mScreenHeight(0)
+{
+ std::vector<GType> types;
+ mWnd = new Gtk::Window();
+ if (!mWnd) {
+ g_error("%s: unable to allocate DragDetWnd object\n", __FUNCTION__);
+ return;
+ }
+
+ // Makes this window can grab focus.
+ mWnd->set_decorated(false);
+ mWnd->set_resizable(false);
+ mWnd->set_modal(false);
+ mWnd->set_can_focus(true);
+ mWnd->set_focusable(true);
+ mWnd->set_sensitive(false);
+ // Makes this window transparent because we don't want user to see it.
+ mWnd->set_opacity(0.01);
+ mWnd->set_visible(true);
+ mWnd->set_visible(false);
+
+ //dnd Source controller
+ mSource = Gtk::DragSource::create();
+ if (!mSource) {
+ g_error("%s: unable to allocate source for DragDetWnd object\n",
+ __FUNCTION__);
+ goto fail;
+ }
+ mSource->set_actions(Gdk::DragAction::COPY | Gdk::DragAction::MOVE);
+
+ //dnd target controller
+ mTarget = Gtk::DropTarget::create(G_TYPE_INVALID, Gdk::DragAction::COPY | Gdk::DragAction::MOVE);
+ if (!mTarget) {
+ g_error("%s: unable to allocate target for DragDetWnd object\n",
+ __FUNCTION__);
+ goto fail;
+ }
+
+ // Put GDK_TYPE_FILE_LIST at first, and that will make GTK report file list when possible.
+ types.push_back(GDK_TYPE_FILE_LIST);
+ types.push_back(G_TYPE_FILE);
+ types.push_back(G_TYPE_STRING);
+ mTarget->set_gtypes(types);
+ // Let GTK preload data early.
+ mTarget->set_preload(true);
+
+ // Uinput simulation for wayland backend.
+ if (UseUInput_fd != -1) {
+ g_debug("%s: Using uinput simulation", __FUNCTION__);
+ Display *display = XOpenDisplay(NULL);
+ Screen * scrn = DefaultScreenOfDisplay(display);
+ if (FakeMouse_Init(UseUInput_fd, scrn->width, scrn->height)) {
+ mUseUInput = true;
+ mScreenWidth = scrn->width;
+ mScreenHeight = scrn->height;
+ g_debug("%s: mScreenWidth is %d, mScreenHeight is %d", __FUNCTION__, mScreenWidth, mScreenHeight);
+ }
+ // Disconnect the connection to X Server.
+ XCloseDisplay(display);
+ }
+ return;
+fail:
+ if (mWnd) {
+ delete mWnd;
+ mWnd = NULL;
+ }
+}
+
+
+/**
+ *
+ * Destructor.
+ */
+
+DragDetWnd::~DragDetWnd()
+{
+ if (mUseUInput) {
+ FakeMouse_Destory();
+ }
+ if (mWnd) {
+ delete mWnd;
+ mWnd = NULL;
+ }
+}
+
+
+/**
+ * Get the actual widget.
+ */
+
+Gtk::Widget *
+DragDetWnd::GetWnd()
+{
+ return static_cast<Gtk::Widget*>(mWnd);
+}
+
+/**
+ * Register GTK callback which was triggered when a drag begin happens.
+ */
+
+bool
+DragDetWnd::register_source_drag_begin_handler(source_drag_begin_handler handler, void *ctx)
+{
+ mSource->signal_drag_begin().connect(sigc::bind(handler, ctx), false);
+ return true;
+}
+
+
+/**
+ * Register GTK callback which was triggered when a drag end happens.
+ */
+
+bool
+DragDetWnd::register_source_drag_end_handler(source_drag_end_handler handler, void *ctx)
+{
+ mSource->signal_drag_end().connect(sigc::bind(handler, ctx), false);
+ return true;
+}
+
+
+/**
+ * Register GTK callback when a drag has failed
+ */
+
+bool
+DragDetWnd::register_source_drag_cancel_handler(source_drag_cancel_handler handler, void *ctx)
+{
+ mSource->signal_drag_cancel().connect(sigc::bind(handler, ctx), false);
+ return true;
+}
+
+
+/**
+ * Register GTK callback when a drag is about to be initiated.
+ */
+
+bool
+DragDetWnd::register_source_drag_prepare_handler(source_drag_prepare_handler handler, void *ctx)
+{
+ mSource->signal_prepare().connect(sigc::bind(handler, ctx), false);
+ return true;
+}
+
+
+/**
+ * Register GTK callback when a drop operation is about to begin.
+ */
+
+bool
+DragDetWnd::register_target_drag_accept_handler(target_drag_accept_handler handler, void *ctx)
+{
+ mTarget->signal_accept().connect(sigc::bind(handler, ctx), false);
+ return true;
+}
+
+
+/**
+ * Register GTK callback when the pointer enters the widget.
+ */
+
+bool
+DragDetWnd::register_target_drag_enter_handler(target_drag_enter_handler handler, void *ctx)
+{
+ mTarget->signal_enter().connect(sigc::bind(handler, ctx), false);
+ return true;
+}
+
+
+/**
+ * Register GTK callback when the user drops the data onto the widget.
+ */
+
+bool
+DragDetWnd::register_target_drag_drop_handler(target_drag_drop_handler handler, void *ctx)
+{
+ mTarget->signal_drop().connect(sigc::bind(handler, ctx), false);
+ return true;
+}
+
+
+/**
+ * Register GTK callback when the pointer leaves the widget.
+ */
+
+bool
+DragDetWnd::register_target_drag_leave_handler(target_drag_leave_handler handler, void *ctx)
+{
+ mTarget->signal_leave().connect(sigc::bind(handler, ctx), false);
+ return true;
+}
+
+
+/**
+ * Register GTK callback when the drop value changed. when set droptarget controller preload,
+ * the value may be ready befor drop action happen. When drop leave happens, the value will be
+ * set to NULL.
+ */
+
+bool
+DragDetWnd::register_target_value_handler(target_drag_value_handler handler, void *ctx)
+{
+ mTarget->property_value().signal_changed().connect(sigc::bind(sigc::ptr_fun(handler), ctx));
+ return true;
+}
+
+
+/**
+ * Register GTK callback while the pointer is moving over the drop target.
+ */
+
+bool
+DragDetWnd::register_target_drag_motion_handler(target_drag_motion_handler handler, void *ctx)
+{
+ mTarget->signal_motion().connect(sigc::bind(handler, ctx), false);
+ return true;
+}
+
+
+/**
+ * Enable drag source and target.
+ */
+
+bool
+DragDetWnd::EnableDND(void)
+{
+ g_debug("%s\n", __FUNCTION__);
+ mWnd->add_controller(mSource);
+ mWnd->add_controller(mTarget);
+ return true;
+}
+
+
+/**
+ * Get the current drag contax from source.
+ */
+
+unsigned long
+DragDetWnd::Get_Current_Drag_ctx(void)
+{
+ Glib::RefPtr< Gdk::Drag > current_drag = mSource->get_drag();
+ if (current_drag != NULL)
+ return (unsigned long) current_drag->gobj();
+ else
+ return 0;
+}
+
+
+/**
+ * Disable drag source and target.
+ */
+
+bool
+DragDetWnd::DisableDND(void)
+{
+ g_debug("%s\n", __FUNCTION__);
+ mWnd->remove_controller(mSource);
+ mWnd->remove_controller(mTarget);
+ return true;
+}
+
+
+/**
+ * Query if drop value is ready.
+ */
+
+bool
+DragDetWnd::Is_DropValue_Ready(void)
+{
+ Glib::ValueBase value= mTarget->get_value();
+ return G_IS_VALUE(value.gobj());
+}
+
+
+/**
+ * Query if current drop type is supported.
+ */
+
+bool
+DragDetWnd::Is_Current_Drop_Supported(void)
+{
+ Glib::RefPtr< Gdk::Drop > current_drop = mTarget->get_drop();
+ return this->Is_Drop_Supported(current_drop);
+}
+
+
+/**
+ * Query if a drop type is supported.
+ */
+
+bool
+DragDetWnd::Is_Drop_Supported(const Glib::RefPtr< Gdk::Drop > &drop)
+{
+ Glib::RefPtr<Gdk::ContentFormats> supported_format = drop->get_formats();
+ g_debug("%s content type is %s", __FUNCTION__, supported_format->to_string().c_str());
+
+ return (supported_format->contain_gtype(GDK_TYPE_FILE_LIST) ||
+ supported_format->contain_gtype(G_TYPE_STRING));
+}
+
+
+/**
+ * Query the current drop context from target.
+ */
+
+unsigned long
+DragDetWnd::Get_Current_Drop_ctx(void)
+{
+ Glib::RefPtr< Gdk::Drop > current_drop = mTarget->get_drop();
+ if (current_drop != NULL)
+ return (unsigned long) mTarget->get_drop()->gobj();
+ else
+ return 0;
+}
+
+
+/**
+ * Get drop value from detect window.
+ */
+
+bool
+DragDetWnd::Get_Drop_Value(std::string &value, std::string &value_type)
+{
+ Glib::ValueBase cur_value= mTarget->get_value();
+ bool ret = false;
+
+ if (G_IS_VALUE(cur_value.gobj())) {
+ if (G_VALUE_HOLDS(cur_value.gobj(), GDK_TYPE_FILE_LIST)) {
+ GSList *file_list = gdk_file_list_get_files(static_cast<GdkFileList *>(g_value_get_boxed(cur_value.gobj())));
+ g_debug("%s Get file list ...", __FUNCTION__);
+ for (GSList *l = file_list; l != nullptr; l = l->next) {
+ GFile *file = static_cast<GFile *>(l->data);
+ gchar *uri = g_file_get_uri(file);
+ if (uri) {
+ g_debug("%s Get file uri: %s", __FUNCTION__, uri);
+ value += uri;
+ value += '\n';
+ }
+ }
+ if (value.length()) {
+ ret = true;
+ value_type = DRAG_TARGET_NAME_URI_LIST;
+ }
+ } else if (G_VALUE_HOLDS(cur_value.gobj(), G_TYPE_FILE)) {
+ GFile* gfile = G_FILE(g_value_get_object(cur_value.gobj()));
+ g_debug("%s Get file ...", __FUNCTION__);
+ if (gfile) {
+ gchar *uri = g_file_get_uri(gfile);
+ if (uri) {
+ g_debug("%s Get file uri: %s", __FUNCTION__, uri);
+ value += uri;
+ }
+ }
+ } else if (G_VALUE_HOLDS(cur_value.gobj(), G_TYPE_STRING)) {
+ const gchar* text= g_value_get_string(cur_value.gobj());
+ Glib::RefPtr<Gdk::ContentFormats> supported_format = mTarget->get_drop()->get_formats();
+
+ value += text;
+ g_debug("%s Get str: %s", __FUNCTION__, text);
+ if (supported_format->to_string().find(TARGET_NAME_TEXT_RTF) != Glib::ustring::npos) {
+ value_type = TARGET_NAME_TEXT_RTF;
+ } else {
+ value_type = TARGET_NAME_STRING;
+ }
+ g_debug("%s Set type as: %s", __FUNCTION__, value_type.c_str());
+ ret = true;
+
+ } else {
+ g_warning("%s Unsupported DND Value type %s",
+ __FUNCTION__, g_type_name(G_TYPE_FROM_INSTANCE(cur_value.gobj())));
+ }
+ } else
+ g_debug("%s No value available %ld", __FUNCTION__, (unsigned long) cur_value.gobj());
+
+ return ret;
+}
+
+
+/**
+ * Flush the X connection.
+ */
+
+void
+DragDetWnd::Flush()
+{
+ Glib::RefPtr<Gdk::Display> gdkdisplay = Gdk::Display::get_default();
+ g_debug("%s\n", __FUNCTION__);
+ if (gdkdisplay) {
+ gdkdisplay->sync();
+ gdkdisplay->flush();
+ }
+}
+
+
+/**
+ * Show the window.
+ */
+
+void
+DragDetWnd::Show(void)
+{
+ Glib::RefPtr<Gdk::Display> display = Gdk::Display::get_default();
+ int width = 0;
+ int height = 0;
+ g_debug("%s: enter\n", __FUNCTION__);
+ if (display) {
+ Glib::RefPtr<Gio::ListModel> monitors = display->get_monitors();
+ if (!monitors || monitors->get_n_items() == 0)
+ return;
+
+ GListModel* c_model = monitors->gobj();
+ for (guint i = 0; i < monitors->get_n_items(); ++i) {
+ GObject* c_item = G_OBJECT(g_list_model_get_item(c_model, i));
+ Glib::RefPtr<Gdk::Monitor> monitor = Glib::wrap(GDK_MONITOR(c_item), true);
+ Gdk::Rectangle geometry = monitor->property_geometry();
+ mWnd->set_default_size(geometry.get_width(), geometry.get_height());
+ break;
+ }
+ } else {
+ g_error("%s: Cannot get default display \n", __FUNCTION__);
+ return;
+ }
+
+ mWnd->set_sensitive(true);
+ mWnd->set_visible(true);
+
+ if (mWnd->grab_focus()) {
+ g_debug("%s: grab_focus successfully\n", __FUNCTION__);
+ } else {
+ Warning("%s: grab_focus failed! May unable to detect drag actions\n", __FUNCTION__);
+ }
+
+ m_isVisible = true;
+ // Add a delay to let dnd warming up
+ usleep(300);
+
+ if (mWnd->get_focus())
+ {
+ g_debug("%s: detwnd have focus on %s\n", __FUNCTION__, G_OBJECT_TYPE_NAME(mWnd->get_focus()->gobj()));
+ }
+ else
+ g_debug("%s: detwnd doesn't have focus\n", __FUNCTION__);
+
+ g_debug("%s: exiting \n", __FUNCTION__);
+}
+
+
+/**
+ * Hide the window.
+ */
+
+void
+DragDetWnd::Hide(void)
+{
+ g_debug("%s: enter\n", __FUNCTION__);
+ g_debug("%s: detwnd current visible is %d\n", __FUNCTION__, mWnd->is_visible());
+ g_debug("%s: detwnd current focus is %d", __FUNCTION__, gtk_widget_has_focus(GTK_WIDGET(mWnd->gobj())));
+
+ mWnd->set_sensitive(false);
+ mWnd->set_visible(false);
+ m_isVisible = false;
+ g_debug("%s: exiting \n", __FUNCTION__);
+}
+
+
+/**
+ * Lower the the GTK window by set lower its surface
+ */
+
+static bool lower_wnd(Gtk::Window *window)
+{
+ GtkWidget* widget = GTK_WIDGET(window->gobj());
+ GtkNative* native = gtk_widget_get_native(widget);
+
+ if (native) {
+ GdkSurface* surface = gtk_native_get_surface(native);
+ if (surface && GDK_IS_TOPLEVEL(surface)) {
+ gdk_toplevel_lower(GDK_TOPLEVEL(surface));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+/**
+ * Get the monitor of the GTK window
+ */
+
+static Glib::RefPtr<Gdk::Monitor> get_monitor(Gtk::Window *window)
+{
+ GtkWidget* widget = GTK_WIDGET(window->gobj());
+ GtkNative* native = gtk_widget_get_native(widget);
+
+ if (native) {
+ GdkSurface* surface = gtk_native_get_surface(native);
+ g_object_ref(surface);
+ Glib::RefPtr<Gdk::Surface> share_surface(Glib::wrap(surface));
+ if (surface)
+ return Gdk::Display::get_default()->get_monitor_at_surface(share_surface);
+ }
+ return NULL;
+}
+
+
+/**
+ * Raise the window.
+ */
+
+void
+DragDetWnd::Raise(void)
+{
+ mWnd->present();
+ g_debug("%s: detwnd Presenting\n", __FUNCTION__);
+ Flush();
+}
+
+
+/**
+ * Lower the window.
+ */
+
+void
+DragDetWnd::Lower(void)
+{
+ lower_wnd(mWnd);
+ Flush();
+}
+
+
+/**
+ *
+ * Get the width of the screen associated with this window.
+ *
+ * @return width of screen, in pixels.
+ */
+
+int
+DragDetWnd::GetScreenWidth(void)
+{
+ Gdk::Rectangle geometry;
+ Glib::RefPtr<Gdk::Monitor> monitor = get_monitor(mWnd);
+ monitor->get_geometry(geometry);
+ return geometry.get_width();
+}
+
+
+/**
+ *
+ * Get the height of the screen associated with this window.
+ *
+ * @return height of screen, in pixels.
+ */
+
+int
+DragDetWnd::GetScreenHeight(void)
+{
+ Gdk::Rectangle geometry;
+ Glib::RefPtr<Gdk::Monitor> monitor = get_monitor(mWnd);
+ monitor->get_geometry(geometry);
+ return geometry.get_height();
+}
+
+
+/**
+ *
+ * Set the geometry of the window.
+ *
+ * @param[in] x desired x-coordinate of the window.
+ * @param[in] y desired y-coordinate of the window.
+ * @param[in] width desired width of the window.
+ * @param[in] height desired height of the window.
+ */
+
+void
+DragDetWnd::SetGeometry(const int x,
+ const int y,
+ const int width,
+ const int height)
+{
+ GtkWidget* widget = GetWnd()->gobj();
+ GtkNative* native = gtk_widget_get_native(widget);
+ Gtk::Window* gdkwin = dynamic_cast<Gtk::Window*>(GetWnd());
+ GdkSurface* surface = gtk_native_get_surface(native);
+ g_debug("%s: set Geo at (%d, %d, %d, %d)\n", __FUNCTION__, x, y, width, height);
+
+ if (gdkwin && surface) {
+ gdkwin->set_default_size(width, height);
+ if (GDK_IS_X11_SURFACE(surface)) {
+ Display* display = gdk_x11_display_get_xdisplay(gdk_surface_get_display(surface));
+ Window xwindow = gdk_x11_surface_get_xid(surface);
+ XMoveWindow(display, xwindow, x, y);
+ XFlush(display);
+ }
+ Flush();
+ }
+}
+
+
+/**
+ *
+ * Get the current geometry of the window.
+ *
+ * @param[out] x current x-coordinate of the window.
+ * @param[out] y current y-coordinate of the window.
+ * @param[out] width current width of the window.
+ * @param[out] height current height of the window.
+ *
+ * @note The current geometry may be inaccurate if retrieved too quickly
+ * after a change made by SetGeometry(). This is due to the realities of
+ * X and window managers. Some of this is mitigated by the use of flush()
+ * and sync() calls in SetGeometry(), but these are no guarantee.
+ */
+
+void
+DragDetWnd::GetGeometry(int &x, int &y, int &width, int &height)
+{
+ GtkWidget* widget = GetWnd()->gobj();
+ GtkNative* native = gtk_widget_get_native(widget);
+ GdkSurface* surface = gtk_native_get_surface(native);
+
+ width = gdk_surface_get_width(surface);
+ height = gdk_surface_get_height(surface);
+
+ if (GDK_IS_X11_SURFACE(surface)) {
+ Display* display = gdk_x11_display_get_xdisplay(gdk_surface_get_display(surface));
+ Window xwindow = gdk_x11_surface_get_xid(GDK_X11_SURFACE(surface));
+
+ Window root, child;
+ int dx, dy;
+ unsigned int w, h, bw, depth;
+
+ if (XGetGeometry(display, xwindow, &root, &dx, &dy, &w, &h, &bw, &depth)) {
+ XTranslateCoordinates(display, xwindow, root, 0, 0, &x, &y, &child);
+ }
+ }
+}
+
+
+/**
+ *
+ * Show or hide detect window and move it to the positon.
+ *
+ */
+
+void
+DragDetWnd::UpdateDetWnd(bool show, int32 x, int32 y)
+{
+ g_debug("%s: enter 0x%lx show %d x %d y %d\n",
+ __FUNCTION__,
+ (unsigned long) Get_Current_Drop_ctx(), show, x, y);
+
+ /* If the window is being shown, move it to the right place. */
+ if (show) {
+ Show();
+ Raise();
+ SetGeometry(x, y, DRAG_DET_WINDOW_WIDTH * 2, DRAG_DET_WINDOW_WIDTH * 2);
+ g_debug("%s: show at (%d, %d, %d, %d)\n", __FUNCTION__, x, y, DRAG_DET_WINDOW_WIDTH * 2, DRAG_DET_WINDOW_WIDTH * 2);
+ /*
+ * Wiggle the mouse here. Especially for G->H DnD, this improves
+ * reliability of making the drag escape the guest window immensly.
+ * Stolen from the legacy V2 DnD code.
+ */
+
+ SimulateMouseMove(x + 2, y + 2);
+ SetIsVisible(true);
+ } else {
+ g_debug("%s: hide\n", __FUNCTION__);
+ Hide();
+ SetIsVisible(false);
+ }
+}
+
+
+/**
+ * Start drag from drag source with current coordinate.
+ */
+
+void
+DragDetWnd::StartDrag(Glib::RefPtr<Gdk::ContentProvider> provider,
+ int mouseX,
+ int mouseY)
+{
+ g_debug("%s: start drag from offset (%d, %d)\n", __FUNCTION__,
+ mouseX, mouseY);
+ mSource->set_content(provider);
+}
+
+
+/**
+ * adjust the current x,y coordinate slightly.
+ */
+
+static
+bool adjust_coord(int *x, int *y, int width, int height)
+{
+ bool change = false;
+ /*
+ * first do left and top edges.
+ */
+ if (*x <= 5){
+ *x = 6;
+ change = true;
+ }
+
+ if (*y <= 5){
+ *y = 6;
+ change = true;
+ }
+
+ /*
+ * next, move result away from right and bottom edges.
+ */
+ if (*x > width - 5){
+ *x = width - 6;
+ change = true;
+ }
+
+ if (*y > height - 5){
+ *y = height - 6;
+ change = true;
+ }
+ return change;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * TryXTestFakeDeviceButtonEvent --
+ *
+ * Fake X mouse events in device level.
+ *
+ * XXX The function will only be called if XTestFakeButtonEvent does
+ * not work for mouse button release. Later on we may only call this
+ * one for mouse button simulation if this is more reliable.
+ *
+ * Results:
+ * Returns true on success, false on failure.
+ *
+ * Side effects:
+ * Generates mouse events.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+static
+bool TryXTestSimulateDeviceButtonEvent(GdkSurface* surface)
+{
+ XDeviceInfo *list = NULL;
+ XDeviceInfo *list2 = NULL;
+ XDevice *tdev = NULL;
+ XDevice *buttonDevice = NULL;
+ int numDevices = 0;
+ int i = 0;
+ int j = 0;
+ XInputClassInfo *ip = NULL;
+ Display *dndXDisplay;
+
+ if (!surface) {
+ g_debug("%s: unable to get widget\n", __FUNCTION__);
+ return false;
+ }
+
+ dndXDisplay = GDK_SURFACE_XDISPLAY(surface);
+
+ /* First get list of all input device. */
+ if (!(list = XListInputDevices (dndXDisplay, &numDevices))) {
+ g_debug("%s: XListInputDevices failed\n", __FUNCTION__);
+ return false;
+ } else {
+ g_debug("%s: XListInputDevices got %d devices\n", __FUNCTION__, numDevices);
+ }
+
+ list2 = list;
+
+ for (i = 0; i < numDevices; i++, list++) {
+ /* We only care about mouse device. */
+ if (list->use != IsXExtensionPointer) {
+ continue;
+ }
+
+ tdev = XOpenDevice(dndXDisplay, list->id);
+ if (!tdev) {
+ g_debug("%s: XOpenDevice failed\n", __FUNCTION__);
+ continue;
+ }
+
+ for (ip = tdev->classes, j = 0; j < tdev->num_classes; j++, ip++) {
+ if (ip->input_class == ButtonClass) {
+ buttonDevice = tdev;
+ break;
+ }
+ }
+
+ if (buttonDevice) {
+ g_debug("%s: calling XTestFakeDeviceButtonEvent for %s\n",
+ __FUNCTION__, list->name);
+ XTestFakeDeviceButtonEvent(dndXDisplay, buttonDevice, 1, False,
+ NULL, 0, CurrentTime);
+ buttonDevice = NULL;
+ }
+ XCloseDevice(dndXDisplay, tdev);
+ }
+ XFreeDeviceList(list2);
+ return true;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DragDetWnd::SimulateXEvents --
+ *
+ * Fake X mouse events and window movement for the detection window.
+ *
+ * This function shows the detection window and generates button
+ * press/release and pointer motion events.
+ *
+ * XXX This code should be implemented using GDK APIs.
+ * (gdk_display_warp_pointer?)
+ *
+ *
+ * Results:
+ * Returns true if generated X events, false on failure.
+ *
+ * Side effects:
+ * A ton of things.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool
+DragDetWnd::SimulateXEvents(const bool showWidget, const bool buttonEvent,
+ const bool buttonPress, const bool moveWindow,
+ const bool coordsProvided,
+ const int xCoord, const int yCoord)
+{
+ Display *dndXDisplay;
+ Window rootWnd;
+ Window dndXWindow;
+ Window rootReturn;
+ Window childReturn;
+ GdkSurface* surface;
+
+ bool ret = false;
+ int x, y;
+ int rootXReturn, rootYReturn;
+ int winXReturn, winYReturn;
+ unsigned int maskReturn;
+
+ TRACE_CALL();
+
+ x = xCoord;
+ y = yCoord;
+
+ surface = gtk_native_get_surface(mWnd->get_native()->gobj());
+ dndXDisplay = GDK_SURFACE_XDISPLAY(surface);
+ dndXWindow = GDK_SURFACE_XID(surface);
+ rootWnd = RootWindow(dndXDisplay, DefaultScreen(dndXDisplay));
+
+ /*
+ * Turn on X synchronization in order to ensure that our X events occur in
+ * the order called. In particular, we want the window movement to occur
+ * before the mouse movement so that the events we are coercing do in fact
+ * happen.
+ */
+ XSynchronize(dndXDisplay, True);
+
+ if (showWidget) {
+ g_debug("%s: showing Gtk widget\n", __FUNCTION__);
+ gtk_widget_set_visible(GTK_WIDGET(mWnd->gobj()), true);
+ gtk_window_present(mWnd->gobj());
+
+ }
+
+ /* Get the current location of the mouse if coordinates weren't provided. */
+ if (!coordsProvided) {
+ if (!XQueryPointer(dndXDisplay, rootWnd, &rootReturn, &childReturn,
+ &rootXReturn, &rootYReturn, &winXReturn, &winYReturn,
+ &maskReturn)) {
+ Warning("%s: XQueryPointer() returned False.\n", __FUNCTION__);
+ goto exit;
+ }
+
+ g_debug("%s: current mouse is at (%d, %d)\n", __FUNCTION__,
+ rootXReturn, rootYReturn);
+
+ /*
+ * Position away from the edge of the window.
+ */
+ int width = GetScreenWidth();
+ int height = GetScreenHeight();
+
+ x = rootXReturn;
+ y = rootYReturn;
+
+ if (adjust_coord(&x, &y, width, height)) {
+ g_debug("%s: adjusting mouse position. root %d, %d, adjusted %d, %d\n",
+ __FUNCTION__, rootXReturn, rootYReturn, x, y);
+ }
+ }
+
+ if (moveWindow) {
+ /*
+ * Make sure the window is at this point and at the top (raised). The
+ * window is resized to be a bit larger than we would like to increase
+ * the likelihood that mouse events are attributed to our window -- this
+ * is okay since the window is invisible and hidden on cancels and DnD
+ * finish.
+ */
+ XMoveResizeWindow(dndXDisplay,
+ dndXWindow,
+ x - DRAG_DET_WINDOW_WIDTH / 2 ,
+ y - DRAG_DET_WINDOW_WIDTH / 2,
+ DRAG_DET_WINDOW_WIDTH,
+ DRAG_DET_WINDOW_WIDTH);
+ XRaiseWindow(dndXDisplay, dndXWindow);
+ g_debug("%s: move wnd to (%d, %d, %d, %d)\n",
+ __FUNCTION__,
+ x - DRAG_DET_WINDOW_WIDTH / 2 ,
+ y - DRAG_DET_WINDOW_WIDTH / 2,
+ DRAG_DET_WINDOW_WIDTH,
+ DRAG_DET_WINDOW_WIDTH);
+ }
+
+ /*
+ * Generate mouse movements over the window. The second one makes ungrabs
+ * happen more reliably on KDE, but isn't necessary on GNOME.
+ */
+ if (mUseUInput) {
+ FakeMouse_Move(x, y);
+ FakeMouse_Move(x + 1, y + 1);
+ g_debug("%s: Uinput simulating moving mouse\n", __FUNCTION__);
+ } else {
+ XTestFakeMotionEvent(dndXDisplay, -1, x, y, CurrentTime);
+ XTestFakeMotionEvent(dndXDisplay, -1, x + 1, y + 1, CurrentTime);
+ }
+
+ g_debug("%s: move mouse to (%d, %d) and (%d, %d)\n", __FUNCTION__, x, y, x + 1, y + 1);
+
+ if (buttonEvent)
+ {
+ g_debug("%s: faking left mouse button %s\n", __FUNCTION__,
+ buttonPress ? "press" : "release");
+ if (mUseUInput) {
+ ret = FakeMouse_Click(buttonPress);
+ g_debug("%s: Uinput simulating click mouse with ret %d\n", __FUNCTION__, ret);
+ }
+ else {
+ XTestFakeButtonEvent(dndXDisplay, 1, buttonPress, CurrentTime);
+ XSync(dndXDisplay, False);
+
+ if (!buttonPress) {
+ /*
+ * The button release simulation may be failed with some distributions
+ * like Ubuntu 10.4 and RHEL 6 for guest->host DnD. So first query
+ * mouse button status. If some button is still down, we will try
+ * mouse device level event simulation. For details please refer
+ * to bug 552807.
+ */
+ if (!XQueryPointer(dndXDisplay, rootWnd, &rootReturn, &childReturn,
+ &rootXReturn, &rootYReturn, &winXReturn,
+ &winYReturn, &maskReturn)) {
+ Warning("%s: XQueryPointer returned False.\n", __FUNCTION__);
+ goto exit;
+ }
+
+ if ((maskReturn & Button1Mask)
+ || (maskReturn & Button2Mask)
+ || (maskReturn & Button3Mask)
+ || (maskReturn & Button4Mask)
+ || (maskReturn & Button5Mask)) {
+ Debug("%s: XTestFakeButtonEvent was not working for button "
+ "release, trying XTestFakeDeviceButtonEvent now.\n",
+ __FUNCTION__);
+ ret = TryXTestSimulateDeviceButtonEvent(surface);
+ }
+ else {
+ g_debug("%s: XTestFakeButtonEvent was working for button release.\n",
+ __FUNCTION__);
+ ret = true;
+ }
+ } else {
+ ret = true;
+ }
+ }
+ }
+
+exit:
+ XSynchronize(dndXDisplay, False);
+ return ret;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * DragDetWnd::SimulateMouseMove --
+ *
+ * Issue a fake mouse move event to the detection window.
+ *
+ * Results:
+ * Returns true on success, false on failure.
+ *
+ * Side effects:
+ * Generates mouse events.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool
+DragDetWnd::SimulateMouseMove(const int x, // IN: x-coord
+ const int y) // IN: y-coord
+{
+ return SimulateXEvents(false, false, false, false, true, x, y);
+}
--- /dev/null
+/*********************************************************
+ * Copyright (c) 2025 Broadcom. All Rights Reserved.
+ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *********************************************************/
+
+#ifndef GTK4
+#error "This should only build with GTK4"
+#endif
+
+#include "xutils/xutilsGTK4.hh"
+
+/* These must be after the gtkmm includes(in xutilsGTK4.hh), as gtkmm is quite picky. */
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+#include <gdk/x11/gdkx.h>
+#include <cstring>
+
+#include "vm_assert.h"
+
+
+namespace xutils {
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * xutils::GetCardinal --
+ *
+ * Utility function to get only one cardinal from a window property.
+ *
+ * Results:
+ * true if the function succeeded, along with a value for retValue.
+ * Otherwise false.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool
+GetCardinal(Glib::RefPtr<const Gdk::Surface> surface, // IN: Surface
+ const utf::string& atomName, // IN: Atom name
+ unsigned long& retValue) // OUT: Return value
+{
+ ASSERT(surface);
+ ASSERT(!atomName.empty());
+
+ std::vector<unsigned long> retValues;
+ bool result = GetCardinalList(surface, atomName, retValues);
+
+ if (result && retValues.size() == 1) {
+ retValue = retValues[0];
+ return true;
+ }
+
+ return false;
+}
+
+
+/*
+ *-----------------------------------------------------------------------------
+ *
+ * xutils::GetCardinalList --
+ *
+ * Utility function to get a cardinal list from a window property.
+ *
+ * Results:
+ * true if the function succeeded, along with return values for retValues.
+ * Otherwise false.
+ *
+ * Side effects:
+ * None.
+ *
+ *-----------------------------------------------------------------------------
+ */
+
+bool
+GetCardinalList(Glib::RefPtr<const Gdk::Surface> surface, // IN: Surface
+ const utf::string& atomName, // IN: Atom name
+ std::vector<unsigned long>& retValues) // OUT: Return values
+{
+ ASSERT(surface);
+ ASSERT(surface->get_display());
+ ASSERT(!atomName.empty());
+
+ GdkDisplay* gdkDisplay = const_cast<GdkDisplay*>(surface->get_display()->gobj());
+
+ Atom atom = gdk_x11_get_xatom_by_name_for_display(gdkDisplay, atomName.c_str());
+ Atom type;
+ int format;
+ unsigned long nitems;
+ unsigned long bytes_after;
+ uint8* values;
+ bool result = false;
+
+ gdk_x11_display_error_trap_push(gdkDisplay);
+
+ /*
+ * About the parameter "format" of Xorg XGetWindowProperty function.
+ * The "format" param specifies whether the data should be viewed as a list
+ * of 8-bit, 16-bit, or 32-bit quantities. Possible values are 8, 16, and 32.
+ * This information allows the X server to correctly perform byte-swap
+ * operations as necessary. If the format is 16-bit or 32-bit, you must
+ * explicitly cast your data pointer to an (unsigned char *) in the call
+ * to XChangeProperty.
+ *
+ * For more info, please refer to the document from Xorg:
+ * https://www.x.org/archive/X11R7.5/doc/man/man3/XChangeProperty.3.html
+ */
+ int ret = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(gdkDisplay),
+ gdk_x11_display_get_xrootwindow(gdkDisplay),
+ atom, 0, G_MAXLONG, False, XA_CARDINAL, &type,
+ &format, &nitems, &bytes_after, &values);
+ int err = gdk_x11_display_error_trap_pop(gdkDisplay);
+
+ if (ret == Success && err == 0) {
+ if (type == XA_CARDINAL && nitems > 0) {
+ retValues.resize(nitems);
+ result = true;
+
+ if (format == 8) {
+ for (unsigned long i = 0; i < nitems; i++) {
+ retValues[i] = values[i];
+ }
+ } else if (format == 16) {
+ uint16* shortValues = (uint16*)values;
+ for (unsigned long i = 0; i < nitems; i++) {
+ retValues[i] = shortValues[i];
+ }
+ } else if (format == 32) {
+ unsigned long* longValues = (unsigned long*)values;
+ for (unsigned long i = 0; i < nitems; i++) {
+ retValues[i] = longValues[i];
+ }
+ } else {
+ NOT_IMPLEMENTED();
+ }
+ }
+ }
+
+ if (values != NULL) {
+ XFree(values);
+ }
+ return result;
+}
+
+
+} // namespace xutils
--- /dev/null
+/*********************************************************
+ * Copyright (c) 2025 Broadcom. All Rights Reserved.
+ * The term "Broadcom" refers to Broadcom Inc. and/or its subsidiaries.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published
+ * by the Free Software Foundation version 2.1 and no later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the Lesser GNU General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ *********************************************************/
+
+#ifndef XUTILS_XUTILS_HH
+#define XUTILS_XUTILS_HH
+
+#include <gdkmm.h>
+#include <gtkmm.h>
+
+#include "stringxx/string.hh"
+
+
+namespace xutils {
+/* General property helpers */
+bool GetCardinal(Glib::RefPtr<const Gdk::Surface> surface,
+ const utf::string& atomName,
+ unsigned long& retValue);
+bool GetCardinalList(Glib::RefPtr<const Gdk::Surface> surface,
+ const utf::string& atomName,
+ std::vector<unsigned long>& retValues);
+} // namespace xutils
+
+#endif // XUTILS_XUTILS_HH