diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:17:27 +0000 |
commit | f215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch) | |
tree | 6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Additions/x11 | |
parent | Initial commit. (diff) | |
download | virtualbox-upstream/7.0.14-dfsg.tar.xz virtualbox-upstream/7.0.14-dfsg.zip |
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
58 files changed, 22075 insertions, 0 deletions
diff --git a/src/VBox/Additions/x11/.scm-settings b/src/VBox/Additions/x11/.scm-settings new file mode 100644 index 00000000..be559aa2 --- /dev/null +++ b/src/VBox/Additions/x11/.scm-settings @@ -0,0 +1,48 @@ +# $Id: .scm-settings $ +## @file +# Source code massager settings for the x11 guest addition components. +# + +# +# Copyright (C) 2010-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +--filter-out-files /undefined_* + +# x11include +--filter-out-files /x11include/*README +/x11include/*: --external-copyright --no-convert-tabs --strip-no-trailing-lines --no-strip-trailing-blanks --no-fix-flower-box-markers --no-convert-eol --no-force-final-eol --dont-set-svn-eol --dont-set-svn-keywords --no-fix-header-guards --skip-unicode-checks +/x11include/*h.in: --treat-as .h + +# vboxvideo +--filter-out-files /vboxvideo/README.testing +/vboxvideo/*.c|/vboxvideo/*.h: --license-mit +/vboxvideo/vboxvideo*.c|/vboxvideo/vboxvideo*.h: --license-based-on-mit --no-convert-tabs --dont-set-svn-keywords +/vboxvideo/edid.c: --license-based-on-mit +/vboxvideo/setmode.c: --license-based-on-mit +/VBoxClient/display-svga-xf86cvt.cpp: --license-based-on-mit + +# Installer +/Installer/98vboxadd-xclient: --treat-as .sh +--filter-out-files /Installer/vboxvideo.ids +--filter-out-files /Installer/linux_xorg_suse11.conf +--filter-out-files /Installer/solaris_xorg.conf +--filter-out-files /Installer/solaris_xorg_modeless.conf diff --git a/src/VBox/Additions/x11/Installer/98vboxadd-xclient b/src/VBox/Additions/x11/Installer/98vboxadd-xclient new file mode 100755 index 00000000..718a43bd --- /dev/null +++ b/src/VBox/Additions/x11/Installer/98vboxadd-xclient @@ -0,0 +1,48 @@ +#!/bin/sh +## @file +# Start the Guest Additions X11 Client +# + +# +# Copyright (C) 2007-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +# Sanity check: if non-writeable PID-files are present in the user home +# directory VBoxClient will fail to start. +for i in $HOME/.vboxclient-*.pid; do + test -w $i || rm -f $i +done + +if ! test -c /dev/vboxguest 2>/dev/null; then + # Do not start if the kernel module is not present. + # Execute notify-send in the back-ground to avoid racing with sddm, + # as notify-send may wait for sddm to start while it waits for us to exit. + notify-send "VBoxClient: the VirtualBox kernel service is not running. Exiting." & +elif test -z "${SSH_CONNECTION}"; then + # This script can also be triggered by a connection over SSH, which is not + # what we had in mind, so we do not start VBoxClient in that case. We do + # not use "exit" here as this script is "source"d, not executed. + /usr/bin/VBoxClient --clipboard + /usr/bin/VBoxClient --checkhostversion + /usr/bin/VBoxClient --seamless + /usr/bin/VBoxClient --draganddrop + /usr/bin/VBoxClient --vmsvga-session # In case VMSVGA emulation is enabled +fi diff --git a/src/VBox/Additions/x11/Installer/linux_xorg_suse11.conf b/src/VBox/Additions/x11/Installer/linux_xorg_suse11.conf new file mode 100644 index 00000000..c55ea88e --- /dev/null +++ b/src/VBox/Additions/x11/Installer/linux_xorg_suse11.conf @@ -0,0 +1,130 @@ +# Default X11 configuration file for SUSE and openSUSE 11 guests in VirtualBox. +# Based on: +# SaX generated X11 config file +# Version: 8.1 +# + +Section "Files" + FontPath "/usr/share/fonts/misc:unscaled" + FontPath "/usr/share/fonts/local" + FontPath "/usr/share/fonts/75dpi:unscaled" + FontPath "/usr/share/fonts/100dpi:unscaled" + FontPath "/usr/share/fonts/Type1" + FontPath "/usr/share/fonts/URW" + FontPath "/usr/share/fonts/Speedo" + FontPath "/usr/share/fonts/PEX" + FontPath "/usr/share/fonts/cyrillic" + FontPath "/usr/share/fonts/latin2/misc:unscaled" + FontPath "/usr/share/fonts/latin2/75dpi:unscaled" + FontPath "/usr/share/fonts/latin2/100dpi:unscaled" + FontPath "/usr/share/fonts/latin2/Type1" + FontPath "/usr/share/fonts/latin7/75dpi:unscaled" + FontPath "/usr/share/fonts/baekmuk:unscaled" + FontPath "/usr/share/fonts/japanese:unscaled" + FontPath "/usr/share/fonts/kwintv" + FontPath "/usr/share/fonts/truetype" + FontPath "/usr/share/fonts/uni:unscaled" + FontPath "/usr/share/fonts/CID" + FontPath "/usr/share/fonts/ucs/misc:unscaled" + FontPath "/usr/share/fonts/ucs/75dpi:unscaled" + FontPath "/usr/share/fonts/ucs/100dpi:unscaled" + FontPath "/usr/share/fonts/hellas/misc:unscaled" + FontPath "/usr/share/fonts/hellas/75dpi:unscaled" + FontPath "/usr/share/fonts/hellas/100dpi:unscaled" + FontPath "/usr/share/fonts/hellas/Type1" + FontPath "/usr/share/fonts/misc/sgi:unscaled" + FontPath "/usr/share/fonts/xtest" + FontPath "/opt/kde3/share/fonts" + InputDevices "/dev/gpmdata" + InputDevices "/dev/input/mice" +EndSection + +Section "ServerFlags" + Option "AllowMouseOpenFail" "on" + Option "ZapWarning" "on" +EndSection + +Section "Module" + Load "dri" + Load "dbe" + Load "freetype" + Load "extmod" + Load "glx" +EndSection + +Section "InputDevice" + Driver "kbd" + Identifier "Keyboard[0]" + Option "Protocol" "Standard" + Option "XkbLayout" "us" + Option "XkbModel" "microsoftpro" + Option "XkbRules" "xfree86" +EndSection + + +Section "InputDevice" + Driver "mouse" + Identifier "Mouse[1]" + Option "Buttons" "9" + Option "Device" "/dev/input/mice" + Option "Name" "VirtualBox Mouse Buttons" + Option "Protocol" "explorerps/2" + Option "Vendor" "Oracle Corporation" + Option "ZAxisMapping" "4 5" +EndSection + + +Section "InputDevice" + Driver "vboxmouse" + Identifier "Mouse[2]" + Option "Device" "/dev/vboxguest" + Option "Name" "VirtualBox Mouse" + Option "Vendor" "Oracle Corporation" +EndSection + + +Section "Monitor" + Identifier "Monitor[0]" + ModelName "VirtualBox Virtual Output" + VendorName "Oracle Corporation" +EndSection + + +Section "Screen" + SubSection "Display" + Depth 24 + EndSubSection + Device "Device[0]" + Identifier "Screen[0]" + Monitor "Monitor[0]" +EndSection + + +Section "Device" + BoardName "VirtualBox Graphics" + Driver "vboxvideo" + Identifier "Device[0]" + VendorName "Oracle Corporation" +EndSection + + + +Section "ServerLayout" + Identifier "Layout[all]" + InputDevice "Keyboard[0]" "CoreKeyboard" + InputDevice "Mouse[1]" "CorePointer" + InputDevice "Mouse[2]" "SendCoreEvents" + Option "Clone" "off" + Option "Xinerama" "off" + Screen "Screen[0]" +EndSection + + +Section "DRI" + Group "video" + Mode 0660 +EndSection + +Section "Extensions" +EndSection + diff --git a/src/VBox/Additions/x11/Installer/solaris_xorg.conf b/src/VBox/Additions/x11/Installer/solaris_xorg.conf new file mode 100644 index 00000000..dd5d780e --- /dev/null +++ b/src/VBox/Additions/x11/Installer/solaris_xorg.conf @@ -0,0 +1,113 @@ +# Default xorg.conf for Solaris guests. +# +# This file was created by VirtualBox Additions installer as it +# was unable to find any existing configuration file for X. + +Section "ServerLayout" + Identifier "X.org Configured" + Screen 0 "Screen0" 0 0 + InputDevice "Mouse0" "CorePointer" + InputDevice "Keyboard0" "CoreKeyboard" +EndSection + +Section "Files" + RgbPath "/usr/X11/lib/X11/rgb" + FontPath "/usr/X11/lib/X11/fonts/misc/:unscaled" + FontPath "/usr/X11/lib/X11/fonts/100dpi/:unscaled" + FontPath "/usr/X11/lib/X11/fonts/75dpi/:unscaled" + FontPath "/usr/X11/lib/X11/fonts/misc/" + FontPath "/usr/X11/lib/X11/fonts/Type1/" + FontPath "/usr/X11/lib/X11/fonts/100dpi/" + FontPath "/usr/X11/lib/X11/fonts/75dpi/" + FontPath "/usr/X11/lib/X11/fonts/TrueType/" + FontPath "/usr/X11/lib/X11/fonts/Type1/sun/" + FontPath "/usr/X11/lib/X11/fonts/F3bitmaps/" +EndSection + +Section "Module" + Load "IA" + Load "dbe" + Load "extmod" + Load "record" + Load "xtrap" + Load "Glcore" + Load "glx" + Load "dri" + Load "xtsol" + Load "type1" + Load "freetype" +EndSection + +Section "InputDevice" + Identifier "Keyboard0" + Driver "kbd" +EndSection + +Section "InputDevice" + Identifier "Mouse0" + Driver "mouse" + Option "CorePointer" + Option "Device" "/dev/mouse" + Option "Protocol" "auto" + Option "ZAxisMapping" "4 5" +EndSection + +Section "Device" + Identifier "Generic Video Card" + Driver "vesa" + BusID "0:2:0" +EndSection + +Section "Monitor" + Identifier "Monitor0" + VendorName "Monitor Vendor" + ModelName "Monitor Model" + HorizSync 30.0 - 110.0 + VertRefresh 50.0 - 150.0 +EndSection + +Section "Device" + Identifier "Card0" + Driver "vesa" + VendorName "Unknown Vendor" + BoardName "Unknown Board" + BusID "PCI:0:2:0" +EndSection + +Section "Screen" + Identifier "Screen0" + Device "Card0" + Monitor "Monitor0" + DefaultDepth 24 + SubSection "Display" + ViewPort 0 0 + Depth 1 + Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00" + EndSubSection + SubSection "Display" + ViewPort 0 0 + Depth 4 + Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00" + EndSubSection + SubSection "Display" + ViewPort 0 0 + Depth 8 + Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00" + EndSubSection + SubSection "Display" + ViewPort 0 0 + Depth 15 + Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00" + EndSubSection + SubSection "Display" + ViewPort 0 0 + Depth 16 + Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00" + EndSubSection + SubSection "Display" + ViewPort 0 0 + Depth 24 + Modes "1024x768_75.00" "800x600_75.00" "640x480_60.00" + EndSubSection +EndSection + diff --git a/src/VBox/Additions/x11/Installer/solaris_xorg_modeless.conf b/src/VBox/Additions/x11/Installer/solaris_xorg_modeless.conf new file mode 100644 index 00000000..0468ba24 --- /dev/null +++ b/src/VBox/Additions/x11/Installer/solaris_xorg_modeless.conf @@ -0,0 +1,81 @@ +# Default xorg.conf for Solaris guests. +# +# This file was created by VirtualBox Additions installer as it +# was unable to find any existing configuration file for X. + +Section "ServerLayout" + Identifier "X.org Configured" + Screen 0 "Screen0" 0 0 + InputDevice "Mouse0" "CorePointer" + InputDevice "Keyboard0" "CoreKeyboard" +EndSection + +Section "Files" + FontPath "/usr/X11/lib/X11/fonts/misc/:unscaled" + FontPath "/usr/X11/lib/X11/fonts/100dpi/:unscaled" + FontPath "/usr/X11/lib/X11/fonts/75dpi/:unscaled" + FontPath "/usr/X11/lib/X11/fonts/misc/" + FontPath "/usr/X11/lib/X11/fonts/Type1/" + FontPath "/usr/X11/lib/X11/fonts/100dpi/" + FontPath "/usr/X11/lib/X11/fonts/75dpi/" + FontPath "/usr/X11/lib/X11/fonts/TrueType/" + FontPath "/usr/X11/lib/X11/fonts/Type1/sun/" + FontPath "/usr/X11/lib/X11/fonts/F3bitmaps/" +EndSection + +Section "Module" + Load "IA" + Load "dbe" + Load "extmod" + Load "record" + Load "xtrap" + Load "Glcore" + Load "glx" + Load "dri" + Load "xtsol" + Load "type1" + Load "freetype" +EndSection + +Section "InputDevice" + Identifier "Keyboard0" + Driver "kbd" +EndSection + +Section "InputDevice" + Identifier "Mouse0" + Driver "mouse" + Option "CorePointer" + Option "Device" "/dev/mouse" + Option "Protocol" "auto" + Option "ZAxisMapping" "4 5" +EndSection + +Section "Device" + Identifier "Generic Video Card" + Driver "vesa" + BusID "0:2:0" +EndSection + +Section "Monitor" + Identifier "Monitor0" + VendorName "Monitor Vendor" + ModelName "Monitor Model" + HorizSync 30.0 - 110.0 + VertRefresh 50.0 - 150.0 +EndSection + +Section "Device" + Identifier "Card0" + Driver "vesa" + VendorName "Unknown Vendor" + BoardName "Unknown Board" + BusID "PCI:0:2:0" +EndSection + +Section "Screen" + Identifier "Screen0" + Device "Card0" + Monitor "Monitor0" +EndSection + diff --git a/src/VBox/Additions/x11/Installer/vboxclient.desktop b/src/VBox/Additions/x11/Installer/vboxclient.desktop new file mode 100644 index 00000000..b5e4d863 --- /dev/null +++ b/src/VBox/Additions/x11/Installer/vboxclient.desktop @@ -0,0 +1,13 @@ +[Desktop Entry] +Type=Application +Encoding=UTF-8 +Version=1.0 +Name=vboxclient +Name[C]=vboxclient +Comment[C]=VirtualBox User Session Services +Comment=VirtualBox User Session Services +Comment[it]=Servizi di sessione utente di VirtualBox +Comment[pl]=Usługi sesji użytkownika VirtualBox +Exec=/usr/bin/VBoxClient-all +X-GNOME-Autostart-enabled=true +X-KDE-autostart-after=panel diff --git a/src/VBox/Additions/x11/Installer/vboxvideo.ids b/src/VBox/Additions/x11/Installer/vboxvideo.ids new file mode 100644 index 00000000..8286a95e --- /dev/null +++ b/src/VBox/Additions/x11/Installer/vboxvideo.ids @@ -0,0 +1 @@ +80eebeef diff --git a/src/VBox/Additions/x11/Installer/x11config.pl b/src/VBox/Additions/x11/Installer/x11config.pl new file mode 100755 index 00000000..dcbb6618 --- /dev/null +++ b/src/VBox/Additions/x11/Installer/x11config.pl @@ -0,0 +1,139 @@ +#!/usr/bin/perl -w +# $Id: x11config.pl $ +## @file +# Guest Additions X11 config update script +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +my $temp="/tmp/xorg.conf"; +my $os_type=`uname -s`; +my @cfg_files = ("/etc/X11/xorg.conf-4", "/etc/X11/xorg.conf", "/etc/X11/.xorg.conf", "/etc/xorg.conf", + "/usr/etc/X11/xorg.conf-4", "/usr/etc/X11/xorg.conf", "/usr/lib/X11/xorg.conf-4", + "/usr/lib/X11/xorg.conf", "/etc/X11/XF86Config-4", "/etc/X11/XF86Config", + "/etc/XF86Config", "/usr/X11R6/etc/X11/XF86Config-4", "/usr/X11R6/etc/X11/XF86Config", + "/usr/X11R6/lib/X11/XF86Config-4", "/usr/X11R6/lib/X11/XF86Config"); +my $CFG; +my $TMP; + +my $config_count = 0; + +foreach $cfg (@cfg_files) +{ + + if (open(CFG, $cfg)) + { + open(TMP, ">$temp") or die "Can't create $TMP: $!\n"; + + my $have_mouse = 0; + my $in_section = 0; + + while (defined ($line = <CFG>)) + { + if ($line =~ /^\s*Section\s*"([a-zA-Z]+)"/i) + { + my $section = lc($1); + if (($section eq "inputdevice") || ($section eq "device")) + { + $in_section = 1; + } + if ($section eq "serverlayout") + { + $in_layout = 1; + } + } else { + if ($line =~ /^\s*EndSection/i) + { + $in_section = 0; + $in_layout = 0; + } + } + + if ($in_section) + { + if ($line =~ /^\s*driver\s+\"(?:mouse|vboxmouse)\"/i) + { + $line = " Driver \"vboxmouse\"\n Option \"CorePointer\"\n"; + $have_mouse = 1 + } + + # Other drivers sending events interfere badly with pointer integration + if ($line =~ /^\s*option\s+\"(?:alwayscore|sendcoreevents|corepointer)\"/i) + { + $line = ""; + } + + # Solaris specific: /dev/kdmouse for PS/2 and not /dev/mouse + if ($os_type =~ 'SunOS') + { + if ($line =~ /^\s*option\s+\"(?:device)\"\s+\"(?:\/dev\/mouse)\"/i) + { + $line = " Option \"Device\" \"\/dev\/kdmouse\"\n" + } + } + + if ($line =~ /^\s*driver\s+\"(?:fbdev|vga|vesa|vboxvideo|ChangeMe)\"/i) + { + $line = " Driver \"vboxvideo\"\n"; + } + } + if ($in_layout) + { + # Other drivers sending events interfere badly with pointer integration + if ( $line =~ /^\s*inputdevice.*\"(?:alwayscore|sendcoreevents)\"/i) + { + $line = ""; + } + } + print TMP $line; + } + + if (!$have_mouse) { + print TMP "\n"; + print TMP "Section \"InputDevice\"\n"; + print TMP " Identifier \"VBoxMouse\"\n"; + print TMP " Driver \"vboxmouse\"\n"; + if ($os_type eq 'SunOS') + { + print TMP " Option \"Device\" \"\/dev\/kdmouse\"\n"; + } + print TMP " Option \"CorePointer\"\n"; + print TMP "EndSection\n"; + } + close(TMP); + + rename $cfg, $cfg.".bak"; + system("cp $temp $cfg"); + unlink $temp; + + # Solaris specific: Rename our modified .xorg.conf to xorg.conf for it to be used + if (($os_type =~ 'SunOS') && ($cfg =~ '/etc/X11/.xorg.conf')) + { + system("mv -f $cfg /etc/X11/xorg.conf"); + } + + $config_count++; + } +} + +$config_count != 0 or die "Could not find any X11 configuration files"; diff --git a/src/VBox/Additions/x11/Installer/x11config.sh b/src/VBox/Additions/x11/Installer/x11config.sh new file mode 100755 index 00000000..a17bf47f --- /dev/null +++ b/src/VBox/Additions/x11/Installer/x11config.sh @@ -0,0 +1,171 @@ +#!/bin/sh +# $Id: x11config.sh $ +## @file +# Guest Additions X11 config update script +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +auto_mouse="" +auto_keyboard="" +no_bak="" +old_mouse_dev="/dev/psaux" +video_driver="vboxvideo" + +tab=`printf '\t'` + +ALL_SECTIONS=\ +'^[ '$tab']*[Ss][Ee][Cc][Tt][Ii][Oo][Nn][ '$tab']*'\ +'"\([Ii][Nn][Pp][Uu][Tt][Dd][Ee][Vv][Ii][Cc][Ee]\|'\ +'[Dd][Ee][Vv][Ii][Cc][Ee]\|'\ +'[Ss][Ee][Rr][Vv][Ee][Rr][Ll][Aa][Yy][Oo][Uu][Tt]\|'\ +'[Ss][Cc][Rr][Ee][Ee][Nn]\|'\ +'[Mm][Oo][Nn][Ii][Tt][Oo][Rr]\|'\ +'[Kk][Ee][Yy][Bb][Oo][Aa][Rr][Dd]\|'\ +'[Pp][Oo][Ii][Nn][Tt][Ee][Rr]\)"' +# ^\s*Section\s*"(InputDevice|Device|ServerLayout|Screen|Monitor|Keyboard|Pointer)" + +KBD_SECTION='^[ '$tab']*[Ss][Ee][Cc][Tt][Ii][Oo][Nn][ '$tab']*"'\ +'[Ii][Nn][Pp][Uu][Tt][Dd][Ee][Vv][Ii][Cc][Ee]"' # ^\s*section\s*\"inputdevice\" + +END_SECTION='[Ee][Nn][Dd][Ss][Ee][Cc][Tt][Ii][Oo][Nn]' # EndSection + +OPT_XKB='^[ '$tab']*option[ '$tab'][ '$tab']*"xkb' + +DRIVER_KBD='^[ '$tab']*[Dd][Rr][Ii][Vv][Ee][Rr][ '$tab'][ '$tab']*'\ +'"\(kbd\|keyboard\)"' +# ^\s*driver\s+\"(kbd|keyboard)\" + +reconfigure() +{ + cfg="$1" + tmp="$cfg.vbox.tmp" + test -w "$cfg" || { echo "$cfg does not exist"; return; } + rm -f "$tmp" + test ! -e "$tmp" || { echo "Failed to delete $tmp"; return; } + touch "$tmp" + test -w "$tmp" || { echo "Failed to create $tmp"; return; } + xkb_opts="`cat "$cfg" | sed -n -e "/$KBD_SECTION/,/$END_SECTION/p" | + grep -i "$OPT_XKB"`" + kbd_drv="`cat "$cfg" | sed -n -e "/$KBD_SECTION/,/$END_SECTION/p" | + sed -n -e "0,/$DRIVER_KBD/s/$DRIVER_KBD/\\1/p"`" + test -z "${kbd_drv}" && test -z "${auto_keyboard}" && kbd_drv=keyboard + cat > "$tmp" << EOF +# VirtualBox generated configuration file +# based on $cfg. +EOF + cat "$cfg" | sed -e "/$ALL_SECTIONS/,/$END_SECTION/s/\\(.*\\)/# \\1/" >> "$tmp" + test -n "$kbd_drv" && cat >> "$tmp" << EOF +Section "InputDevice" + Identifier "Keyboard[0]" + Driver "$kbd_drv" +$xkb_opts + Option "Protocol" "Standard" + Option "CoreKeyboard" +EndSection +EOF + kbd_line="" + test -n "$kbd_drv" && kbd_line=' InputDevice "Keyboard[0]" "CoreKeyboard"' + test -z "$auto_mouse" && + cat >> "$tmp" << EOF + +Section "InputDevice" + Driver "mouse" + Identifier "Mouse[1]" + Option "Buttons" "9" + Option "Device" "$old_mouse_dev" + Option "Name" "VirtualBox Mouse Buttons" + Option "Protocol" "explorerps/2" + Option "Vendor" "Oracle Corporation" + Option "ZAxisMapping" "4 5" + Option "CorePointer" +EndSection + +Section "InputDevice" + Driver "vboxmouse" + Identifier "Mouse[2]" + Option "Device" "/dev/vboxguest" + Option "Name" "VirtualBox Mouse" + Option "Vendor" "Oracle Corporation" + Option "SendCoreEvents" +EndSection + +Section "ServerLayout" + Identifier "Layout[all]" +${kbd_line} + InputDevice "Mouse[1]" "CorePointer" + InputDevice "Mouse[2]" "SendCoreEvents" + Option "Clone" "off" + Option "Xinerama" "off" + Screen "Screen[0]" +EndSection +EOF + + cat >> "$tmp" << EOF + +Section "Monitor" + Identifier "Monitor[0]" + ModelName "VirtualBox Virtual Output" + VendorName "Oracle Corporation" +EndSection + +Section "Device" + BoardName "VirtualBox Graphics" + Driver "${video_driver}" + Identifier "Device[0]" + VendorName "Oracle Corporation" +EndSection + +Section "Screen" + SubSection "Display" + Depth 24 + EndSubSection + Device "Device[0]" + Identifier "Screen[0]" + Monitor "Monitor[0]" +EndSection +EOF + + test -n "$no_bak" -o -f "$cfg.vbox" || cp "$cfg" "$cfg.vbox" + test -n "$no_bak" || mv "$cfg" "$cfg.bak" + mv "$tmp" "$cfg" +} + +while test -n "$1" +do + case "$1" in + --autoMouse) + auto_mouse=1 ;; + --autoKeyboard) + auto_keyboard=1 ;; + --noBak) + no_bak=1 ;; + --nopsaux) + old_mouse_dev="/dev/input/mice" ;; + --vmsvga) + video_driver="vmware" ;; + *) + reconfigure "$1" ;; + esac + shift +done diff --git a/src/VBox/Additions/x11/Installer/x11config15.pl b/src/VBox/Additions/x11/Installer/x11config15.pl new file mode 100755 index 00000000..7f404c95 --- /dev/null +++ b/src/VBox/Additions/x11/Installer/x11config15.pl @@ -0,0 +1,97 @@ +#!/usr/bin/perl -w +# $Id: x11config15.pl $ +## @file +# Guest Additions X11 config update script for X.org 1.5 +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +# What this script does: X.org 1.5 introduces full hardware autodetection +# and no longer requires the user to provide an X.org configuration file. +# However, if such a file is provided, it will override autodetection of +# the graphics card (not of vboxmouse as far as I can see). Although this +# would normally be the user's business, at least Fedora 9 still generates +# a configuration file by default, so we have to rewrite it if we want +# the additions to work on a default guest installation. So we simply go +# through any configuration files we may find on the system and replace +# references to VESA or framebuffer drivers (which might be autodetected +# for use on a VirtualBox guest) and replace them with vboxvideo. + +use File::Copy; + +my $temp="/tmp/xorg.conf"; +# The list of possible names of X.org configuration files +my @cfg_files = ("/etc/X11/xorg.conf-4", "/etc/X11/xorg.conf", "/etc/X11/.xorg.conf", "/etc/xorg.conf", + "/usr/etc/X11/xorg.conf-4", "/usr/etc/X11/xorg.conf", "/usr/lib/X11/xorg.conf-4", + "/usr/lib/X11/xorg.conf"); +my $CFG; +my $TMP; + +# Subroutine to roll back after a partial installation +sub do_fail { + foreach $cfg (@cfg_files) { + move $cfg.".vbox", $cfg; + unlink $cfg.".vbox"; + } + die $1; +} + +# Perform the substitution on any configuration file we may find. +foreach $cfg (@cfg_files) { + + if (open(CFG, $cfg)) { + open(TMP, ">$temp") + or &do_fail("Can't create $TMP: $!\n"); + + while (defined ($line = <CFG>)) { + if ($line =~ /^\s*Section\s*"([a-zA-Z]+)"/i) { + my $section = lc($1); + if ($section eq "device") { + $in_section = 1; + } + } else { + if ($line =~ /^\s*EndSection/i) { + $in_section = 0; + } + } + + if ($in_section) { + if ($line =~ /^\s*driver\s+\"(fbdev|vga|vesa|vboxvideo|ChangeMe)\"/i) { + $line =~ s/(fbdev|vga|vesa|vboxvideo|ChangeMe)/vboxvideo/i; + } + } + print TMP $line; + } + close(TMP); + + # We do not overwrite existing $cfg.".vbox" files because that will + # likely ruin any future attempts to uninstall the additions + copy $cfg, $cfg.".bak"; + if (! -e $cfg.".vbox") { + rename $cfg, $cfg.".vbox"; + } + copy $temp, $cfg + or &do_fail("Could not overwrite configuration file $cfg! Exiting..."); + unlink $temp; + } +} diff --git a/src/VBox/Additions/x11/Installer/x11config15sol.pl b/src/VBox/Additions/x11/Installer/x11config15sol.pl new file mode 100755 index 00000000..a4bcedf8 --- /dev/null +++ b/src/VBox/Additions/x11/Installer/x11config15sol.pl @@ -0,0 +1,123 @@ +#!/usr/bin/perl +# $Id: x11config15sol.pl $ +## @file +# Guest Additions X11 config update script +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +use strict; +use warnings; + +my $temp="/tmp/xorg.conf"; +my $os_type=`uname -s`; +my @cfg_files = ("/etc/X11/xorg.conf", "/etc/X11/.xorg.conf", "/etc/X11/xorg.conf-4", "/etc/xorg.conf", + "/usr/etc/X11/xorg.conf-4", "/usr/etc/X11/xorg.conf", "/usr/lib/X11/xorg.conf-4", + "/usr/lib/X11/xorg.conf", "/etc/X11/XF86Config-4", "/etc/X11/XF86Config", + "/etc/XF86Config", "/usr/X11R6/etc/X11/XF86Config-4", "/usr/X11R6/etc/X11/XF86Config", + "/usr/X11R6/lib/X11/XF86Config-4", "/usr/X11R6/lib/X11/XF86Config"); + +## @todo: r=ramshankar: Hmm, why do we use the same variable name with upper/lower case for different variables? +my $cfg; +my $CFG; +my $TMP; +my $line; +my $config_count = 0; + +# Command line options +if ($#ARGV < 0) +{ + die "x11config15sol.pl: Missing driver name argument to configure for X.org"; +} +my $driver_name = $ARGV[0]; + +# Loop through all possible config files and change them. It's done this wasy for hysterical raisins +# as we didn't know what the correct config file is so we update all of them. However, for Solaris it's +# most likely -only- one of the 2 config files (/etc/X11/xorg.conf, /etc/X11/.xorg.conf). +foreach $cfg (@cfg_files) +{ + + if (($os_type =~ 'SunOS') && (defined $ENV{PKG_INSTALL_ROOT})) + { + $cfg = $ENV{PKG_INSTALL_ROOT}.$cfg; + } + if (open(CFG, $cfg)) + { + open(TMP, ">$temp") or die "Can't create $TMP: $!\n"; + + my $in_section = 0; + + while (defined ($line = <CFG>)) + { + if ($line =~ /^\s*Section\s*"([a-zA-Z]+)"/i) + { + my $section = lc($1); + if ($section eq "device") + { + $in_section = 1; + } + } + else + { + if ($line =~ /^\s*EndSection/i) + { + $in_section = 0; + } + } + + if ($in_section) + { + if ($line =~ /^\s*driver\s+\"(?:fbdev|vga|vesa|vboxvideo|ChangeMe)\"/i) + { + $line = " Driver \"$driver_name\"\n"; + } + } + print TMP $line; + } + + close(TMP); + + rename $cfg, $cfg.".bak"; + system("cp $temp $cfg"); + unlink $temp; + + # Solaris specific: Rename our modified .xorg.conf to xorg.conf for it to be used + if ((defined $ENV{PKG_INSTALL_ROOT}) && + ($os_type =~ 'SunOS') && ($cfg =~ "$ENV{PKG_INSTALL_ROOT}/etc/X11/.xorg.conf")) + { + system("mv -f $cfg $ENV{PKG_INSTALL_ROOT}/etc/X11/xorg.conf"); + } + else + { + if (($os_type =~ 'SunOS') && ($cfg =~ '/etc/X11/.xorg.conf')) + { + system("mv -f $cfg /etc/X11/xorg.conf"); + } + } + + $config_count++; + } +} + +$config_count != 0 or die "Could not find any X11 configuration files."; + diff --git a/src/VBox/Additions/x11/Installer/x11config15suse.pl b/src/VBox/Additions/x11/Installer/x11config15suse.pl new file mode 100755 index 00000000..d221a785 --- /dev/null +++ b/src/VBox/Additions/x11/Installer/x11config15suse.pl @@ -0,0 +1,166 @@ +#!/usr/bin/perl -w +# $Id: x11config15suse.pl $ +## @file +# Guest Additions X11 config update script +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +# Versions of (open)SUSE which ship X.Org Server 1.5 still do not enable +# mouse autodetection, so on these systems we have to enable vboxmouse in the +# X.Org configuration file as well as vboxvideo. When uninstalling, we enable +# the fbdev driver, which SUSE prefers over vesa, and we leave the references +# to vboxmouse in place, as without the driver they are harmless. + +use File::Copy; + +# This is the file name for the temporary file we write the new configuration +# to. +# @todo: perl must have an API for generating this +my $temp="/tmp/xorg.conf"; +# The list of possible names of X.org configuration files +my @cfg_files = ("/etc/X11/xorg.conf-4", "/etc/X11/xorg.conf", "/etc/X11/.xorg.conf", "/etc/xorg.conf", + "/usr/etc/X11/xorg.conf-4", "/usr/etc/X11/xorg.conf", "/usr/lib/X11/xorg.conf-4", + "/usr/lib/X11/xorg.conf"); +# File descriptor of the old configuration file +my $CFG; +# File descriptor of the temporary file +my $TMP; + +# The name of the mouse driver we are enabling +my $mousedrv = 'vboxmouse'; +# The name of the video driver we are enabling +my $videodrv= 'vboxvideo'; + +# If we are uninstalling, restore the old video driver +if (@ARGV && "$ARGV[0]" eq 'uninstall') +{ + $videodrv = 'fbdev' # SUSE prefers this one +} + +# How many different configuration files have we found? +my $config_count = 0; + +# Subroutine to roll back after a partial installation +sub do_fail { + foreach $cfg (@cfg_files) { + move "$cfg.vbox", $cfg; + unlink "$cfg.vbox"; + } + die $_[0]; +} + +# Perform the substitution on any configuration file we may find. +foreach $cfg (@cfg_files) { + + if (open(CFG, $cfg)) { + open(TMP, ">$temp") + or &do_fail("Can't create $TMP: $!\n"); + + my $have_mouse = 0; + my $in_section = 0; + my $in_layout = 0; + + # Go through the configuration file line by line + while (defined ($line = <CFG>)) { + # Look for the start of sections + if ($line =~ /^\s*Section\s*"([a-zA-Z]+)"/i) { + my $section = lc($1); + # And see if they are device or input device sections + if (($section eq "inputdevice") || $section eq "device") { + $in_section = 1; + } + # Or server layout sections + if ($section eq "serverlayout") + { + $in_section = 1; + $in_layout = 1; + } + } else { + if ($line =~ /^\s*EndSection/i && $in_layout) { + # We always add this to the end of the server layout. + print TMP " InputDevice \"VBoxMouse\"\n" + } + if ($line =~ /^\s*EndSection/i) { + $in_section = 0; + $in_layout = 0; + } + } + + if ($in_section) { + # Inside sections, look for any graphics drivers and replace + # them with our one. + if ($line =~ /^\s*driver\s+\"(fbdev|vga|vesa|vboxvideo|ChangeMe)\"/i) { + $line =~ s/(fbdev|vga|vesa|vboxvideo|ChangeMe)/$videodrv/i; + } + # Also keep track of whether this configuration file contains + # an input device section for vboxmouse. If it does, we don't + # need to add one later. + if ($line =~ /^\s*driver\s+\"(?:vboxmouse)\"/i) + { + $have_mouse = 1 + } + + # We add vboxmouse to the server layout section ourselves, so + # remove any existing references to it. + if ( $line =~ /^\s*inputdevice.*\"vboxmouse\"/i) + { + $line = ""; + } + } + print TMP $line; + } + + # We always add a vboxmouse section at the end for SUSE guests using + # X.Org 1.5 if vboxmouse is not referenced anywhere else in the file, + # and we do not remove it when we uninstall the additions, as it will + # not do any harm if it is left. + if (!$have_mouse) { + print TMP "\n"; + print TMP "Section \"InputDevice\"\n"; + print TMP " Identifier \"VBoxMouse\"\n"; + print TMP " Driver \"$mousedrv\"\n"; + print TMP " Option \"Device\" \"\/dev\/vboxguest\"\n"; + print TMP " Option \"SendCoreEvents\" \"on\"\n"; + print TMP "EndSection\n"; + } + close(TMP); + + # We do not overwrite existing "$cfg.vbox" files in order to keep a + # record of what the configuration looked like before the very first + # installation of the additions. + copy $cfg, "$cfg.bak"; + if (! -e "$cfg.vbox") { + rename $cfg, "$cfg.vbox"; + } + copy $temp, $cfg + or &do_fail("Could not overwrite configuration file $cfg! Exiting..."); + unlink $temp; + + $config_count++; + } +} + +# Warn if we did not find any configuration files +$config_count != 0 or die "Could not find any X11 configuration files"; + diff --git a/src/VBox/Additions/x11/Installer/x11restore.pl b/src/VBox/Additions/x11/Installer/x11restore.pl new file mode 100755 index 00000000..f1c485fc --- /dev/null +++ b/src/VBox/Additions/x11/Installer/x11restore.pl @@ -0,0 +1,79 @@ +#!/usr/bin/perl -w +# $Id: x11restore.pl $ +## @file +# Restore xorg.conf while removing Guest Additions. +# + +# +# Copyright (C) 2008-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + + +my $os_type=`uname -s`; +my @cfg_files = ("/etc/X11/xorg.conf-4", "/etc/X11/xorg.conf", "/etc/X11/.xorg.conf", "/etc/xorg.conf", + "/usr/etc/X11/xorg.conf-4", "/usr/etc/X11/xorg.conf", "/usr/lib/X11/xorg.conf-4", + "/usr/lib/X11/xorg.conf", "/etc/X11/XF86Config-4", "/etc/X11/XF86Config", + "/etc/XF86Config", "/usr/X11R6/etc/X11/XF86Config-4", "/usr/X11R6/etc/X11/XF86Config", + "/usr/X11R6/lib/X11/XF86Config-4", "/usr/X11R6/lib/X11/XF86Config"); +my $CFG; +my $BAK; + +my $config_count = 0; +my $vboxpresent = "vboxvideo"; + +foreach $cfg (@cfg_files) +{ + if (($os_type =~ 'SunOS') && (defined $ENV{PKG_INSTALL_ROOT})) + { + $cfg = $ENV{PKG_INSTALL_ROOT}.$cfg; + } + if (open(CFG, $cfg)) + { + @array=<CFG>; + close(CFG); + + foreach $line (@array) + { + if ($line =~ /$vboxpresent/) + { + if (open(BAK, $cfg.".bak")) + { + close(BAK); + print("Restoring $cfg.back to $cfg.\n"); + rename $cfg.".bak", $cfg; + } + else + { + # On Solaris just delete existing conf if backup is not found (Possible on distros like Indiana) + if ($os_type =~ 'SunOS') + { + unlink $cfg + } + else + { + die "Failed to restore xorg.conf! Your existing config. still uses VirtualBox drivers!!"; + } + } + } + } + $config_count++; + } +} diff --git a/src/VBox/Additions/x11/Makefile.kmk b/src/VBox/Additions/x11/Makefile.kmk new file mode 100644 index 00000000..2e8619d8 --- /dev/null +++ b/src/VBox/Additions/x11/Makefile.kmk @@ -0,0 +1,43 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the X11 Guest Additions. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefiles. +if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) + include $(PATH_SUB_CURRENT)/VBoxClient/Makefile.kmk + ifndef VBOX_NO_LEGACY_XORG_X11 + include $(PATH_SUB_CURRENT)/vboxvideo/Makefile.kmk + ifn1of ($(KBUILD_TARGET), netbsd solaris) + include $(PATH_SUB_CURRENT)/vboxmouse/Makefile.kmk + endif + endif +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/x11/VBoxClient/Makefile.kmk b/src/VBox/Additions/x11/VBoxClient/Makefile.kmk new file mode 100644 index 00000000..1687b59b --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/Makefile.kmk @@ -0,0 +1,236 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the VirtualBox Guest Addition X11 Client. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Use header files from our tree for randr and xinerama. and don't link but rather dlopen libXrand +# This is mostly because the GA build boxes can have a very old xrandr lib, so instead of linking we dlopen. +VBOX_WITH_DISTRO_XRAND_XINERAMA = + +# We don't yet have a seamless mode compilation flag, so define it here unconditionally. +VBOX_WITH_SEAMLESS := 1 + +# +# VBoxClient - clipboard and seamless. +# +PROGRAMS += VBoxClient +# +# Please make sure that you grep the source tree and modify all occurences accordingly +# if you rename the following program name. +# +PROGRAMS.linux += VBoxDRMClient + +VBoxClient_TEMPLATE = VBoxGuestR3Exe +VBoxClient_DEFS += VBOX_X11_CLIPBOARD VBOX_WITH_HGCM +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + VBoxClient_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)" +else + VBoxClient_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\" +endif +ifdef VBOX_WITH_DBUS + VBoxClient_DEFS += VBOX_WITH_DBUS +endif + +VBoxClient_DEFS.linux += _GNU_SOURCE +VBoxClient_INCS = $(VBOX_GRAPHICS_INCS) +VBoxClient_INCS += ../x11include/panoramiXproto-1.1 +VBoxClient_INCS += ../x11include/libXrandr-1.5.2 +VBoxClient_INCS += ../x11include/randrproto-1.5.0 +VBoxClient_SOURCES = \ + main.cpp \ + logging.cpp + +VBoxDRMClient_TEMPLATE = VBoxGuestR3Exe +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + VBoxDRMClient_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)" +else + VBoxDRMClient_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\" +endif +VBoxDRMClient_SOURCES = \ + display-drm.cpp \ + display-ipc.cpp \ + logging.cpp + + +VBoxClient_SOURCES.linux = \ + chk_stubs.c +VBoxClient_LIBPATH = \ + $(VBOX_LIBPATH32_X11) +VBoxClient_LIBS.freebsd = \ + iconv +VBoxClient_LIBS.linux = \ + dl +VBoxClient_LIBS.netbsd = \ + crypt +VBoxClient_LIBS.solaris = \ + dl +VBoxClient_LIBS = \ + X11 Xt Xext Xmu +ifdef VBOX_WITH_DISTRO_XRAND_XINERAMA + VBoxClient_DEFS += WITH_DISTRO_XRAND_XINERAMA + VBoxClient_LIBS += Xrandr +endif + +# XXX: -L comes from the template, but not rpath +VBoxClient_LDFLAGS.netbsd = \ + -Wl,-rpath /usr/X11R7/lib + +ifdef VBOX_WITH_DRAG_AND_DROP + ifdef VBOX_DND_WITH_XTEST + VBoxClient_DEFS += VBOX_DND_WITH_XTEST + VBoxClient_LIBS += \ + Xtst + endif +endif + +# This forces the memcpy references in the static libraries to go to +# __wrap_memcpy, which we can wrap around memcpy@GLIBC_2.2.5. I do not know +# how else to do that without recompiling or implementing our own memcpy. +ifeq ($(KBUILD_TARGET),linux) + VBoxClient_LDFLAGS.amd64 += \ + -Wl,--wrap=memcpy +endif + +ifdef VBOX_WITH_GUEST_PROPS + VBoxClient_DEFS += VBOX_WITH_GUEST_PROPS + VBoxClient_SOURCES += \ + hostversion.cpp + VBoxDRMClient_DEFS += VBOX_WITH_GUEST_PROPS +endif + +ifdef VBOX_WITH_DRAG_AND_DROP + VBoxClient_DEFS += \ + VBOX_WITH_DRAG_AND_DROP \ + $(if $(VBOX_WITH_DRAG_AND_DROP_GH),VBOX_WITH_DRAG_AND_DROP_GH,) + VBoxClient_SOURCES += \ + draganddrop.cpp + VBoxClient_LIBS += \ + $(VBOX_LIB_VBGL_R3) \ + $(PATH_STAGE_LIB)/additions/VBoxDnDGuestR3Lib$(VBOX_SUFF_LIB) +endif + +ifdef VBOX_WITH_SEAMLESS + VBoxClient_DEFS += VBOX_WITH_SEAMLESS + VBoxClient_SOURCES += \ + seamless.cpp \ + seamless-x11.cpp +endif + +ifdef VBOX_WITH_VMSVGA + VBoxClient_DEFS += VBOX_WITH_VMSVGA + VBoxClient_SOURCES += \ + display.cpp \ + display-svga-x11.cpp \ + display-svga-xf86cvt.cpp + VBoxClient_SOURCES.linux += \ + display-svga-session.cpp \ + display-ipc.cpp \ + display-helper-gnome3.cpp \ + display-helper-generic.cpp + + ### include $(PATH_SUB_CURRENT)/helpers/Makefile.kmk +endif + +ifdef VBOX_WITH_SHARED_CLIPBOARD + VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD + VBoxClient_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \ + clipboard.cpp + ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS VBOX_WITH_SHARED_CLIPBOARD_GUEST + VBoxClient_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardPath.cpp + ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + VBoxClient_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp + endif + ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE + VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD_FUSE + # @todo Make this dynamic loading more generic. + VBoxClient_SOURCES += \ + $(PATH_ROOT)/src/VBox/ImageMounter/vboximg-mount/fuse.cpp \ + clipboard-fuse.cpp + # @todo Ditto. + VBoxClient_INCS += \ + $(PATH_ROOT)/src/VBox/ImageMounter/vboximg-mount + endif + endif +endif + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) + if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) + + # Set this in LocalConfig.kmk if you are working on the X11 clipboard service + # to automatically run the unit test at build time. + # OTHERS += $(tstSeamlessX11-auto_0_OUTDIR)/tstSeamlessX11-auto.run + + PROGRAMS += tstSeamlessX11-auto + tstSeamlessX11-auto_TEMPLATE = VBoxR3TstExe + tstSeamlessX11-auto_SOURCES = \ + testcase/tstSeamlessX11-auto.cpp \ + seamless-x11.cpp + tstSeamlessX11-auto_DEFS = TESTCASE + tstSeamlessX11-auto_LIBS = \ + $(LIB_RUNTIME) + + TESTING += $(tstSeamlessX11-auto_0_OUTDIR)/tstSeamlessX11-auto + $$(tstSeamlessX11-auto_0_OUTDIR)/tstSeamlessX11-auto.run: \ + $$(tstSeamlessX11-auto_1_STAGE_TARGET) + export VBOX_LOG_DEST=nofile; $(tstSeamlessX11-auto_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + # + # Additional testcase designed to be run manually, which initiates and starts the Linux + # guest client part of the seamless additions in the host, faking seamless events from + # the host and writing information about guest events to standard output. + # + PROGRAMS += tstSeamlessX11 + tstSeamlessX11_TEMPLATE = VBoxR3TstExe + tstSeamlessX11_SOURCES = \ + testcase/tstSeamlessX11.cpp \ + seamless.cpp \ + seamless-x11.cpp + ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + tstSeamlessX11_DEFS += VBOX_BUILD_TARGET="$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)" + else + tstSeamlessX11_DEFS += VBOX_BUILD_TARGET=\"$(KBUILD_TARGET).$(KBUILD_TARGET_ARCH)\" + endif + tstSeamlessX11_LIBPATH = \ + $(VBOX_LIBPATH_X11) + tstSeamlessX11_LIBS = \ + $(LIB_RUNTIME) \ + Xext \ + Xmu \ + X11 + endif +endif + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/Additions/x11/VBoxClient/VBoxClient.h b/src/VBox/Additions/x11/VBoxClient/VBoxClient.h new file mode 100644 index 00000000..2bbf88bf --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/VBoxClient.h @@ -0,0 +1,148 @@ +/* $Id: VBoxClient.h $ */ +/** @file + * + * VirtualBox additions user session daemon. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_x11_VBoxClient_VBoxClient_h +#define GA_INCLUDED_SRC_x11_VBoxClient_VBoxClient_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/log.h> +#include <iprt/cpp/utils.h> +#include <iprt/string.h> + +/** Environment variable which is exported when in Wayland Desktop Environment. */ +#define VBCL_ENV_WAYLAND_DISPLAY "WAYLAND_DISPLAY" +/** Environment variable which contains information about currently running Desktop Environment. */ +#define VBCL_ENV_XDG_CURRENT_DESKTOP "XDG_CURRENT_DESKTOP" +/** Environment variable which contains information about currently running session (X11, Wayland, etc). */ +#define VBCL_ENV_XDG_SESSION_TYPE "XDG_SESSION_TYPE" + +int VBClShowNotify(const char *pszHeader, const char *pszBody); + +void VBClLogInfo(const char *pszFormat, ...); +void VBClLogError(const char *pszFormat, ...); +void VBClLogFatalError(const char *pszFormat, ...); +void VBClLogVerbose(unsigned iLevel, const char *pszFormat, ...); + +int VBClLogCreate(const char *pszLogFile); +void VBClLogSetLogPrefix(const char *pszPrefix); +void VBClLogDestroy(void); + +/** + * Detect if user is running on Wayland by checking corresponding environment variable. + * + * @returns True if Wayland has been detected, False otherwise. + */ +extern bool VBClHasWayland(void); + +/** Call clean-up for the current service and exit. */ +extern void VBClShutdown(bool fExit = true); + +/** + * A service descriptor. + */ +typedef struct +{ + /** The short service name. 16 chars maximum (RTTHREAD_NAME_LEN). */ + const char *pszName; + /** The longer service name. */ + const char *pszDesc; + /** Get the services default path to pidfile, relative to $HOME */ + /** @todo Should this also have a component relative to the X server number? + */ + const char *pszPidFilePathTemplate; + /** The usage options stuff for the --help screen. */ + const char *pszUsage; + /** The option descriptions for the --help screen. */ + const char *pszOptions; + + /** + * Tries to parse the given command line option. + * + * @returns 0 if we parsed, -1 if it didn't and anything else means exit. + * @param ppszShort If not NULL it points to the short option iterator. a short argument. + * If NULL examine argv[*pi]. + * @param argc The argument count. + * @param argv The argument vector. + * @param pi The argument vector index. Update if any value(s) are eaten. + */ + DECLCALLBACKMEMBER(int, pfnOption,(const char **ppszShort, int argc, char **argv, int *pi)); + + /** + * Called before parsing arguments. + * @returns VBox status code, or + * VERR_NOT_AVAILABLE if service is supported on this platform in general but not available at the moment. + * VERR_NOT_SUPPORTED if service is not supported on this platform. */ + DECLCALLBACKMEMBER(int, pfnInit,(void)); + + /** Called from the worker thread. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if exitting because *pfShutdown was set. + * @param pfShutdown Pointer to a per service termination flag to check + * before and after blocking. + */ + DECLCALLBACKMEMBER(int, pfnWorker,(bool volatile *pfShutdown)); + + /** + * Asks the service to stop. + * + * @remarks Will be called from the signal handler. + */ + DECLCALLBACKMEMBER(void, pfnStop,(void)); + + /** + * Does termination cleanups. + * + * @remarks This will be called even if pfnInit hasn't been called or pfnStop failed! + */ + DECLCALLBACKMEMBER(int, pfnTerm,(void)); +} VBCLSERVICE; +/** Pointer to a VBCLSERVICE. */ +typedef VBCLSERVICE *PVBCLSERVICE; +/** Pointer to a const VBCLSERVICE. */ +typedef VBCLSERVICE const *PCVBCLSERVICE; + +RT_C_DECLS_BEGIN +extern VBCLSERVICE g_SvcClipboard; +extern VBCLSERVICE g_SvcDisplayDRM; +extern VBCLSERVICE g_SvcDisplaySVGA; +extern VBCLSERVICE g_SvcDisplayLegacy; +# ifdef RT_OS_LINUX +extern VBCLSERVICE g_SvcDisplaySVGASession; +# endif +extern VBCLSERVICE g_SvcDragAndDrop; +extern VBCLSERVICE g_SvcHostVersion; +extern VBCLSERVICE g_SvcSeamless; + +extern unsigned g_cVerbosity; +extern bool g_fDaemonized; +RT_C_DECLS_END + +#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_VBoxClient_h */ diff --git a/src/VBox/Additions/x11/VBoxClient/chk_stubs.c b/src/VBox/Additions/x11/VBoxClient/chk_stubs.c new file mode 100644 index 00000000..9b5093eb --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/chk_stubs.c @@ -0,0 +1,69 @@ +/* $Id: chk_stubs.c $ */ +/** @file + * glibc stubs for the VirtualBox Guest Addition X11 Client. + */ + +/* + * Copyright (C) 2018-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/* If we want the binary to be usable with glibc 2.3, we have to prevent + VBoxClient from containing later symbols. This includes resolution of + symbols from supc++ and gcc_eh. */ + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> + +extern int __sprintf_chk(char *psz, int fFlags, size_t cb, const char *pszFormat, ...); +int __sprintf_chk(char *psz, int fFlags, size_t cb, const char *pszFormat, ...) +{ + int rc; + va_list va; + + (void)fFlags; + va_start(va, pszFormat); + rc = vsnprintf(psz, cb, pszFormat, va); + va_end(va); + return rc; +} + +extern void __stack_chk_fail(void); +void __stack_chk_fail(void) +{ + fprintf(stderr, "Stack check failed!\n"); + _exit(1); +} + +#ifdef __x86_64 +/* Furthermore, wrap references to memcpy to force them to go to the right + * version. We are forced to do it this way because the shared libraries + * supc++ and gcc_eh contain references which we cannot change. */ + +extern void *__wrap_memcpy(void *dest, const void *src, size_t n); + +asm (".symver memcpy, memcpy@GLIBC_2.2.5"); +void *__wrap_memcpy(void *dest, const void *src, size_t n) +{ + return memcpy(dest, src, n); +} +#endif diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp new file mode 100644 index 00000000..13b1fbc2 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp @@ -0,0 +1,440 @@ +/** $Id: clipboard.cpp $ */ +/** @file + * Guest Additions - X11 Shared Clipboard. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/alloc.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE +# include <iprt/dir.h> +#endif +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/semaphore.h> + +#include <VBox/log.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/HostServices/VBoxClipboardSvc.h> +#include <VBox/GuestHost/SharedClipboard.h> +#include <VBox/GuestHost/SharedClipboard-x11.h> + +#include "VBoxClient.h" + +#include "clipboard.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE +# include "clipboard-fuse.h" +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Only one context is supported at a time for now. */ +SHCLCONTEXT g_Ctx; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE +SHCLFUSECTX g_FuseCtx; +#endif + + +static DECLCALLBACK(int) vbclOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, + SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser) +{ + RT_NOREF(pvUser); + + LogFlowFunc(("pCtx=%p, uFmt=%#x\n", pCtx, uFmt)); + + int rc = VINF_SUCCESS; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (uFmt == VBOX_SHCL_FMT_URI_LIST) + { + //rc = VbglR3ClipboardRootListRead() + rc = VERR_NO_DATA; + } + else +#endif + { + uint32_t cbRead = 0; + + uint32_t cbData = _4K; /** @todo Make this dynamic. */ + void *pvData = RTMemAlloc(cbData); + if (pvData) + { + rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead); + } + else + rc = VERR_NO_MEMORY; + + /* + * A return value of VINF_BUFFER_OVERFLOW tells us to try again with a + * larger buffer. The size of the buffer needed is placed in *pcb. + * So we start all over again. + */ + if (rc == VINF_BUFFER_OVERFLOW) + { + /* cbRead contains the size required. */ + + cbData = cbRead; + pvData = RTMemRealloc(pvData, cbRead); + if (pvData) + { + rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, uFmt, pvData, cbData, &cbRead); + if (rc == VINF_BUFFER_OVERFLOW) + rc = VERR_BUFFER_OVERFLOW; + } + else + rc = VERR_NO_MEMORY; + } + + if (!cbRead) + rc = VERR_NO_DATA; + + if (RT_SUCCESS(rc)) + { + *pcb = cbRead; /* Actual bytes read. */ + *ppv = pvData; + } + else + { + /* + * Catch other errors. This also catches the case in which the buffer was + * too small a second time, possibly because the clipboard contents + * changed half-way through the operation. Since we can't say whether or + * not this is actually an error, we just return size 0. + */ + RTMemFree(pvData); + } + } + + if (RT_FAILURE(rc)) + LogRel(("Requesting data in format %#x from host failed with %Rrc\n", uFmt, rc)); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Opaque data structure describing a request from the host for clipboard + * data, passed in when the request is forwarded to the X11 backend so that + * it can be completed correctly. + */ +struct CLIPREADCBREQ +{ + /** The data format that was requested. */ + SHCLFORMAT uFmt; +}; + +static DECLCALLBACK(int) vbclReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser) +{ + RT_NOREF(pvUser); + + LogFlowFunc(("fFormats=%#x\n", fFormats)); + + int rc = VbglR3ClipboardReportFormats(pCtx->CmdCtx.idClient, fFormats); + LogFlowFuncLeaveRC(rc); + + return rc; +} + +static DECLCALLBACK(int) vbclOnSendDataToDestCallback(PSHCLCONTEXT pCtx, void *pv, uint32_t cb, void *pvUser) +{ + PSHCLX11READDATAREQ pData = (PSHCLX11READDATAREQ)pvUser; + AssertPtrReturn(pData, VERR_INVALID_POINTER); + + LogFlowFunc(("rcCompletion=%Rrc, Format=0x%x, pv=%p, cb=%RU32\n", pData->rcCompletion, pData->pReq->uFmt, pv, cb)); + + Assert((cb == 0 && pv == NULL) || (cb != 0 && pv != NULL)); + pData->rcCompletion = VbglR3ClipboardWriteDataEx(&pCtx->CmdCtx, pData->pReq->uFmt, pv, cb); + + RTMemFree(pData->pReq); + + LogFlowFuncLeaveRC(pData->rcCompletion); + + return VINF_SUCCESS; +} + +/** + * Connect the guest clipboard to the host. + * + * @returns VBox status code. + */ +static int vboxClipboardConnect(void) +{ + LogFlowFuncEnter(); + + SHCLCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Callbacks.pfnReportFormats = vbclReportFormatsCallback; + Callbacks.pfnOnRequestDataFromSource = vbclOnRequestDataFromSourceCallback; + Callbacks.pfnOnSendDataToDest = vbclOnSendDataToDestCallback; + + int rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &g_Ctx, false /* fHeadless */); + if (RT_SUCCESS(rc)) + { + rc = ShClX11ThreadStart(&g_Ctx.X11, false /* grab */); + if (RT_SUCCESS(rc)) + { + rc = VbglR3ClipboardConnectEx(&g_Ctx.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID); + if (RT_FAILURE(rc)) + ShClX11ThreadStop(&g_Ctx.X11); + } + } + else + rc = VERR_NO_MEMORY; + + if (RT_FAILURE(rc)) + { + VBClLogError("Error connecting to host service, rc=%Rrc\n", rc); + + VbglR3ClipboardDisconnectEx(&g_Ctx.CmdCtx); + ShClX11Destroy(&g_Ctx.X11); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * The main loop of our clipboard reader. + */ +int vboxClipboardMain(void) +{ + int rc; + + PSHCLCONTEXT pCtx = &g_Ctx; + + bool fShutdown = false; + + /* The thread waits for incoming messages from the host. */ + for (;;) + { + PVBGLR3CLIPBOARDEVENT pEvent = (PVBGLR3CLIPBOARDEVENT)RTMemAllocZ(sizeof(VBGLR3CLIPBOARDEVENT)); + AssertPtrBreakStmt(pEvent, rc = VERR_NO_MEMORY); + + LogFlowFunc(("Waiting for host message (fUseLegacyProtocol=%RTbool, fHostFeatures=%#RX64) ...\n", + pCtx->CmdCtx.fUseLegacyProtocol, pCtx->CmdCtx.fHostFeatures)); + + uint32_t idMsg = 0; + uint32_t cParms = 0; + rc = VbglR3ClipboardMsgPeekWait(&pCtx->CmdCtx, &idMsg, &cParms, NULL /* pidRestoreCheck */); + if (RT_SUCCESS(rc)) + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + rc = VbglR3ClipboardEventGetNextEx(idMsg, cParms, &pCtx->CmdCtx, &pCtx->TransferCtx, pEvent); +#else + rc = VbglR3ClipboardEventGetNext(idMsg, cParms, &pCtx->CmdCtx, pEvent); +#endif + } + + if (RT_FAILURE(rc)) + { + LogFlowFunc(("Getting next event failed with %Rrc\n", rc)); + + VbglR3ClipboardEventFree(pEvent); + pEvent = NULL; + + if (fShutdown) + break; + + /* Wait a bit before retrying. */ + RTThreadSleep(1000); + continue; + } + else + { + AssertPtr(pEvent); + LogFlowFunc(("Event uType=%RU32\n", pEvent->enmType)); + + switch (pEvent->enmType) + { + case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS: + { + ShClX11ReportFormatsToX11(&g_Ctx.X11, pEvent->u.fReportedFormats); + break; + } + + case VBGLR3CLIPBOARDEVENTTYPE_READ_DATA: + { + /* The host needs data in the specified format. */ + CLIPREADCBREQ *pReq; + pReq = (CLIPREADCBREQ *)RTMemAllocZ(sizeof(CLIPREADCBREQ)); + if (pReq) + { + pReq->uFmt = pEvent->u.fReadData; + ShClX11ReadDataFromX11(&g_Ctx.X11, pReq->uFmt, pReq); + } + else + rc = VERR_NO_MEMORY; + break; + } + + case VBGLR3CLIPBOARDEVENTTYPE_QUIT: + { + VBClLogVerbose(2, "Host requested termination\n"); + fShutdown = true; + break; + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBGLR3CLIPBOARDEVENTTYPE_TRANSFER_STATUS: + { + /* Nothing to do here. */ + rc = VINF_SUCCESS; + break; + } +#endif + case VBGLR3CLIPBOARDEVENTTYPE_NONE: + { + /* Nothing to do here. */ + rc = VINF_SUCCESS; + break; + } + + default: + { + AssertMsgFailedBreakStmt(("Event type %RU32 not implemented\n", pEvent->enmType), rc = VERR_NOT_SUPPORTED); + } + } + + if (pEvent) + { + VbglR3ClipboardEventFree(pEvent); + pEvent = NULL; + } + } + + if (fShutdown) + break; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vbclShClInit(void) +{ + int rc; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + rc = ShClTransferCtxInit(&g_Ctx.TransferCtx); +#else + rc = VINF_SUCCESS; +#endif + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vbclShClWorker(bool volatile *pfShutdown) +{ + RT_NOREF(pfShutdown); + + /* Initialise the guest library. */ + int rc = vboxClipboardConnect(); + if (RT_SUCCESS(rc)) + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE + rc = VbClShClFUSEInit(&g_FuseCtx, &g_Ctx); + if (RT_SUCCESS(rc)) + { + rc = VbClShClFUSEStart(&g_FuseCtx); + if (RT_SUCCESS(rc)) + { +#endif + /* Let the main thread know that it can continue spawning services. */ + RTThreadUserSignal(RTThreadSelf()); + + rc = vboxClipboardMain(); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_FUSE + int rc2 = VbClShClFUSEStop(&g_FuseCtx); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } +#endif + } + + if (RT_FAILURE(rc)) + VBClLogError("Service terminated abnormally with %Rrc\n", rc); + + if (rc == VERR_HGCM_SERVICE_NOT_FOUND) + rc = VINF_SUCCESS; /* Prevent automatic restart by daemon script if host service not available. */ + + return rc; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vbclShClStop(void) +{ + /* Disconnect from the host service. + * This will also send a VBOX_SHCL_HOST_MSG_QUIT from the host so that we can break out from our message worker. */ + VbglR3ClipboardDisconnect(g_Ctx.CmdCtx.idClient); + g_Ctx.CmdCtx.idClient = 0; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnTerm} + */ +static DECLCALLBACK(int) vbclShClTerm(void) +{ +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ShClTransferCtxDestroy(&g_Ctx.TransferCtx); +#endif + + return VINF_SUCCESS; +} + +VBCLSERVICE g_SvcClipboard = +{ + "shcl", /* szName */ + "Shared Clipboard", /* pszDescription */ + ".vboxclient-clipboard", /* pszPidFilePathTemplate */ + NULL, /* pszUsage */ + NULL, /* pszOptions */ + NULL, /* pfnOption */ + vbclShClInit, /* pfnInit */ + vbclShClWorker, /* pfnWorker */ + vbclShClStop, /* pfnStop*/ + vbclShClTerm /* pfnTerm */ +}; + diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard.h b/src/VBox/Additions/x11/VBoxClient/clipboard.h new file mode 100644 index 00000000..bc98487b --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/clipboard.h @@ -0,0 +1,49 @@ +/** $Id: clipboard.h $ */ +/** @file + * Guest Additions - X11 Shared Clipboard - Main header. + */ + +/* + * Copyright (C) 2020-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_x11_VBoxClient_clipboard_h +#define GA_INCLUDED_SRC_x11_VBoxClient_clipboard_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/** + * Struct keeping a Shared Clipboard context. + */ +struct SHCLCONTEXT +{ + /** Client command context */ + VBGLR3SHCLCMDCTX CmdCtx; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Associated transfer data. */ + SHCLTRANSFERCTX TransferCtx; +#endif + /** X11 clipboard context. */ + SHCLX11CTX X11; +}; + +#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_clipboard_h */ diff --git a/src/VBox/Additions/x11/VBoxClient/display-drm.cpp b/src/VBox/Additions/x11/VBoxClient/display-drm.cpp new file mode 100644 index 00000000..fa5d56d2 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-drm.cpp @@ -0,0 +1,1369 @@ +/* $Id: display-drm.cpp $ */ +/** @file + * Guest Additions - VMSVGA guest screen resize service. + * + * A user space daemon which communicates with VirtualBox host interface + * and performs VMSVGA-specific guest screen resize and communicates with + * Desktop Environment helper daemon over IPC. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_vboxdrmcliet VBoxDRMClient - The VMSVGA Guest Screen Resize Service + * + * The VMSVGA Guest Screen Resize Service is a service which communicates with a + * guest VMSVGA driver and triggers it to perform screen resize on a guest side. + * + * This service supposed to be started on early boot. On start it will try to find + * compatible VMSVGA graphics card and terminate immediately if not found. + * VMSVGA functionality implemented here is only supported starting from vmgfx + * driver version 2.10 which was introduced in Linux kernel 4.6. When compatible + * graphics card is found, service will start a worker loop in order to receive screen + * update data from host and apply it to local DRM stack. + * + * In addition, it will start a local IPC server in order to communicate with Desktop + * Environment specific service(s). Currently, it will propagate to IPC client information regarding to + * which display should be set as primary on Desktop Environment level. As well as + * receive screen layout change events obtained on Desktop Environment level and send it + * back to host, so host and guest will have the same screen layout representation. + * + * By default, access to IPC server socket is granted to all users. It can be restricted to + * only root and users from group 'vboxdrmipc' if '/VirtualBox/GuestAdd/DRMIpcRestricted' guest + * property is set and READ-ONLY for guest. User group 'vboxdrmipc' is created during Guest + * Additions installation. If this group is removed (or not found due to any reason) prior to + * service start, access to IPC server socket will be granted to root only regardless + * if '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property is set or not. If guest property + * is set, but is not READ-ONLY for guest, property is ignored and IPC socket access is granted + * to all users. + * + * Logging is implemented in a way that errors are always printed out, VBClLogVerbose(1) and + * VBClLogVerbose(2) are used for debugging purposes. Verbosity level 1 is for messages related + * to service itself (excluding IPC), level 2 is for IPC communication debugging. In order to see + * logging on a host side it is enough to do: + * + * echo 1 > /sys/module/vboxguest/parameters/r3_log_to_host. + * + * + * Service is running the following threads: + * + * DrmResizeThread - this thread listens for display layout update events from host. + * Once event is received, it either injects new screen layout data into DRM stack, + * and/or asks IPC client(s) to set primary display. This thread is accessing IPC + * client connection list when it needs to sent new primary display data to all the + * connected clients. + * + * DrmIpcSRV - this thread is a main loop for IPC server. It accepts new connection(s), + * authenticates it and starts new client thread IpcCLT-XXX for processing client + * requests. This thread is accessing IPC client connection list by adding a new + * connection data into it. + * + * IpcCLT-%u - this thread processes all the client data. Suffix '-%u' in thread name is PID + * of a remote client process. Typical name for client thread would be IpcCLT-1234. This + * thread is accessing IPC client connection list when it removes connection data from it + * when actual IPC connection is closed. Due to IPRT thread name limitation, actual thread + * name will be cropped by 15 characters. + * + * + * The following locks are utilized: + * + * #g_ipcClientConnectionsListCritSect - protects access to list of IPC client connections. + * It is used by each thread - DrmResizeThread, DrmIpcSRV and IpcCLT-XXX. + * + * #g_monitorPositionsCritSect - protects access to display layout data cache and vmwgfx driver + * handle, serializes access to host interface and vmwgfx driver handle between + * DrmResizeThread and IpcCLT-%u. + */ + +#include "VBoxClient.h" +#include "display-ipc.h" + +#include <VBox/VBoxGuestLib.h> +#include <VBox/HostServices/GuestPropertySvc.h> + +#include <iprt/getopt.h> +#include <iprt/assert.h> +#include <iprt/file.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/thread.h> +#include <iprt/asm.h> +#include <iprt/localipc.h> + +#include <unistd.h> +#include <stdio.h> +#include <limits.h> +#include <signal.h> +#include <grp.h> +#include <errno.h> + +#ifdef RT_OS_LINUX +# include <sys/ioctl.h> +#else /* Solaris and BSDs, in case they ever adopt the DRM driver. */ +# include <sys/ioccom.h> +#endif + +/** Ioctl command to query vmwgfx version information. */ +#define DRM_IOCTL_VERSION _IOWR('d', 0x00, struct DRMVERSION) +/** Ioctl command to set new screen layout. */ +#define DRM_IOCTL_VMW_UPDATE_LAYOUT _IOW('d', 0x40 + 20, struct DRMVMWUPDATELAYOUT) +/** A driver name which identifies VMWare driver. */ +#define DRM_DRIVER_NAME "vmwgfx" +/** VMWare driver compatible version number. On previous versions resizing does not seem work. */ +#define DRM_DRIVER_VERSION_MAJOR_MIN (2) +#define DRM_DRIVER_VERSION_MINOR_MIN (10) + +/** VMWare char device driver minor numbers range. */ +#define VMW_CONTROL_DEVICE_MINOR_START (64) +#define VMW_RENDER_DEVICE_MINOR_START (128) +#define VMW_RENDER_DEVICE_MINOR_END (192) + +/** Name of DRM resize thread. */ +#define DRM_RESIZE_THREAD_NAME "DrmResizeThread" + +/** Name of DRM IPC server thread. */ +#define DRM_IPC_SERVER_THREAD_NAME "DrmIpcSRV" +/** Maximum length of thread name. */ +#define DRM_IPC_THREAD_NAME_MAX (16) +/** Name pattern of DRM IPC client thread. */ +#define DRM_IPC_CLIENT_THREAD_NAME_PTR "IpcCLT-%u" +/** Maximum number of simultaneous IPC client connections. */ +#define DRM_IPC_SERVER_CONNECTIONS_MAX (16) + +/** IPC client connections counter. */ +static volatile uint32_t g_cDrmIpcConnections = 0; +/* A flag which indicates whether access to IPC socket should be restricted. + * This flag caches '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property + * in order to prevent its retrieving from the host side each time a new IPC + * client connects to server. This flag is updated each time when property is + * changed on the host side. */ +static volatile bool g_fDrmIpcRestricted; + +/** Global handle to vmwgfx file descriptor (protected by #g_monitorPositionsCritSect). */ +static RTFILE g_hDevice = NIL_RTFILE; + +/** DRM version structure. */ +struct DRMVERSION +{ + int cMajor; + int cMinor; + int cPatchLevel; + size_t cbName; + char *pszName; + size_t cbDate; + char *pszDate; + size_t cbDescription; + char *pszDescription; +}; +AssertCompileSize(struct DRMVERSION, 8 + 7 * sizeof(void *)); + +/** Preferred screen layout information for DRM_VMW_UPDATE_LAYOUT IoCtl. The + * rects argument is a cast pointer to an array of drm_vmw_rect. */ +struct DRMVMWUPDATELAYOUT +{ + uint32_t cOutputs; + uint32_t u32Pad; + uint64_t ptrRects; +}; +AssertCompileSize(struct DRMVMWUPDATELAYOUT, 16); + +/** A node of IPC client connections list. */ +typedef struct VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE +{ + /** The list node. */ + RTLISTNODE Node; + /** List node payload. */ + PVBOX_DRMIPC_CLIENT pClient; +} VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE; + +/* Pointer to VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE. */ +typedef VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE *PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE; + +/** IPC client connections list. */ +static VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE g_ipcClientConnectionsList; + +/** IPC client connections list critical section. */ +static RTCRITSECT g_ipcClientConnectionsListCritSect; + +/** Critical section used for reporting monitors position back to host. */ +static RTCRITSECT g_monitorPositionsCritSect; + +/** Counter of how often our daemon has been re-spawned. */ +unsigned g_cRespawn = 0; +/** Logging verbosity level. */ +unsigned g_cVerbosity = 0; + +/** Path to the PID file. */ +static const char *g_pszPidFile = "/var/run/VBoxDRMClient"; + +/** Global flag which is triggered when service requested to shutdown. */ +static bool volatile g_fShutdown; + +/** + * Go over all existing IPC client connection and put set-primary-screen request + * data into TX queue of each of them . + * + * @return IPRT status code. + * @param u32PrimaryDisplay Primary display ID. + */ +static int vbDrmIpcBroadcastPrimaryDisplay(uint32_t u32PrimaryDisplay); + +/** + * Attempts to open DRM device by given path and check if it is + * capable for screen resize. + * + * @return DRM device handle on success, NIL_RTFILE otherwise. + * @param szPathPattern Path name pattern to the DRM device. + * @param uInstance Driver / device instance. + */ +static RTFILE vbDrmTryDevice(const char *szPathPattern, uint8_t uInstance) +{ + int rc = VERR_NOT_FOUND; + char szPath[PATH_MAX]; + struct DRMVERSION vmwgfxVersion; + RTFILE hDevice = NIL_RTFILE; + + RT_ZERO(szPath); + RT_ZERO(vmwgfxVersion); + + rc = RTStrPrintf(szPath, sizeof(szPath), szPathPattern, uInstance); + if (RT_SUCCESS(rc)) + { + rc = RTFileOpen(&hDevice, szPath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + char szVmwgfxDriverName[sizeof(DRM_DRIVER_NAME)]; + RT_ZERO(szVmwgfxDriverName); + + vmwgfxVersion.cbName = sizeof(szVmwgfxDriverName); + vmwgfxVersion.pszName = szVmwgfxDriverName; + + /* Query driver version information and check if it can be used for screen resizing. */ + rc = RTFileIoCtl(hDevice, DRM_IOCTL_VERSION, &vmwgfxVersion, sizeof(vmwgfxVersion), NULL); + if ( RT_SUCCESS(rc) + && strncmp(szVmwgfxDriverName, DRM_DRIVER_NAME, sizeof(DRM_DRIVER_NAME) - 1) == 0 + && ( vmwgfxVersion.cMajor > DRM_DRIVER_VERSION_MAJOR_MIN + || ( vmwgfxVersion.cMajor == DRM_DRIVER_VERSION_MAJOR_MIN + && vmwgfxVersion.cMinor >= DRM_DRIVER_VERSION_MINOR_MIN))) + { + VBClLogInfo("found compatible device: %s\n", szPath); + } + else + { + RTFileClose(hDevice); + hDevice = NIL_RTFILE; + rc = VERR_NOT_FOUND; + } + } + } + else + { + VBClLogError("unable to construct path to DRM device: %Rrc\n", rc); + } + + return RT_SUCCESS(rc) ? hDevice : NIL_RTFILE; +} + +/** + * Attempts to find and open DRM device to be used for screen resize. + * + * @return DRM device handle on success, NIL_RTFILE otherwise. + */ +static RTFILE vbDrmOpenVmwgfx(void) +{ + /* Control devices for drm graphics driver control devices go from + * controlD64 to controlD127. Render node devices go from renderD128 + * to renderD192. The driver takes resize hints via the control device + * on pre-4.10 (???) kernels and on the render device on newer ones. + * At first, try to find control device and render one if not found. + */ + uint8_t i; + RTFILE hDevice = NIL_RTFILE; + + /* Lookup control device. */ + for (i = VMW_CONTROL_DEVICE_MINOR_START; i < VMW_RENDER_DEVICE_MINOR_START; i++) + { + hDevice = vbDrmTryDevice("/dev/dri/controlD%u", i); + if (hDevice != NIL_RTFILE) + return hDevice; + } + + /* Lookup render device. */ + for (i = VMW_RENDER_DEVICE_MINOR_START; i <= VMW_RENDER_DEVICE_MINOR_END; i++) + { + hDevice = vbDrmTryDevice("/dev/dri/renderD%u", i); + if (hDevice != NIL_RTFILE) + return hDevice; + } + + VBClLogError("unable to find DRM device\n"); + + return hDevice; +} + +/** + * This function converts input monitors layout array passed from DevVMM + * into monitors layout array to be passed to DRM stack. Last validation + * request is cached. + * + * @return VINF_SUCCESS on success, VERR_DUPLICATE if monitors layout was not changed, IPRT error code otherwise. + * @param aDisplaysIn Input displays array. + * @param cDisplaysIn Number of elements in input displays array. + * @param aDisplaysOut Output displays array. + * @param cDisplaysOutMax Number of elements in output displays array. + * @param pu32PrimaryDisplay ID of a display which marked as primary. + * @param pcActualDisplays Number of displays to report to DRM stack (number of enabled displays). + * @param fPartialLayout Whether aDisplaysIn array contains complete display layout information or not. + * When layout is reported by Desktop Environment helper, aDisplaysIn does not have + * idDisplay, fDisplayFlags and cBitsPerPixel data (guest has no info about them). + */ +static int vbDrmValidateLayout(VMMDevDisplayDef *aDisplaysIn, uint32_t cDisplaysIn, + struct VBOX_DRMIPC_VMWRECT *aDisplaysOut, uint32_t *pu32PrimaryDisplay, + uint32_t cDisplaysOutMax, uint32_t *pcActualDisplays, bool fPartialLayout) +{ + /* This array is a cache of what was received from DevVMM so far. + * DevVMM may send to us partial information bout scree layout. This + * cache remembers entire picture. */ + static struct VMMDevDisplayDef aVmMonitorsCache[VBOX_DRMIPC_MONITORS_MAX]; + /* Number of valid (enabled) displays in output array. */ + uint32_t cDisplaysOut = 0; + /* Flag indicates that current layout cache is consistent and can be passed to DRM stack. */ + bool fValid = true; + + /* Make sure input array fits cache size. */ + if (cDisplaysIn > VBOX_DRMIPC_MONITORS_MAX) + { + VBClLogError("unable to validate screen layout: input (%u) array does not fit to cache size (%u)\n", + cDisplaysIn, VBOX_DRMIPC_MONITORS_MAX); + return VERR_INVALID_PARAMETER; + } + + /* Make sure there is enough space in output array. */ + if (cDisplaysIn > cDisplaysOutMax) + { + VBClLogError("unable to validate screen layout: input array (%u) is bigger than output one (%u)\n", + cDisplaysIn, cDisplaysOut); + return VERR_INVALID_PARAMETER; + } + + /* Make sure input and output arrays are of non-zero size. */ + if (!(cDisplaysIn > 0 && cDisplaysOutMax > 0)) + { + VBClLogError("unable to validate screen layout: invalid size of either input (%u) or output display array\n", + cDisplaysIn, cDisplaysOutMax); + return VERR_INVALID_PARAMETER; + } + + /* Update cache. */ + for (uint32_t i = 0; i < cDisplaysIn; i++) + { + uint32_t idDisplay = !fPartialLayout ? aDisplaysIn[i].idDisplay : i; + if (idDisplay < VBOX_DRMIPC_MONITORS_MAX) + { + if (!fPartialLayout) + { + aVmMonitorsCache[idDisplay].idDisplay = idDisplay; + aVmMonitorsCache[idDisplay].fDisplayFlags = aDisplaysIn[i].fDisplayFlags; + aVmMonitorsCache[idDisplay].cBitsPerPixel = aDisplaysIn[i].cBitsPerPixel; + } + + aVmMonitorsCache[idDisplay].cx = aDisplaysIn[i].cx; + aVmMonitorsCache[idDisplay].cy = aDisplaysIn[i].cy; + aVmMonitorsCache[idDisplay].xOrigin = aDisplaysIn[i].xOrigin; + aVmMonitorsCache[idDisplay].yOrigin = aDisplaysIn[i].yOrigin; + } + else + { + VBClLogError("received display ID (0x%x, position %u) is invalid\n", idDisplay, i); + /* If monitor configuration cannot be placed into cache, consider entire cache is invalid. */ + fValid = false; + } + } + + /* Now, go though complete cache and check if it is valid. */ + for (uint32_t i = 0; i < VBOX_DRMIPC_MONITORS_MAX; i++) + { + if (i == 0) + { + if (aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED) + { + VBClLogError("unable to validate screen layout: first monitor is not allowed to be disabled\n"); + fValid = false; + } + else + cDisplaysOut++; + } + else + { + /* Check if there is no hole in between monitors (i.e., if current monitor is enabled, but previous one does not). */ + if ( !(aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED) + && aVmMonitorsCache[i - 1].fDisplayFlags & VMMDEV_DISPLAY_DISABLED) + { + VBClLogError("unable to validate screen layout: there is a hole in displays layout config, " + "monitor (%u) is ENABLED while (%u) does not\n", i, i - 1); + fValid = false; + } + else + { + /* Always align screens since unaligned layout will result in disaster. */ + aVmMonitorsCache[i].xOrigin = aVmMonitorsCache[i - 1].xOrigin + aVmMonitorsCache[i - 1].cx; + aVmMonitorsCache[i].yOrigin = aVmMonitorsCache[i - 1].yOrigin; + + /* Only count enabled monitors. */ + if (!(aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)) + cDisplaysOut++; + } + } + } + + /* Copy out layout data. */ + if (fValid) + { + /* Start with invalid display ID. */ + uint32_t u32PrimaryDisplay = VBOX_DRMIPC_MONITORS_MAX; + + for (uint32_t i = 0; i < cDisplaysOut; i++) + { + aDisplaysOut[i].x = aVmMonitorsCache[i].xOrigin; + aDisplaysOut[i].y = aVmMonitorsCache[i].yOrigin; + aDisplaysOut[i].w = aVmMonitorsCache[i].cx; + aDisplaysOut[i].h = aVmMonitorsCache[i].cy; + + if (aVmMonitorsCache[i].fDisplayFlags & VMMDEV_DISPLAY_PRIMARY) + { + /* Make sure display layout has only one primary display + * set (for display 0, host side sets primary flag, so exclude it). */ + Assert(u32PrimaryDisplay == 0 || u32PrimaryDisplay == VBOX_DRMIPC_MONITORS_MAX); + u32PrimaryDisplay = i; + } + + VBClLogVerbose(1, "update monitor %u parameters: %dx%d, (%d, %d)\n", + i, aDisplaysOut[i].w, aDisplaysOut[i].h, aDisplaysOut[i].x, aDisplaysOut[i].y); + } + + *pu32PrimaryDisplay = u32PrimaryDisplay; + *pcActualDisplays = cDisplaysOut; + } + + return (fValid && cDisplaysOut > 0) ? VINF_SUCCESS : VERR_INVALID_PARAMETER; +} + +/** + * This function sends screen layout data to DRM stack. + * + * Helper function for vbDrmPushScreenLayout(). Should be called + * under g_monitorPositionsCritSect lock. + * + * @return VINF_SUCCESS on success, IPRT error code otherwise. + * @param hDevice Handle to opened DRM device. + * @param paRects Array of screen configuration data. + * @param cRects Number of elements in screen configuration array. + */ +static int vbDrmSendHints(RTFILE hDevice, struct VBOX_DRMIPC_VMWRECT *paRects, uint32_t cRects) +{ + int rc = 0; + uid_t curuid; + + /* Store real user id. */ + curuid = getuid(); + + /* Change effective user id. */ + if (setreuid(0, 0) == 0) + { + struct DRMVMWUPDATELAYOUT ioctlLayout; + + RT_ZERO(ioctlLayout); + ioctlLayout.cOutputs = cRects; + ioctlLayout.ptrRects = (uint64_t)paRects; + + rc = RTFileIoCtl(hDevice, DRM_IOCTL_VMW_UPDATE_LAYOUT, + &ioctlLayout, sizeof(ioctlLayout), NULL); + + if (setreuid(curuid, 0) != 0) + { + VBClLogError("reset of setreuid failed after drm ioctl"); + rc = VERR_ACCESS_DENIED; + } + } + else + { + VBClLogError("setreuid failed during drm ioctl\n"); + rc = VERR_ACCESS_DENIED; + } + + return rc; +} + +/** + * This function converts vmwgfx monitors layout data into an array of monitor offsets + * and sends it back to the host in order to ensure that host and guest have the same + * monitors layout representation. + * + * @return IPRT status code. + * @param cDisplays Number of displays (elements in pDisplays). + * @param pDisplays Displays parameters as it was sent to vmwgfx driver. + */ +static int drmSendMonitorPositions(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *pDisplays) +{ + static RTPOINT aPositions[VBOX_DRMIPC_MONITORS_MAX]; + + if (!pDisplays || !cDisplays || cDisplays > VBOX_DRMIPC_MONITORS_MAX) + { + return VERR_INVALID_PARAMETER; + } + + /* Prepare monitor offsets list to be sent to the host. */ + for (uint32_t i = 0; i < cDisplays; i++) + { + aPositions[i].x = pDisplays[i].x; + aPositions[i].y = pDisplays[i].y; + } + + return VbglR3SeamlessSendMonitorPositions(cDisplays, aPositions); +} + +/** + * Validate and apply screen layout data. + * + * @return IPRT status code. + * @param aDisplaysIn An array with screen layout data. + * @param cDisplaysIn Number of elements in aDisplaysIn. + * @param fPartialLayout Whether aDisplaysIn array contains complete display layout information or not. + * When layout is reported by Desktop Environment helper, aDisplaysIn does not have + * idDisplay, fDisplayFlags and cBitsPerPixel data (guest has no info about them). + * @param fApply Whether to apply provided display layout data to the DRM stack or send display offsets only. + */ +static int vbDrmPushScreenLayout(VMMDevDisplayDef *aDisplaysIn, uint32_t cDisplaysIn, bool fPartialLayout, bool fApply) +{ + int rc; + + struct VBOX_DRMIPC_VMWRECT aDisplaysOut[VBOX_DRMIPC_MONITORS_MAX]; + uint32_t cDisplaysOut = 0; + + uint32_t u32PrimaryDisplay = VBOX_DRMIPC_MONITORS_MAX; + + rc = RTCritSectEnter(&g_monitorPositionsCritSect); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to lock monitor data cache, rc=%Rrc\n", rc); + return rc; + } + + static uint32_t u32PrimaryDisplayLast = VBOX_DRMIPC_MONITORS_MAX; + + RT_ZERO(aDisplaysOut); + + /* Validate displays layout and push it to DRM stack if valid. */ + rc = vbDrmValidateLayout(aDisplaysIn, cDisplaysIn, aDisplaysOut, &u32PrimaryDisplay, + sizeof(aDisplaysOut), &cDisplaysOut, fPartialLayout); + if (RT_SUCCESS(rc)) + { + if (fApply) + { + rc = vbDrmSendHints(g_hDevice, aDisplaysOut, cDisplaysOut); + VBClLogInfo("push screen layout data of %u display(s) to DRM stack, fPartialLayout=%RTbool, rc=%Rrc\n", + cDisplaysOut, fPartialLayout, rc); + } + + /* In addition, notify host that configuration was successfully applied to the guest vmwgfx driver. */ + if (RT_SUCCESS(rc)) + { + rc = drmSendMonitorPositions(cDisplaysOut, aDisplaysOut); + if (RT_FAILURE(rc)) + VBClLogError("cannot send host notification: %Rrc\n", rc); + + /* If information about primary display is present in display layout, send it to DE over IPC. */ + if (u32PrimaryDisplay != VBOX_DRMIPC_MONITORS_MAX + && u32PrimaryDisplayLast != u32PrimaryDisplay) + { + rc = vbDrmIpcBroadcastPrimaryDisplay(u32PrimaryDisplay); + + /* Cache last value in order to avoid sending duplicate data over IPC. */ + u32PrimaryDisplayLast = u32PrimaryDisplay; + + VBClLogVerbose(2, "DE was notified that display %u is now primary, rc=%Rrc\n", u32PrimaryDisplay, rc); + } + else + VBClLogVerbose(2, "do not notify DE second time that display %u is now primary, rc=%Rrc\n", u32PrimaryDisplay, rc); + } + } + else if (rc == VERR_DUPLICATE) + VBClLogVerbose(2, "do not notify DRM stack about monitors layout change twice, rc=%Rrc\n", rc); + else + VBClLogError("displays layout is invalid, will not notify guest driver, rc=%Rrc\n", rc); + + int rc2 = RTCritSectLeave(&g_monitorPositionsCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("unable to unlock monitor data cache, rc=%Rrc\n", rc); + + return rc; +} + +/** Worker thread for resize task. */ +static DECLCALLBACK(int) vbDrmResizeWorker(RTTHREAD ThreadSelf, void *pvUser) +{ + int rc = VERR_GENERAL_FAILURE; + + RT_NOREF(ThreadSelf); + RT_NOREF(pvUser); + + for (;;) + { + /* Do not acknowledge the first event we query for to pick up old events, + * e.g. from before a guest reboot. */ + bool fAck = false; + + uint32_t events; + + VMMDevDisplayDef aDisplaysIn[VBOX_DRMIPC_MONITORS_MAX]; + uint32_t cDisplaysIn = 0; + + RT_ZERO(aDisplaysIn); + + /* Query the first size without waiting. This lets us e.g. pick up + * the last event before a guest reboot when we start again after. */ + rc = VbglR3GetDisplayChangeRequestMulti(VBOX_DRMIPC_MONITORS_MAX, &cDisplaysIn, aDisplaysIn, fAck); + fAck = true; + if (RT_SUCCESS(rc)) + { + rc = vbDrmPushScreenLayout(aDisplaysIn, cDisplaysIn, false, true); + if (RT_FAILURE(rc)) + VBClLogError("Failed to push display change as requested by host, rc=%Rrc\n", rc); + } + else + VBClLogError("Failed to get display change request, rc=%Rrc\n", rc); + + do + { + rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, VBOX_DRMIPC_RX_TIMEOUT_MS, &events); + } while ((rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED) && !ASMAtomicReadBool(&g_fShutdown)); + + if (ASMAtomicReadBool(&g_fShutdown)) + { + VBClLogInfo("exiting resize thread: shutdown requested\n"); + /* This is a case when we should return positive status. */ + rc = (rc == VERR_TIMEOUT) ? VINF_SUCCESS : rc; + break; + } + else if (RT_FAILURE(rc)) + VBClLogFatalError("VBoxDRMClient: resize thread: failure waiting for event, rc=%Rrc\n", rc); + } + + return rc; +} + +/** + * Go over all existing IPC client connection and put set-primary-screen request + * data into TX queue of each of them . + * + * @return IPRT status code. + * @param u32PrimaryDisplay Primary display ID. + */ +static int vbDrmIpcBroadcastPrimaryDisplay(uint32_t u32PrimaryDisplay) +{ + int rc; + + rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect); + if (RT_SUCCESS(rc)) + { + if (!RTListIsEmpty(&g_ipcClientConnectionsList.Node)) + { + PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pEntry; + RTListForEach(&g_ipcClientConnectionsList.Node, pEntry, VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE, Node) + { + AssertReturn(pEntry, VERR_INVALID_PARAMETER); + AssertReturn(pEntry->pClient, VERR_INVALID_PARAMETER); + AssertReturn(pEntry->pClient->hThread, VERR_INVALID_PARAMETER); + + rc = vbDrmIpcSetPrimaryDisplay(pEntry->pClient, u32PrimaryDisplay); + + VBClLogInfo("thread %s notified IPC Client that display %u is now primary, rc=%Rrc\n", + RTThreadGetName(pEntry->pClient->hThread), u32PrimaryDisplay, rc); + } + } + + int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("notify DE: unable to leave critical section, rc=%Rrc\n", rc2); + } + else + VBClLogError("notify DE: unable to enter critical section, rc=%Rrc\n", rc); + + return rc; +} + +/** + * Main loop for IPC client connection handling. + * + * @return IPRT status code. + * @param pClient Pointer to IPC client data. + */ +static int vbDrmIpcConnectionProc(PVBOX_DRMIPC_CLIENT pClient) +{ + int rc = VERR_GENERAL_FAILURE; + + AssertReturn(pClient, VERR_INVALID_PARAMETER); + + /* This loop handles incoming messages. */ + for (;;) + { + rc = vbDrmIpcConnectionHandler(pClient); + + /* Try to detect if we should shutdown as early as we can. */ + if (ASMAtomicReadBool(&g_fShutdown)) + break; + + /* Normal case. No data received within short interval. */ + if (rc == VERR_TIMEOUT) + { + continue; + } + else if (RT_FAILURE(rc)) + { + /* Terminate connection handling in case of error. */ + VBClLogError("unable to handle IPC session, rc=%Rrc\n", rc); + break; + } + } + + return rc; +} + +/** + * Add IPC client connection data into list of connections. + * + * List size is limited indirectly by DRM_IPC_SERVER_CONNECTIONS_MAX value. + * This function should only be invoked from client thread context + * (from vbDrmIpcClientWorker() in particular). + * + * @return IPRT status code. + * @param pClientNode Client connection information to add to the list. + */ +static int vbDrmIpcClientsListAdd(PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pClientNode) +{ + int rc; + + AssertReturn(pClientNode, VERR_INVALID_PARAMETER); + + rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect); + if (RT_SUCCESS(rc)) + { + RTListAppend(&g_ipcClientConnectionsList.Node, &pClientNode->Node); + + int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("add client connection: unable to leave critical section, rc=%Rrc\n", rc2); + } + else + VBClLogError("add client connection: unable to enter critical section, rc=%Rrc\n", rc); + + return rc; +} + +/** + * Remove IPC client connection data from list of connections. + * + * This function should only be invoked from client thread context + * (from vbDrmIpcClientWorker() in particular). + * + * @return IPRT status code. + * @param pClientNode Client connection information to remove from the list. + */ +static int vbDrmIpcClientsListRemove(PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pClientNode) +{ + int rc; + PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE pEntry, pNextEntry, pFound = NULL; + + AssertReturn(pClientNode, VERR_INVALID_PARAMETER); + + rc = RTCritSectEnter(&g_ipcClientConnectionsListCritSect); + if (RT_SUCCESS(rc)) + { + + if (!RTListIsEmpty(&g_ipcClientConnectionsList.Node)) + { + RTListForEachSafe(&g_ipcClientConnectionsList.Node, pEntry, pNextEntry, VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE, Node) + { + if (pEntry == pClientNode) + pFound = (PVBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE)RTListNodeRemoveRet(&pEntry->Node); + } + } + else + VBClLogError("remove client connection: connections list empty, node %p not there\n", pClientNode); + + int rc2 = RTCritSectLeave(&g_ipcClientConnectionsListCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("remove client connection: unable to leave critical section, rc=%Rrc\n", rc2); + } + else + VBClLogError("remove client connection: unable to enter critical section, rc=%Rrc\n", rc); + + if (!pFound) + VBClLogError("remove client connection: node not found\n"); + + return !rc && pFound ? VINF_SUCCESS : VERR_INVALID_PARAMETER; +} + +/** + * Convert VBOX_DRMIPC_VMWRECT into VMMDevDisplayDef and check layout correctness. + * + * VBOX_DRMIPC_VMWRECT does not represent enough information needed for + * VMMDevDisplayDef. Missing fields (fDisplayFlags, idDisplay, cBitsPerPixel) + * are initialized with default (invalid) values due to this. + * + * @return True if given screen layout is correct (i.e., has no displays which overlap), False + * if it needs to be adjusted before injecting into DRM stack. + * @param cDisplays Number of displays in configuration data. + * @param pIn A pointer to display configuration data array in form of VBOX_DRMIPC_VMWRECT (input). + * @param pOut A pointer to display configuration data array in form of VMMDevDisplayDef (output). + */ +static bool vbDrmVmwRectToDisplayDef(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *pIn, VMMDevDisplayDef *pOut) +{ + bool fCorrect = true; + + for (uint32_t i = 0; i < cDisplays; i++) + { + /* VBOX_DRMIPC_VMWRECT has no information about this fields. */ + pOut[i].fDisplayFlags = 0; + pOut[i].idDisplay = VBOX_DRMIPC_MONITORS_MAX; + pOut[i].cBitsPerPixel = 0; + + pOut[i].xOrigin = pIn[i].x; + pOut[i].yOrigin = pIn[i].y; + pOut[i].cx = pIn[i].w; + pOut[i].cy = pIn[i].h; + + /* Make sure that displays do not overlap within reported screen layout. Ask IPC server to fix layout otherwise. */ + fCorrect = i > 0 + && pIn[i].x != (int32_t)pIn[i - 1].w + pIn[i - 1].x + ? false + : fCorrect; + } + + return fCorrect; +} + +/** + * @interface_method_impl{VBOX_DRMIPC_CLIENT,pfnRxCb} + */ +static DECLCALLBACK(int) vbDrmIpcClientRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData) +{ + int rc = VERR_INVALID_PARAMETER; + + AssertReturn(pvData, VERR_INVALID_PARAMETER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + switch (idCmd) + { + case VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS: + { + VMMDevDisplayDef aDisplays[VBOX_DRMIPC_MONITORS_MAX]; + bool fCorrect; + + PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS pCmd = (PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS)pvData; + AssertReturn(cbData == sizeof(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS), VERR_INVALID_PARAMETER); + AssertReturn(pCmd->cDisplays < VBOX_DRMIPC_MONITORS_MAX, VERR_INVALID_PARAMETER); + + /* Convert input display config into VMMDevDisplayDef representation. */ + RT_ZERO(aDisplays); + fCorrect = vbDrmVmwRectToDisplayDef(pCmd->cDisplays, pCmd->aDisplays, aDisplays); + + rc = vbDrmPushScreenLayout(aDisplays, pCmd->cDisplays, true, !fCorrect); + if (RT_FAILURE(rc)) + VBClLogError("Failed to push display change as requested by Desktop Environment helper, rc=%Rrc\n", rc); + + break; + } + + default: + { + VBClLogError("received unknown IPC command 0x%x\n", idCmd); + break; + } + } + + return rc; +} + +/** Worker thread for IPC client task. */ +static DECLCALLBACK(int) vbDrmIpcClientWorker(RTTHREAD ThreadSelf, void *pvUser) +{ + VBOX_DRMIPC_CLIENT hClient = VBOX_DRMIPC_CLIENT_INITIALIZER; + RTLOCALIPCSESSION hSession = (RTLOCALIPCSESSION)pvUser; + int rc; + + AssertReturn(RT_VALID_PTR(hSession), VERR_INVALID_PARAMETER); + + /* Initialize client session resources. */ + rc = vbDrmIpcClientInit(&hClient, ThreadSelf, hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbDrmIpcClientRxCallBack); + if (RT_SUCCESS(rc)) + { + /* Add IPC client connection data into clients list. */ + VBOX_DRMIPC_CLIENT_CONNECTION_LIST_NODE hClientNode = { { 0, 0 } , &hClient }; + + rc = vbDrmIpcClientsListAdd(&hClientNode); + if (RT_SUCCESS(rc)) + { + rc = RTThreadUserSignal(ThreadSelf); + if (RT_SUCCESS(rc)) + { + /* Start spinning the connection. */ + VBClLogInfo("IPC client connection started\n", rc); + rc = vbDrmIpcConnectionProc(&hClient); + VBClLogInfo("IPC client connection ended, rc=%Rrc\n", rc); + } + else + VBClLogError("unable to report IPC client connection handler start, rc=%Rrc\n", rc); + + /* Remove IPC client connection data from clients list. */ + rc = vbDrmIpcClientsListRemove(&hClientNode); + if (RT_FAILURE(rc)) + VBClLogError("unable to remove IPC client session from list of connections, rc=%Rrc\n", rc); + } + else + VBClLogError("unable to add IPC client connection to the list, rc=%Rrc\n"); + + /* Disconnect remote peer if still connected. */ + if (RT_VALID_PTR(hSession)) + { + rc = RTLocalIpcSessionClose(hSession); + VBClLogInfo("IPC session closed, rc=%Rrc\n", rc); + } + + /* Connection handler loop has ended, release session resources. */ + rc = vbDrmIpcClientReleaseResources(&hClient); + if (RT_FAILURE(rc)) + VBClLogError("unable to release IPC client session, rc=%Rrc\n", rc); + + ASMAtomicDecU32(&g_cDrmIpcConnections); + } + else + VBClLogError("unable to initialize IPC client session, rc=%Rrc\n", rc); + + VBClLogInfo("closing IPC client session, rc=%Rrc\n", rc); + + return rc; +} + +/** + * Start processing thread for IPC client requests handling. + * + * @returns IPRT status code. + * @param hSession IPC client connection handle. + */ +static int vbDrmIpcClientStart(RTLOCALIPCSESSION hSession) +{ + int rc; + RTTHREAD hThread = 0; + RTPROCESS hProcess = 0; + + rc = RTLocalIpcSessionQueryProcess(hSession, &hProcess); + if (RT_SUCCESS(rc)) + { + char pszThreadName[DRM_IPC_THREAD_NAME_MAX]; + RT_ZERO(pszThreadName); + + RTStrPrintf2(pszThreadName, DRM_IPC_THREAD_NAME_MAX, DRM_IPC_CLIENT_THREAD_NAME_PTR, hProcess); + + /* Attempt to start IPC client connection handler task. */ + rc = RTThreadCreate(&hThread, vbDrmIpcClientWorker, (void *)hSession, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, pszThreadName); + if (RT_SUCCESS(rc)) + { + rc = RTThreadUserWait(hThread, RT_MS_5SEC); + } + } + + return rc; +} + +/** Worker thread for IPC server task. */ +static DECLCALLBACK(int) vbDrmIpcServerWorker(RTTHREAD ThreadSelf, void *pvUser) +{ + int rc = VERR_GENERAL_FAILURE; + RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser; + + RT_NOREF1(ThreadSelf); + + AssertReturn(hIpcServer, VERR_INVALID_PARAMETER); + + /* This loop accepts incoming connections. */ + for (;;) + { + RTLOCALIPCSESSION hClientSession; + + /* Wait for incoming connection. */ + rc = RTLocalIpcServerListen(hIpcServer, &hClientSession); + if (RT_SUCCESS(rc)) + { + VBClLogVerbose(2, "new IPC session\n"); + + if (ASMAtomicIncU32(&g_cDrmIpcConnections) <= DRM_IPC_SERVER_CONNECTIONS_MAX) + { + /* Authenticate remote peer. */ + if (ASMAtomicReadBool(&g_fDrmIpcRestricted)) + rc = vbDrmIpcAuth(hClientSession); + + if (RT_SUCCESS(rc)) + { + /* Start incoming connection handler thread. */ + rc = vbDrmIpcClientStart(hClientSession); + VBClLogVerbose(2, "connection processing ended, rc=%Rrc\n", rc); + } + else + VBClLogError("IPC authentication failed, rc=%Rrc\n", rc); + } + else + rc = VERR_RESOURCE_BUSY; + + /* Release resources in case of error. */ + if (RT_FAILURE(rc)) + { + VBClLogError("maximum amount of IPC client connections reached, dropping connection\n"); + + int rc2 = RTLocalIpcSessionClose(hClientSession); + if (RT_FAILURE(rc2)) + VBClLogError("unable to close IPC session, rc=%Rrc\n", rc2); + + ASMAtomicDecU32(&g_cDrmIpcConnections); + } + } + else + VBClLogError("IPC authentication failed, rc=%Rrc\n", rc); + + /* Check shutdown was requested. */ + if (ASMAtomicReadBool(&g_fShutdown)) + { + VBClLogInfo("exiting IPC thread: shutdown requested\n"); + break; + } + + /* Wait a bit before spinning a loop if something went wrong. */ + if (RT_FAILURE(rc)) + RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS); + } + + return rc; +} + +/** A signal handler. */ +static void vbDrmRequestShutdown(int sig) +{ + RT_NOREF(sig); + ASMAtomicWriteBool(&g_fShutdown, true); +} + +/** + * Grant access to DRM IPC server socket depending on VM configuration. + * + * If VM has '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property set + * and this property is READ-ONLY for the guest side, access will be + * granted to root and users from 'vboxdrmipc' group only. If group does + * not exists, only root will have access to the socket. When property is + * not set or not READ-ONLY, all users will have access to the socket. + * + * @param hIpcServer IPC server handle. + * @param fRestrict Whether to restrict access to socket or not. + */ +static void vbDrmSetIpcServerAccessPermissions(RTLOCALIPCSERVER hIpcServer, bool fRestrict) +{ + int rc; + + if (fRestrict) + { + struct group *pGrp; + pGrp = getgrnam(VBOX_DRMIPC_USER_GROUP); + if (pGrp) + { + rc = RTLocalIpcServerGrantGroupAccess(hIpcServer, pGrp->gr_gid); + if (RT_SUCCESS(rc)) + VBClLogInfo("IPC server socket access granted to '" VBOX_DRMIPC_USER_GROUP "' users\n"); + else + VBClLogError("unable to grant IPC server socket access to '" VBOX_DRMIPC_USER_GROUP "' users, rc=%Rrc\n", rc); + + } + else + VBClLogError("unable to grant IPC server socket access to '" VBOX_DRMIPC_USER_GROUP "', group does not exist\n"); + } + else + { + rc = RTLocalIpcServerSetAccessMode(hIpcServer, + RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR | + RTFS_UNIX_IRGRP | RTFS_UNIX_IWGRP | + RTFS_UNIX_IROTH | RTFS_UNIX_IWOTH); + if (RT_SUCCESS(rc)) + VBClLogInfo("IPC server socket access granted to all users\n"); + else + VBClLogError("unable to grant IPC server socket access to all users, rc=%Rrc\n", rc); + } + + /* Set flag for the thread which serves incomming IPC connections. */ + ASMAtomicWriteBool(&g_fDrmIpcRestricted, fRestrict); +} + +/** + * Wait and handle '/VirtualBox/GuestAdd/DRMIpcRestricted' guest property change. + * + * This function is executed in context of main(). + * + * @param hIpcServer IPC server handle. + */ +static void vbDrmPollIpcServerAccessMode(RTLOCALIPCSERVER hIpcServer) +{ + HGCMCLIENTID idClient; + int rc; + + rc = VbglR3GuestPropConnect(&idClient); + if (RT_SUCCESS(rc)) + { + do + { + /* Buffer should be big enough to fit guest property data layout: Name\0Value\0Flags\0fWasDeleted\0. */ + static char achBuf[GUEST_PROP_MAX_NAME_LEN]; + char *pszName = NULL; + char *pszValue = NULL; + char *pszFlags = NULL; + bool fWasDeleted = false; + uint64_t u64Timestamp = 0; + + rc = VbglR3GuestPropWait(idClient, VBGLR3DRMPROPPTR, achBuf, sizeof(achBuf), u64Timestamp, + VBOX_DRMIPC_RX_TIMEOUT_MS, &pszName, &pszValue, &u64Timestamp, + &pszFlags, NULL, &fWasDeleted); + if (RT_SUCCESS(rc)) + { + uint32_t fFlags = 0; + + VBClLogVerbose(1, "guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool\n", + pszName, pszValue, pszFlags, fWasDeleted); + + if (RT_SUCCESS(GuestPropValidateFlags(pszFlags, &fFlags))) + { + if (RTStrNCmp(pszName, VBGLR3DRMIPCPROPRESTRICT, GUEST_PROP_MAX_NAME_LEN) == 0) + { + /* Enforce restricted socket access until guest property exist and READ-ONLY for the guest. */ + vbDrmSetIpcServerAccessPermissions(hIpcServer, !fWasDeleted && fFlags & GUEST_PROP_F_RDONLYGUEST); + } + + } else + VBClLogError("guest property change: name: %s, val: %s, flags: %s, fWasDeleted: %RTbool: bad flags\n", + pszName, pszValue, pszFlags, fWasDeleted); + + } else if ( rc != VERR_TIMEOUT + && rc != VERR_INTERRUPTED) + { + VBClLogError("error on waiting guest property notification, rc=%Rrc\n", rc); + RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS); + } + + } while (!ASMAtomicReadBool(&g_fShutdown)); + + VbglR3GuestPropDisconnect(idClient); + } + else + VBClLogError("cannot connect to VM guest properties service, rc=%Rrc\n", rc); +} + +int main(int argc, char *argv[]) +{ + /** Custom log prefix to be used for logger instance of this process. */ + static const char *pszLogPrefix = "VBoxDRMClient:"; + + static const RTGETOPTDEF s_aOptions[] = { { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, }; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + int ch; + + RTFILE hPidFile; + + RTLOCALIPCSERVER hIpcServer; + RTTHREAD vbDrmIpcThread; + int rcDrmIpcThread = 0; + + RTTHREAD drmResizeThread; + int rcDrmResizeThread = 0; + int rc, rc2 = 0; + + rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + VBClLogFatalError("VBoxDRMClient: VbglR3InitUser failed: %Rrc", rc); + + /* Process command line options. */ + rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */); + if (RT_FAILURE(rc)) + VBClLogFatalError("VBoxDRMClient: unable to process command line options, rc=%Rrc\n", rc); + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + switch (ch) + { + case 'v': + { + g_cVerbosity++; + break; + } + + case VERR_GETOPT_UNKNOWN_OPTION: + { + VBClLogFatalError("unknown command line option '%s'\n", ValueUnion.psz); + return RTEXITCODE_SYNTAX; + + } + + default: + break; + } + } + + rc = VBClLogCreate(""); + if (RT_FAILURE(rc)) + VBClLogFatalError("VBoxDRMClient: failed to setup logging, rc=%Rrc\n", rc); + VBClLogSetLogPrefix(pszLogPrefix); + + /* Check PID file before attempting to initialize anything. */ + rc = VbglR3PidFile(g_pszPidFile, &hPidFile); + if (rc == VERR_FILE_LOCK_VIOLATION) + { + VBClLogInfo("already running, exiting\n"); + return RTEXITCODE_SUCCESS; + } + if (RT_FAILURE(rc)) + { + VBClLogError("unable to lock PID file (%Rrc), exiting\n", rc); + return RTEXITCODE_FAILURE; + } + + g_hDevice = vbDrmOpenVmwgfx(); + if (g_hDevice == NIL_RTFILE) + return RTEXITCODE_FAILURE; + + rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0); + if (RT_FAILURE(rc)) + { + VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc); + return RTEXITCODE_FAILURE; + } + rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false); + if (RT_FAILURE(rc)) + { + VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc); + return RTEXITCODE_FAILURE; + } + + /* Setup signals: gracefully terminate on SIGINT, SIGTERM. */ + if ( signal(SIGINT, vbDrmRequestShutdown) == SIG_ERR + || signal(SIGTERM, vbDrmRequestShutdown) == SIG_ERR) + { + VBClLogError("unable to setup signals\n"); + return RTEXITCODE_FAILURE; + } + + /* Init IPC client connection list. */ + RTListInit(&g_ipcClientConnectionsList.Node); + rc = RTCritSectInit(&g_ipcClientConnectionsListCritSect); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to initialize IPC client connection list critical section\n"); + return RTEXITCODE_FAILURE; + } + + /* Init critical section which is used for reporting monitors offset back to host. */ + rc = RTCritSectInit(&g_monitorPositionsCritSect); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to initialize monitors position critical section\n"); + return RTEXITCODE_FAILURE; + } + + /* Instantiate IPC server for VBoxClient service communication. */ + rc = RTLocalIpcServerCreate(&hIpcServer, VBOX_DRMIPC_SERVER_NAME, 0); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to setup IPC server, rc=%Rrc\n", rc); + return RTEXITCODE_FAILURE; + } + + /* Set IPC server socket access permissions according to VM configuration. */ + vbDrmSetIpcServerAccessPermissions(hIpcServer, VbglR3DrmRestrictedIpcAccessIsNeeded()); + + /* Attempt to start DRM resize task. */ + rc = RTThreadCreate(&drmResizeThread, vbDrmResizeWorker, NULL, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, DRM_RESIZE_THREAD_NAME); + if (RT_SUCCESS(rc)) + { + /* Attempt to start IPC task. */ + rc = RTThreadCreate(&vbDrmIpcThread, vbDrmIpcServerWorker, (void *)hIpcServer, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, DRM_IPC_SERVER_THREAD_NAME); + if (RT_SUCCESS(rc)) + { + /* Poll for host notification about IPC server socket access mode change. */ + vbDrmPollIpcServerAccessMode(hIpcServer); + + /* HACK ALERT! + * The sequence of RTThreadWait(drmResizeThread) -> RTLocalIpcServerDestroy() -> RTThreadWait(vbDrmIpcThread) + * is intentional! Once process received a signal, it will pull g_fShutdown flag, which in turn will cause + * drmResizeThread to quit. The vbDrmIpcThread might hang on accept() call, so we terminate IPC server to + * release it and then wait for its termination. */ + + rc = RTThreadWait(drmResizeThread, RT_INDEFINITE_WAIT, &rcDrmResizeThread); + VBClLogInfo("%s thread exited with status, rc=%Rrc\n", DRM_RESIZE_THREAD_NAME, rcDrmResizeThread); + + rc = RTLocalIpcServerCancel(hIpcServer); + if (RT_FAILURE(rc)) + VBClLogError("unable to notify IPC server about shutdown, rc=%Rrc\n", rc); + + /* Wait for threads to terminate gracefully. */ + rc = RTThreadWait(vbDrmIpcThread, RT_INDEFINITE_WAIT, &rcDrmIpcThread); + VBClLogInfo("%s thread exited with status, rc=%Rrc\n", DRM_IPC_SERVER_THREAD_NAME, rcDrmResizeThread); + + } + else + VBClLogError("unable to start IPC thread, rc=%Rrc\n", rc); + } + else + VBClLogError("unable to start resize thread, rc=%Rrc\n", rc); + + rc = RTLocalIpcServerDestroy(hIpcServer); + if (RT_FAILURE(rc)) + VBClLogError("unable to stop IPC server, rc=%Rrc\n", rc); + + rc2 = RTCritSectDelete(&g_monitorPositionsCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("unable to destroy g_monitorPositionsCritSect critsect, rc=%Rrc\n", rc2); + + rc2 = RTCritSectDelete(&g_ipcClientConnectionsListCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("unable to destroy g_ipcClientConnectionsListCritSect critsect, rc=%Rrc\n", rc2); + + RTFileClose(g_hDevice); + + VBClLogInfo("releasing PID file lock\n"); + VbglR3ClosePidFile(g_pszPidFile, hPidFile); + + VBClLogDestroy(); + + return rc == 0 ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} diff --git a/src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp b/src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp new file mode 100644 index 00000000..8e88e669 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-helper-generic.cpp @@ -0,0 +1,421 @@ +/* $Id: display-helper-generic.cpp $ */ +/** @file + * Guest Additions - Generic Desktop Environment helper. + * + * A generic helper for X11 Client which performs Desktop Environment + * specific actions utilizing libXrandr. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "VBoxClient.h" +#include "display-helper.h" + +#include <stdio.h> +#include <stdlib.h> + +#include <VBox/log.h> +#include <VBox/xrandr.h> + +#include <iprt/errcore.h> +#include <iprt/asm.h> +#include <iprt/thread.h> +#include <iprt/mem.h> +#include <iprt/list.h> + +/** Load libxrandr symbols needed for us. */ +#include <VBox/xrandr.h> +/* Declarations of the functions that we need from libXrandr. */ +#define VBOX_XRANDR_GENERATE_BODY +#include <VBox/xrandr-calls.h> + +#include <X11/Xlibint.h> + +/** Name of Display Change Monitor thread. */ +#define VBCL_HLP_DCM_THREAD_NAME "dcm-task" + +/** Display Change Monitor thread. */ +static RTTHREAD g_vbclHlpGenericDcmThread = NIL_RTTHREAD; + +/** Global flag which is triggered when service requested to shutdown. */ +static bool volatile g_fShutdown; + +/** Node of monitors info list. */ +typedef struct vbcl_hlp_generic_monitor_list_t +{ + /** List node. */ + RTLISTNODE Node; + /** Pointer to xRandr monitor info. */ + XRRMonitorInfo *pMonitorInfo; +} vbcl_hlp_generic_monitor_list_t; + +/** Pointer to display change event notification callback (set by external function call). */ +static FNDISPLAYOFFSETCHANGE *g_pfnDisplayOffsetChangeCb; + +/** + * Determine monitor name strings order in a list of monitors which is sorted in ascending way. + * + * @return TRUE if first name should go first in a list, FALSE otherwise. + * @param pszName1 First monitor name. + * @param pszName2 Second monitor name. + */ +static bool vbcl_hlp_generic_order_names(char *pszName1, char *pszName2) +{ + AssertReturn(pszName1, false); + AssertReturn(pszName2, false); + + char *pszFirst = pszName1; + char *pszSecond = pszName2; + + while (*pszFirst && *pszSecond) + { + if (*pszFirst < *pszSecond) + return true; + + pszFirst++; + pszSecond++; + } + + return false; +} + +/** + * Insert monitor info into the list sorted ascending. + * + * @return IPRT status code. + * @param pDisplay X11 display handle to fetch monitor name string from. + * @param pListHead Head of monitors info list. + * @param pMonitorInfo Monitor info ti be inserted into the list. + */ +static int vbcl_hlp_generic_monitor_list_insert_sorted( + Display *pDisplay, vbcl_hlp_generic_monitor_list_t *pListHead, XRRMonitorInfo *pMonitorInfo) +{ + vbcl_hlp_generic_monitor_list_t *pNode = (vbcl_hlp_generic_monitor_list_t *)RTMemAllocZ(sizeof(vbcl_hlp_generic_monitor_list_t)); + vbcl_hlp_generic_monitor_list_t *pNodeIter; + char *pszMonitorName; + + AssertReturn(pNode, VERR_NO_MEMORY); + + pNode->pMonitorInfo = pMonitorInfo; + + if (RTListIsEmpty(&pListHead->Node)) + { + RTListNodeInsertAfter(&pListHead->Node, &pNode->Node); + return VINF_SUCCESS; + } + + pszMonitorName = XGetAtomName(pDisplay, pMonitorInfo->name); + AssertReturn(pszMonitorName, VERR_NO_MEMORY); + + RTListForEach(&pListHead->Node, pNodeIter, vbcl_hlp_generic_monitor_list_t, Node) + { + char *pszIterMonitorName = XGetAtomName(pDisplay, pNodeIter->pMonitorInfo->name); + + if (vbcl_hlp_generic_order_names(pszMonitorName, pszIterMonitorName)) + { + RTListNodeInsertBefore(&pNodeIter->Node, &pNode->Node); + XFree((void *)pszIterMonitorName); + XFree((void *)pszMonitorName); + return VINF_SUCCESS; + } + + XFree((void *)pszIterMonitorName); + } + + XFree((void *)pszMonitorName); + + /* If we reached the end of the list, it means that monitor + * should be placed in the end (according to alphabetical sorting). */ + RTListNodeInsertBefore(&pNodeIter->Node, &pNode->Node); + + return VINF_SUCCESS; +} + +/** + * Release monitors info list resources. + * + * @param pListHead List head. + */ +static void vbcl_hlp_generic_free_monitor_list(vbcl_hlp_generic_monitor_list_t *pListHead) +{ + vbcl_hlp_generic_monitor_list_t *pEntry, *pNextEntry; + + RTListForEachSafe(&pListHead->Node, pEntry, pNextEntry, vbcl_hlp_generic_monitor_list_t, Node) + { + RTListNodeRemove(&pEntry->Node); + RTMemFree(pEntry); + } +} + +/** + * Handle received RRScreenChangeNotify event. + * + * @param pDisplay X11 display handle. + */ +static void vbcl_hlp_generic_process_display_change_event(Display *pDisplay) +{ + int iCount; + uint32_t idxDisplay = 0; + XRRMonitorInfo *pMonitorsInfo = XRRGetMonitors(pDisplay, DefaultRootWindow(pDisplay), true, &iCount); + if (pMonitorsInfo && iCount > 0 && iCount < VBOX_DRMIPC_MONITORS_MAX) + { + int rc; + vbcl_hlp_generic_monitor_list_t pMonitorsInfoList, *pIter; + struct VBOX_DRMIPC_VMWRECT aDisplays[VBOX_DRMIPC_MONITORS_MAX]; + + RTListInit(&pMonitorsInfoList.Node); + + /* Put monitors info into sorted (by monitor name) list. */ + for (int i = 0; i < iCount; i++) + { + rc = vbcl_hlp_generic_monitor_list_insert_sorted(pDisplay, &pMonitorsInfoList, &pMonitorsInfo[i]); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to fill monitors info list, rc=%Rrc\n", rc); + break; + } + } + + /* Now iterate over sorted list of monitor configurations. */ + RTListForEach(&pMonitorsInfoList.Node, pIter, vbcl_hlp_generic_monitor_list_t, Node) + { + char *pszMonitorName = XGetAtomName(pDisplay, pIter->pMonitorInfo->name); + + VBClLogVerbose(1, "reporting monitor %s offset: (%d, %d)\n", + pszMonitorName, pIter->pMonitorInfo->x, pIter->pMonitorInfo->y); + + XFree((void *)pszMonitorName); + + aDisplays[idxDisplay].x = pIter->pMonitorInfo->x; + aDisplays[idxDisplay].y = pIter->pMonitorInfo->y; + aDisplays[idxDisplay].w = pIter->pMonitorInfo->width; + aDisplays[idxDisplay].h = pIter->pMonitorInfo->height; + + idxDisplay++; + } + + vbcl_hlp_generic_free_monitor_list(&pMonitorsInfoList); + + XRRFreeMonitors(pMonitorsInfo); + + if (g_pfnDisplayOffsetChangeCb) + { + rc = g_pfnDisplayOffsetChangeCb(idxDisplay, aDisplays); + if (RT_FAILURE(rc)) + VBClLogError("unable to notify subscriber about monitors info change, rc=%Rrc\n", rc); + } + } + else + VBClLogError("cannot get monitors info\n"); +} + +/** Worker thread for display change events monitoring. */ +static DECLCALLBACK(int) vbcl_hlp_generic_display_change_event_monitor_worker(RTTHREAD ThreadSelf, void *pvUser) +{ + int rc = VERR_GENERAL_FAILURE; + + RT_NOREF(pvUser); + + VBClLogVerbose(1, "vbcl_hlp_generic_display_change_event_monitor_worker started\n"); + + Display *pDisplay = XOpenDisplay(NULL); + if (pDisplay) + { + bool fSuccess; + int iEventBase, iErrorBase /* unused */, iMajor, iMinor; + + fSuccess = XRRQueryExtension(pDisplay, &iEventBase, &iErrorBase); + fSuccess &= XRRQueryVersion(pDisplay, &iMajor, &iMinor); + + if (fSuccess && iMajor >= 1 && iMinor > 3) + { + /* All required checks are now passed. Notify parent thread that we started. */ + RTThreadUserSignal(ThreadSelf); + + /* Only receive events we need. */ + XRRSelectInput(pDisplay, DefaultRootWindow(pDisplay), RRScreenChangeNotifyMask); + + /* Monitor main loop. */ + while (!ASMAtomicReadBool(&g_fShutdown)) + { + XEvent Event; + + if (XPending(pDisplay) > 0) + { + XNextEvent(pDisplay, &Event); + switch (Event.type - iEventBase) + { + case RRScreenChangeNotify: + { + vbcl_hlp_generic_process_display_change_event(pDisplay); + break; + } + + default: + break; + } + } + else + RTThreadSleep(RT_MS_1SEC / 2); + } + } + else + { + VBClLogError("dcm monitor cannot find XRandr 1.3+ extension\n"); + rc = VERR_NOT_AVAILABLE; + } + + XCloseDisplay(pDisplay); + } + else + { + VBClLogError("dcm monitor cannot open X Display\n"); + rc = VERR_NOT_AVAILABLE; + } + + VBClLogVerbose(1, "vbcl_hlp_generic_display_change_event_monitor_worker ended\n"); + + return rc; +} + +static void vbcl_hlp_generic_start_display_change_monitor() +{ + int rc; + + rc = RTXrandrLoadLib(); + if (RT_SUCCESS(rc)) + { + /* Start thread which will monitor display change events. */ + rc = RTThreadCreate(&g_vbclHlpGenericDcmThread, vbcl_hlp_generic_display_change_event_monitor_worker, (void *)NULL, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, VBCL_HLP_DCM_THREAD_NAME); + if (RT_SUCCESS(rc)) + { + rc = RTThreadUserWait(g_vbclHlpGenericDcmThread, RT_MS_5SEC); + } + else + g_vbclHlpGenericDcmThread = NIL_RTTHREAD; + + VBClLogInfo("attempt to start display change monitor thread, rc=%Rrc\n", rc); + + } + else + VBClLogInfo("libXrandr not available, will not monitor display change events, rc=%Rrc\n", rc); +} + +/** + * @interface_method_impl{VBCLDISPLAYHELPER,pfnSetPrimaryDisplay} + */ +static DECLCALLBACK(int) vbcl_hlp_generic_set_primary_display(uint32_t idDisplay) +{ + XRRScreenResources *pScreenResources; + Display *pDisplay; + + int rc = VERR_INVALID_PARAMETER; + + pDisplay = XOpenDisplay(NULL); + if (pDisplay) + { + pScreenResources = XRRGetScreenResources(pDisplay, DefaultRootWindow(pDisplay)); + if (pScreenResources) + { + if ((int)idDisplay < pScreenResources->noutput) + { + XRRSetOutputPrimary(pDisplay, DefaultRootWindow(pDisplay), pScreenResources->outputs[idDisplay]); + VBClLogInfo("display %u has been set as primary\n", idDisplay); + rc = VINF_SUCCESS; + } + else + VBClLogError("cannot set display %u as primary: index out of range\n", idDisplay); + + XRRFreeScreenResources(pScreenResources); + } + else + VBClLogError("cannot set display %u as primary: libXrandr can not get screen resources\n", idDisplay); + + XCloseDisplay(pDisplay); + } + else + VBClLogError("cannot set display %u as primary: cannot connect to X11\n", idDisplay); + + return rc; +} + +/** + * @interface_method_impl{VBCLDISPLAYHELPER,pfnProbe} + */ +static DECLCALLBACK(int) vbcl_hlp_generic_probe(void) +{ + /* Generic helper always supposed to return positive status on probe(). This + * helper is a fallback one in case all the other helpers were failed to detect + * their environments. */ + return VINF_SUCCESS; +} + +RTDECL(int) vbcl_hlp_generic_init(void) +{ + ASMAtomicWriteBool(&g_fShutdown, false); + + /* Attempt to start display change events monitor. */ + vbcl_hlp_generic_start_display_change_monitor(); + + /* Always return positive status for generic (fallback, last resort) helper. */ + return VINF_SUCCESS; +} + +RTDECL(int) vbcl_hlp_generic_term(void) +{ + int rc = VINF_SUCCESS; + + if (g_vbclHlpGenericDcmThread != NIL_RTTHREAD) + { + /* Signal thread we are going to shutdown. */ + ASMAtomicWriteBool(&g_fShutdown, true); + + /* Wait for thread to terminate gracefully. */ + rc = RTThreadWait(g_vbclHlpGenericDcmThread, RT_MS_5SEC, NULL); + } + + return rc; +} + +RTDECL(void) vbcl_hlp_generic_subscribe_display_offset_changed(FNDISPLAYOFFSETCHANGE *pfnCb) +{ + g_pfnDisplayOffsetChangeCb = pfnCb; +} + +RTDECL(void) vbcl_hlp_generic_unsubscribe_display_offset_changed(void) +{ + g_pfnDisplayOffsetChangeCb = NULL; +} + +/* Helper callbacks. */ +const VBCLDISPLAYHELPER g_DisplayHelperGeneric = +{ + "GENERIC", /* .pszName */ + vbcl_hlp_generic_probe, /* .pfnProbe */ + vbcl_hlp_generic_init, /* .pfnInit */ + vbcl_hlp_generic_term, /* .pfnTerm */ + vbcl_hlp_generic_set_primary_display, /* .pfnSetPrimaryDisplay */ + vbcl_hlp_generic_subscribe_display_offset_changed, /* .pfnSubscribeDisplayOffsetChangeNotification */ + vbcl_hlp_generic_unsubscribe_display_offset_changed, /* .pfnUnsubscribeDisplayOffsetChangeNotification */ +}; diff --git a/src/VBox/Additions/x11/VBoxClient/display-helper-gnome3.cpp b/src/VBox/Additions/x11/VBoxClient/display-helper-gnome3.cpp new file mode 100644 index 00000000..d85f6f46 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-helper-gnome3.cpp @@ -0,0 +1,1019 @@ +/* $Id: display-helper-gnome3.cpp $ */ +/** @file + * Guest Additions - Gnome3 Desktop Environment helper. + * + * A helper for X11/Wayland Client which performs Gnome Desktop + * Environment specific actions. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** + * This helper implements communication protocol between gnome-settings-daemon + * and itself using interface defined in (revision e88467f9): + * + * https://gitlab.gnome.org/GNOME/mutter/-/blob/main/src/org.gnome.Mutter.DisplayConfig.xml + */ + +#include "VBoxClient.h" +#include "display-helper.h" + +#include <stdio.h> +#include <stdlib.h> + +#include <VBox/log.h> +#include <VBox/VBoxGuestLib.h> + +#include <iprt/env.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +/** Load libDbus symbols needed for us. */ +#include <VBox/dbus.h> +/* Declarations of the functions that we need from libXrandr. */ +#define VBOX_DBUS_GENERATE_BODY +#include <VBox/dbus-calls.h> + +/** D-bus parameters for connecting to Gnome display service. */ +#define VBOXCLIENT_HELPER_DBUS_DESTINATION "org.gnome.Mutter.DisplayConfig" +#define VBOXCLIENT_HELPER_DBUS_PATH "/org/gnome/Mutter/DisplayConfig" +#define VBOXCLIENT_HELPER_DBUS_IFACE "org.gnome.Mutter.DisplayConfig" +#define VBOXCLIENT_HELPER_DBUS_GET_METHOD "GetCurrentState" +#define VBOXCLIENT_HELPER_DBUS_APPLY_METHOD "ApplyMonitorsConfig" + +/** D-bus communication timeout value, milliseconds.*/ +#define VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS (1 * 1000) + +/** gnome-settings-daemon ApplyMonitorsConfig method: + * 0: verify - test if configuration can be applied and do not change anything, + * 1: temporary - apply configuration temporary, all will be reverted after re-login, + * 2: persistent - apply configuration permanently (asks for user confirmation). + */ +#define VBOXCLIENT_APPLY_DISPLAY_CONFIG_METHOD (1) + +/** + * Helper macro which is used in order to simplify code when batch of + * values needed to be parsed out of D-bus. Macro prevents execution + * of the 'next' command if 'previous' one was failed (tracked via + * local variable _ret). It is required that '_ret' should be initialized + * to TRUE before batch started. + * + * @param _ret Local variable which is used in order to track execution flow. + * @param _call A function (with full arguments) which returns 'dbus_bool_t'. + */ +#define VBCL_HLP_GNOME3_NEXT(_ret, _call) \ + { _ret &= _ret ? _call : _ret; if (!ret) VBClLogError(__FILE__ ":%d: check fail here!\n", __LINE__); } + +/** + * This structure describes sub-part of physical monitor state + * required to compose a payload for calling ApplyMonitorsConfig method. */ +struct vbcl_hlp_gnome3_physical_display_state +{ + /** Physical display connector name string. */ + char *connector; + /** Current mode name string for physical display. */ + char *mode; +}; + +/** + * Verify if data represented by D-bus message iteration corresponds to given data type. + * + * @return True if D-bus message iteration corresponds to given data type, False otherwise. + * @param iter D-bus message iteration. + * @param type D-bus data type. + */ +static dbus_bool_t vbcl_hlp_gnome3_verify_data_type(DBusMessageIter *iter, int type) +{ + if (!iter) + return false; + + if (dbus_message_iter_get_arg_type(iter) != type) + return false; + + return true; +} + +/** + * Verifies D-bus iterator signature. + * + * @return True if iterator signature matches to given one. + * @param iter D-bus iterator to check. + * @param signature Expected iterator signature. + */ +static dbus_bool_t vbcl_hlp_gnome3_check_iter_signature(DBusMessageIter *iter, const char *signature) +{ + char *iter_signature; + dbus_bool_t match; + + if ( !iter + || !signature) + { + return false; + } + + /* In case of dbus_message_iter_get_signature() returned memory should be freed by us. */ + iter_signature = dbus_message_iter_get_signature(iter); + match = (strcmp(iter_signature, signature) == 0); + + if (!match) + VBClLogError("iter signature mismatch: '%s' vs. '%s'\n", signature, iter_signature); + + if (iter_signature) + dbus_free(iter_signature); + + return match; +} + +/** + * Verifies D-bus message signature. + * + * @return True if message signature matches to given one. + * @param message D-bus message to check. + * @param signature Expected message signature. + */ +static dbus_bool_t vbcl_hlp_gnome3_check_message_signature(DBusMessage *message, const char *signature) +{ + char *message_signature; + dbus_bool_t match; + + if ( !message + || !signature) + { + return false; + } + + /* In case of dbus_message_get_signature() returned memory need NOT be freed by us. */ + message_signature = dbus_message_get_signature(message); + match = (strcmp(message_signature, signature) == 0); + + if (!match) + VBClLogError("message signature mismatch: '%s' vs. '%s'\n", signature, message_signature); + + return match; +} + +/** + * Jump into DBUS_TYPE_ARRAY iter container and initialize sub-iterator + * aimed to traverse container child nodes. + * + * @return True if operation was successful, False otherwise. + * @param iter D-bus iter of type DBUS_TYPE_ARRAY. + * @param array Returned sub-iterator. + */ +static dbus_bool_t vbcl_hlp_gnome3_iter_get_array(DBusMessageIter *iter, DBusMessageIter *array) +{ + if (!iter || !array) + return false; + + if (vbcl_hlp_gnome3_verify_data_type(iter, DBUS_TYPE_ARRAY)) + { + dbus_message_iter_recurse(iter, array); + /* Move to the next iter, returned value not important. */ + dbus_message_iter_next(iter); + return true; + } + else + { + VBClLogError( + "cannot get array: argument signature '%s' does not match to type of array\n", + dbus_message_iter_get_signature(iter)); + } + + return false; +} + +/** + * Get value of D-bus iter of specified simple type (numerals, strings). + * + * @return True if operation was successful, False otherwise. + * @param iter D-bus iter of type simple type. + * @param type D-bus data type. + * @param value Returned value. + */ +static dbus_bool_t vbcl_hlp_gnome3_iter_get_basic(DBusMessageIter *iter, int type, void *value) +{ + if (!iter || !value) + return false; + + if (vbcl_hlp_gnome3_verify_data_type(iter, type)) + { + dbus_message_iter_get_basic(iter, value); + /* Move to the next iter, returned value not important. */ + dbus_message_iter_next(iter); + return true; + } + else + { + VBClLogError( + "cannot get value: argument signature '%s' does not match to specified type\n", + dbus_message_iter_get_signature(iter)); + } + + return false; +} + +/** + * Lookup simple value (numeral, string, bool etc) in D-bus dictionary + * by given key and type. + * + * @return True value is found, False otherwise. + * @param dict D-bus iterator which represents dictionary. + * @param key_match Dictionary key. + * @param type Type of value. + * @param value Returning value. + */ +static dbus_bool_t vbcl_hlp_gnome3_lookup_dict(DBusMessageIter *dict, const char *key_match, int type, void *value) +{ + dbus_bool_t found = false; + + if (!dict || !key_match) + return false; + + if (!vbcl_hlp_gnome3_check_iter_signature(dict, "{sv}")) + return false; + + do + { + dbus_bool_t ret = true; + DBusMessageIter iter; + char *key = NULL; + + /* Proceed to part a{ > sv < } of a{sv}. */ + dbus_message_iter_recurse(dict, &iter); + + /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */ + AssertReturn(ret, false); + + /* Proceed to part a{ > s < v} of a{sv}. */ + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&iter, DBUS_TYPE_STRING, &key)); + + /* Check if key matches. */ + if (strcmp(key_match, key) == 0) + { + DBusMessageIter value_iter; + + /* Proceed to part a{s > v < } of a{sv}. */ + dbus_message_iter_recurse(&iter, &value_iter); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&value_iter, type, value)); + + /* Make sure there are no more arguments. */ + VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&value_iter)); + + if (ret) + { + found = true; + break; + } + } + } + while (dbus_message_iter_next(dict)); + + return found; +} + +/** + * Go through available modes and pick up the one which has property 'is-current' set. + * See GetCurrentState interface documentation for more details. Returned string memory + * must be freed by calling function. + * + * @return Mode name as a string if found, NULL otherwise. + * @param modes List of monitor modes. + */ +static char *vbcl_hlp_gnome3_lookup_monitor_current_mode(DBusMessageIter *modes) +{ + char *szCurrentMode = NULL; + DBusMessageIter modes_iter; + + /* De-serialization parameters for 'modes': (siiddada{sv}). */ + char *id = NULL; + int32_t width = 0; + int32_t height = 0; + double refresh_rate = 0; + double preferred_scale = 0; + DBusMessageIter supported_scales; + DBusMessageIter properties; + + if (!modes) + return NULL; + + if(!vbcl_hlp_gnome3_check_iter_signature(modes, "(siiddada{sv})")) + return NULL; + + do + { + static const char *key_match = "is-current"; + dbus_bool_t default_mode_found = false; + dbus_bool_t ret = true; + + /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */ + AssertReturn(ret, NULL); + + /* Proceed to part a( > siiddada{sv} < ) of a(siiddada{sv}). */ + dbus_message_iter_recurse(modes, &modes_iter); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_STRING, &id)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_INT32, &width)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_INT32, &height)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_DOUBLE, &refresh_rate)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&modes_iter, DBUS_TYPE_DOUBLE, &preferred_scale)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&modes_iter, &supported_scales)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&modes_iter, &properties)); + + ret = vbcl_hlp_gnome3_lookup_dict(&properties, key_match, DBUS_TYPE_BOOLEAN, &default_mode_found); + if (ret && default_mode_found) + { + szCurrentMode = strdup(id); + break; + } + } + while (dbus_message_iter_next(modes)); + + return szCurrentMode; +} + +/** + * Parse physical monitors list entry. See GetCurrentState interface documentation for more details. + * + * @return True if monitors list entry has been successfully parsed, False otherwise. + * @param physical_monitors_in D-bus iterator representing list of physical monitors. + * @param connector Connector name (out). + * @param vendor Vendor name (out). + * @param product Product name (out). + * @param physical_monitor_serial Serial number (out). + * @param modes List of monitor modes (out). + * @param physical_monitor_properties A D-bus dictionary containing monitor properties (out). + */ +static dbus_bool_t vbcl_hlp_gnome3_parse_physical_monitor_record( + DBusMessageIter *physical_monitors_in, + char **connector, + char **vendor, + char **product, + char **physical_monitor_serial, + DBusMessageIter *modes, + DBusMessageIter *physical_monitor_properties) +{ + dbus_bool_t ret = true; + + DBusMessageIter physical_monitors_in_iter; + DBusMessageIter physical_monitors_in_description_iter; + + if ( !physical_monitors_in + || !connector + || !vendor + || !product + || !physical_monitor_serial + || !modes + || !physical_monitor_properties) + { + return false; + } + + /* Validate signature. */ + if (!vbcl_hlp_gnome3_check_iter_signature(physical_monitors_in, "((ssss)a(siiddada{sv})a{sv})")) + return false; + + /* Proceed to part ( > (ssss)a(siiddada{sv})a{sv} < ) of ((ssss)a(siiddada{sv})a{sv}). */ + dbus_message_iter_recurse(physical_monitors_in, &physical_monitors_in_iter); + + /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */ + AssertReturn(ret, false); + + /* Proceed to part ( > (ssss) < a(siiddada{sv})a{sv}) of ((ssss)a(siiddada{sv})a{sv}). */ + dbus_message_iter_recurse(&physical_monitors_in_iter, &physical_monitors_in_description_iter); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, connector)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, vendor)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, product)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&physical_monitors_in_description_iter, DBUS_TYPE_STRING, physical_monitor_serial)); + + /* Proceed to part ((ssss) > a(siiddada{sv}) < a{sv}) of ((ssss)a(siiddada{sv})a{sv}). */ + if (ret) + dbus_message_iter_next(&physical_monitors_in_iter); + + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&physical_monitors_in_iter, modes)); + + /* Proceed to part ((ssss)a(siiddada{sv}) > a{sv} < ) of ((ssss)a(siiddada{sv})a{sv}). */ + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&physical_monitors_in_iter, physical_monitor_properties)); + + /* Make sure there are no more arguments. */ + VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&physical_monitors_in_iter)); + + return ret; +} + +/** + * Parse logical monitors list entry. See GetCurrentState interface documentation for more details. + * + * @return True if monitors list entry has been successfully parsed, False otherwise. + * @param logical_monitors_in D-bus iterator representing list of logical monitors. + * @param x Monitor X position (out). + * @param y Monitor Y position (out). + * @param scale Monitor scale factor (out). + * @param transform Current monitor transform (rotation) (out). + * @param primary A flag which indicates if monitor is set as primary (out). + * @param monitors List of physical monitors which are displaying this logical monitor (out). + * @param properties List of monitor properties (out). + */ +static dbus_bool_t vbcl_hlp_gnome3_parse_logical_monitor_record( + DBusMessageIter *logical_monitors_in, + int32_t *x, + int32_t *y, + double *scale, + uint32_t *transform, + dbus_bool_t *primary, + DBusMessageIter *monitors, + DBusMessageIter *properties) +{ + dbus_bool_t ret = true; + + /* Iter used to traverse logical monitor parameters: @a(iiduba(ssss)a{sv}). */ + DBusMessageIter logical_monitors_in_iter; + + if ( !logical_monitors_in + || !x + || !y + || !scale + || !transform + || !primary + || !monitors + || !properties) + + /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */ + AssertReturn(ret, false); + + /* Proceed to part @a( > iiduba(ssss)a{sv} < ) of @a(iiduba(ssss)a{sv}). */ + dbus_message_iter_recurse(logical_monitors_in, &logical_monitors_in_iter); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_INT32, x)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_INT32, y)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_DOUBLE, scale)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_UINT32, transform)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&logical_monitors_in_iter, DBUS_TYPE_BOOLEAN, primary)); + /* Proceed to part @a(iidub > a(ssss) < a{sv}) of @a(iiduba(ssss)a{sv}). */ + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&logical_monitors_in_iter, monitors)); + /* Proceed to part @a(iiduba(ssss) > a{sv} < ) of @a(iiduba(ssss)a{sv}). */ + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&logical_monitors_in_iter, properties)); + + /* Make sure there are no more arguments. */ + VBCL_HLP_GNOME3_NEXT(ret, !dbus_message_iter_has_next(&logical_monitors_in_iter)); + + return ret; +} + +/** + * Get list of physical monitors parameters from D-bus iterator. + * + * Once this function was traversed 'physical_monitors_in' iterator, we are in the + * end of the list of physical monitors parameters. So, it is important to do it once. + * + * @return True if monitors parameters were successfully discovered, False otherwise. + * @param physical_monitors_in D-bus iterator representing list of physical monitors. + * @param state Storage to put monitors state to. + * @param state_records_max Size of state storage. + * @param cPhysicalMonitors Actual number of physical displays parsed. + */ +static dbus_bool_t vbcl_hlp_gnome3_get_physical_monitors_state( + DBusMessageIter *physical_monitors_in, + vbcl_hlp_gnome3_physical_display_state *state, + uint32_t state_records_max, + uint32_t *cPhysicalMonitors) +{ + dbus_bool_t ret = true; + uint32_t iMonitor = 0; + + if ( !physical_monitors_in + || !state + || !cPhysicalMonitors) + { + return false; + } + + /* Validate signature. */ + if (!vbcl_hlp_gnome3_check_iter_signature(physical_monitors_in, "((ssss)a(siiddada{sv})a{sv})")) + return false; + + /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */ + AssertReturn(ret, false); + + do + { + char *connector = NULL; + char *vendor = NULL; + char *product = NULL; + char *physical_monitor_serial = NULL; + DBusMessageIter modes; + DBusMessageIter physical_monitor_properties; + + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_parse_physical_monitor_record( + physical_monitors_in, &connector, &vendor, &product, &physical_monitor_serial, + &modes, &physical_monitor_properties)); + + if (iMonitor < state_records_max) + { + state[iMonitor].connector = connector; + state[iMonitor].mode = vbcl_hlp_gnome3_lookup_monitor_current_mode(&modes); + + /* Check if both parameters were discovered successfully. */ + VBCL_HLP_GNOME3_NEXT(ret, state[iMonitor].connector && state[iMonitor].mode); + } + + iMonitor++; + + } + while (ret && dbus_message_iter_next(physical_monitors_in)); + + if (iMonitor >= state_records_max) + { + VBClLogError("physical monitors list is too big (%u)\n", iMonitor); + ret = false; + } + + *cPhysicalMonitors = iMonitor; + + return ret; +} + +/** + * Release monitors state resources. + * + * @param state Array of monitor states. + * @param cPhysicalMonitors Number of elements in array. + */ +static void vbcl_hlp_gnome3_free_physical_monitors_state( + vbcl_hlp_gnome3_physical_display_state *state, + uint32_t cPhysicalMonitors) +{ + if (!state || !cPhysicalMonitors) + return; + + for (uint32_t i = 0; i < cPhysicalMonitors; i++) + { + /* Only free() what we allocated ourselves. */ + if (state[i].mode) + free(state[i].mode); + } +} + +/** + * Add dictionary element with boolean value into an array. + * + * @return True on success, False otherwise. + * @param parent_iter Array to add dictionary element into. + * @param key Dictionary key. + * @param value Boolean value for given key. + */ +static dbus_bool_t vbcl_hlp_gnome3_add_dict_bool_entry( + DBusMessageIter *parent_iter, const char *key, const dbus_bool_t value) +{ + dbus_bool_t ret = true; + + DBusMessageIter sub_iter_key; + DBusMessageIter sub_iter_value; + + RT_ZERO(sub_iter_key); + RT_ZERO(sub_iter_value); + + /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */ + AssertReturn(ret, false); + + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(parent_iter, DBUS_TYPE_DICT_ENTRY, NULL, &sub_iter_key)); + { + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter_key, DBUS_TYPE_STRING, &key)); + + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter_key, ((int) 'v'), "b", &sub_iter_value)); + { + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter_value, DBUS_TYPE_BOOLEAN, &value)); + } + + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter_key, &sub_iter_value)); + } + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(parent_iter, &sub_iter_key)); + + return ret; +} + +/** + * This function is responsible for gathering current display + * information (via its helper functions), compose a payload + * for ApplyMonitorsConfig method and finally send configuration + * change to gnome-settings-daemon over D-bus. + * + * @return IPRT status code. + * @param connection Handle to D-bus connection. + * @param serial Serial number obtained from GetCurrentState interface, + * needs to be passed to ApplyMonitorsConfig. + * @param physical_monitors_in List of physical monitors (see GetCurrentState). + * @param logical_monitors_in List of logical monitors (see GetCurrentState). + * @param idPrimaryDisplay ID (number) of display which is requested to be set as primary. + */ +static int vbcl_hlp_gnome3_convert_and_apply_display_settings( + DBusConnection *connection, + uint32_t serial, + DBusMessageIter *physical_monitors_in, + DBusMessageIter *logical_monitors_in, + uint32_t idPrimaryDisplay) +{ + int rc = VERR_INVALID_PARAMETER; + uint32_t iLogicalMonitor = 0; + uint32_t cPhysicalMonitors = 0; + int32_t method = VBOXCLIENT_APPLY_DISPLAY_CONFIG_METHOD; + + dbus_bool_t ret = true; + DBusError error; + DBusMessage *reply = NULL;; + DBusMessage *message = NULL; + DBusMessageIter message_iter; + DBusMessageIter logical_monitors_out_iter; + DBusMessageIter properties_out_iter; + + struct vbcl_hlp_gnome3_physical_display_state + physical_monitors_state[VBOX_DRMIPC_MONITORS_MAX]; + + if ( !connection + || !physical_monitors_in + || !logical_monitors_in) + { + return VERR_INVALID_PARAMETER; + } + + /* Important for error handling code path when dbus_message_iter_abandon_container_if_open() is in place. */ + RT_ZERO(message_iter); + RT_ZERO(logical_monitors_out_iter); + RT_ZERO(properties_out_iter); + + message = dbus_message_new_method_call( + VBOXCLIENT_HELPER_DBUS_DESTINATION, + VBOXCLIENT_HELPER_DBUS_PATH, + VBOXCLIENT_HELPER_DBUS_IFACE, + VBOXCLIENT_HELPER_DBUS_APPLY_METHOD); + if (!message) + { + VBClLogError("unable to apply monitors config: no memory\n"); + return VERR_NO_MEMORY; + } + + /* Start composing payload for ApplyMonitorsConfig method: (uu@a(iiduba(ssa{sv}))@a{sv}). */ + dbus_message_iter_init_append(message, &message_iter); + + /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */ + AssertReturn(ret, VERR_INVALID_PARAMETER); + + /* Get list of physical monitors parameters. */ + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_get_physical_monitors_state( + physical_monitors_in, physical_monitors_state, VBOX_DRMIPC_MONITORS_MAX, &cPhysicalMonitors)); + + /* ( >u< u@a(iiduba(ssa{sv}))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */ + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_UINT32, &serial)); + /* (u >u< @a(iiduba(ssa{sv}))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */ + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_UINT32, &method)); + + /* Parameter "monitors" of method ApplyMonitorsConfig. + * Part (uu >@a(iiduba(ssa{sv}))< @a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */ + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&message_iter, DBUS_TYPE_ARRAY, "(iiduba(ssa{sv}))", &logical_monitors_out_iter)); + + /* Iterate over current configuration monitors (@logical_monitors + * parameter of GetCurrentState interface) and compose the rest part of message. */ + do + { + /* De-serialization parameters for @logical_monitors data (see GetCurrentState interface documentation). */ + int32_t x = 0; + int32_t y = 0; + double scale = 0; + uint32_t transform = 0; + dbus_bool_t primary = false; + dbus_bool_t isPrimary = false; + DBusMessageIter monitors; + DBusMessageIter properties; + + /* These iterators are used in order to compose sub-containers of the message. */ + DBusMessageIter sub_iter0; + DBusMessageIter sub_iter1; + DBusMessageIter sub_iter2; + DBusMessageIter sub_iter3; + /* Important for error handling code path when dbus_message_iter_abandon_container_if_open() is in place. */ + RT_ZERO(sub_iter0); + RT_ZERO(sub_iter1); + RT_ZERO(sub_iter2); + RT_ZERO(sub_iter3); + + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_parse_logical_monitor_record( + logical_monitors_in, &x, &y, &scale, &transform, &primary, &monitors, &properties)); + + if (ret) + { + /* Whether current display supposed to be set as primary. */ + isPrimary = (iLogicalMonitor == idPrimaryDisplay); + + /* Compose part (uu@a( > iiduba(ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */ + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&logical_monitors_out_iter, DBUS_TYPE_STRUCT, NULL, &sub_iter0)); + { + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_INT32, &x)); + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_INT32, &y)); + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_DOUBLE, &scale)); + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_UINT32, &transform)); + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter0, DBUS_TYPE_BOOLEAN, &isPrimary)); + + /* Compose part (uu@a(iidub > a(ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */ + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter0, DBUS_TYPE_ARRAY, "(ssa{sv})", &sub_iter1)); + { + /* Compose part (uu@a(iiduba > (ssa{sv}) < )@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */ + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter1, DBUS_TYPE_STRUCT, NULL, &sub_iter2)); + { + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter2, DBUS_TYPE_STRING, &physical_monitors_state[iLogicalMonitor].connector)); + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_append_basic(&sub_iter2, DBUS_TYPE_STRING, &physical_monitors_state[iLogicalMonitor].mode)); + + /* Compose part (uu@a(iiduba(ss > a{sv} < ))@a{sv}) of (uu@a(iiduba(ssa{sv}))@a{sv}). */ + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&sub_iter2, DBUS_TYPE_ARRAY, "{sv}", &sub_iter3)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_add_dict_bool_entry(&sub_iter3, "is-current", true)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_add_dict_bool_entry(&sub_iter3, "is-preferred", true)); + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter2, &sub_iter3)); + } + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter1, &sub_iter2)); + } + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&sub_iter0, &sub_iter1)); + } + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&logical_monitors_out_iter, &sub_iter0)); + + iLogicalMonitor++; + + if (!ret) + { + dbus_message_iter_abandon_container_if_open(&sub_iter2, &sub_iter3); + dbus_message_iter_abandon_container_if_open(&sub_iter1, &sub_iter2); + dbus_message_iter_abandon_container_if_open(&sub_iter0, &sub_iter1); + dbus_message_iter_abandon_container_if_open(&logical_monitors_out_iter, &sub_iter0); + } + } + else + { + break; + } + + } + while (ret && dbus_message_iter_next(logical_monitors_in)); + + /* Finish with parameter "monitors" of method ApplyMonitorsConfig. */ + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&message_iter, &logical_monitors_out_iter)); + + /* Parameter "properties" of method ApplyMonitorsConfig (empty dict). + * Part (uu@a(iiduba(ssa{sv})) >@a{sv}< ) of (uu@a(iiduba(ssa{sv}))@a{sv}).*/ + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_open_container(&message_iter, DBUS_TYPE_ARRAY, "{sv}", &properties_out_iter)); + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_close_container(&message_iter, &properties_out_iter)); + + if (ret) + { + dbus_error_init(&error); + + reply = dbus_connection_send_with_reply_and_block(connection, message, VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS, &error); + if (reply) + { + VBClLogInfo("display %d has been set as primary\n", idPrimaryDisplay); + dbus_message_unref(reply); + rc = VINF_SUCCESS; + } + else + { + VBClLogError("unable to apply monitors config: %s\n", + dbus_error_is_set(&error) ? error.message : "unknown error"); + dbus_error_free(&error); + rc = VERR_INVALID_PARAMETER; + } + } + else + { + VBClLogError("unable to apply monitors config: cannot compose monitors config\n"); + + dbus_message_iter_abandon_container_if_open(&message_iter, &logical_monitors_out_iter); + dbus_message_iter_abandon_container_if_open(&message_iter, &properties_out_iter); + + rc = VERR_INVALID_PARAMETER; + } + + /* Clean physical monitors state. */ + vbcl_hlp_gnome3_free_physical_monitors_state(physical_monitors_state, cPhysicalMonitors); + + dbus_message_unref(message); + + return rc; +} + +/** + * This function parses GetCurrentState interface call reply and passes it for further processing. + * + * @return IPRT status code. + * @param connection Handle to D-bus connection. + * @param idPrimaryDisplay ID (number) of display which is requested to be set as primary. + * @param reply Reply message of GetCurrentState call. + */ +static int vbcl_hlp_gnome3_process_current_display_layout( + DBusConnection *connection, uint32_t idPrimaryDisplay, DBusMessage *reply) +{ + static const char *expected_signature = "ua((ssss)a(siiddada{sv})a{sv})a(iiduba(ssss)a{sv})a{sv}"; + + dbus_bool_t ret = true; + DBusMessageIter iter; + int rc = VERR_GENERAL_FAILURE; + + uint32_t serial = 0; + DBusMessageIter monitors; + DBusMessageIter logical_monitors_in; + DBusMessageIter properties; + + if (!reply) + { + return VERR_INVALID_PARAMETER; + } + + /* Parse VBOXCLIENT_HELPER_DBUS_GET_METHOD reply payload: + * + * (u@a((ssss)a(siiddada{sv})a{sv})@a(iiduba(ssss)a{sv})@a{sv}). + * + * Method return the following arguments: monitors, logical_monitors, properties. + */ + + /* Should be TRUE in order to satisfy VBCL_HLP_GNOME3_NEXT() requirements. */ + AssertReturn(ret, VERR_INVALID_PARAMETER); + + /* Important: in order to avoid libdbus asserts during parsing, its signature should be verified at first. */ + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_check_message_signature(reply, expected_signature)); + + VBCL_HLP_GNOME3_NEXT(ret, dbus_message_iter_init(reply, &iter)); + if (ret) + { + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_basic(&iter, DBUS_TYPE_UINT32, &serial)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &monitors)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &logical_monitors_in)); + VBCL_HLP_GNOME3_NEXT(ret, vbcl_hlp_gnome3_iter_get_array(&iter, &properties)); + + /* Make sure there are no more arguments. */ + if (ret && !dbus_message_iter_has_next(&iter)) + { + rc = vbcl_hlp_gnome3_convert_and_apply_display_settings( + connection, serial, &monitors, &logical_monitors_in, idPrimaryDisplay); + } + else + { + VBClLogError("cannot fetch current displays configuration: incorrect number of arguments\n"); + rc = VERR_INVALID_PARAMETER; + } + } + else + { + VBClLogError("cannot fetch current displays configuration: no data\n"); + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + +/** + * This function establishes D-bus connection, requests gnome-settings-daemon + * to provide current display configuration via GetCurrentState interface call + * and passes this information further to helper functions in order to set + * requested display as primary. + * + * @return IPRT status code. + * @param idPrimaryDisplay A display ID which is requested to be set as primary. + */ +static DECLCALLBACK(int) vbcl_hlp_gnome3_set_primary_display(uint32_t idPrimaryDisplay) +{ + int rc = VERR_GENERAL_FAILURE; + + DBusConnection *connection = NULL; + DBusMessage *message = NULL; + DBusError error; + + rc = RTDBusLoadLib(); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to load D-bus library\n"); + return VERR_SYMBOL_NOT_FOUND; + } + + dbus_error_init(&error); + connection = dbus_bus_get(DBUS_BUS_SESSION, &error); + if (!dbus_error_is_set(&error)) + { + message = dbus_message_new_method_call( + VBOXCLIENT_HELPER_DBUS_DESTINATION, + VBOXCLIENT_HELPER_DBUS_PATH, + VBOXCLIENT_HELPER_DBUS_IFACE, + VBOXCLIENT_HELPER_DBUS_GET_METHOD); + + if (message) + { + DBusMessage *reply; + + reply = dbus_connection_send_with_reply_and_block(connection, message, VBOXCLIENT_HELPER_DBUS_TIMEOUT_MS, &error); + if (!dbus_error_is_set(&error)) + { + rc = vbcl_hlp_gnome3_process_current_display_layout(connection, idPrimaryDisplay, reply); + dbus_message_unref(reply); + } + else + { + VBClLogError("unable to get current display configuration: %s\n", error.message); + dbus_error_free(&error); + rc = VERR_INVALID_PARAMETER; + } + + dbus_message_unref(message); + } + else + { + VBClLogError("unable to get current display configuration: no memory\n"); + rc = VERR_NO_MEMORY; + } + + dbus_connection_flush(connection); + } + else + { + VBClLogError("unable to establish dbus connection: %s\n", error.message); + dbus_error_free(&error); + rc = VERR_INVALID_HANDLE; + } + + return rc; +} + +/** + * @interface_method_impl{VBCLDISPLAYHELPER,pfnProbe} + */ +static DECLCALLBACK(int) vbcl_hlp_gnome3_probe(void) +{ + const char *pszCurrentDesktop = RTEnvGet(VBCL_ENV_XDG_CURRENT_DESKTOP); + + /* GNOME3 identifies itself by XDG_CURRENT_DESKTOP environment variable. + * It can slightly vary for different distributions, but we assume that this + * variable should at least contain sub-string 'GNOME' in its value. */ + if (pszCurrentDesktop && RTStrStr(pszCurrentDesktop, "GNOME")) + return VINF_SUCCESS; + + return VERR_NOT_FOUND; +} + +/** + * @interface_method_impl{VBCLDISPLAYHELPER,pfnInit} + */ +static DECLCALLBACK(int) vbcl_hlp_gnome3_init(void) +{ + int rc; + + if (!VBClHasWayland()) + { + rc = vbcl_hlp_generic_init(); + VBClLogInfo("attempt to start generic helper routines, rc=%Rrc\n", rc); + } + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VBCLDISPLAYHELPER,pfnTerm} + */ +static DECLCALLBACK(int) vbcl_hlp_gnome3_term(void) +{ + int rc; + + if (!VBClHasWayland()) + { + rc = vbcl_hlp_generic_term(); + VBClLogInfo("attempt to stop generic helper routines, rc=%Rrc\n", rc); + } + + return VINF_SUCCESS; +} + +/* Helper callbacks. */ +const VBCLDISPLAYHELPER g_DisplayHelperGnome3 = +{ + "GNOME3", /* .pszName */ + vbcl_hlp_gnome3_probe, /* .pfnProbe */ + vbcl_hlp_gnome3_init, /* .pfnInit */ + vbcl_hlp_gnome3_term, /* .pfnTerm */ + vbcl_hlp_gnome3_set_primary_display, /* .pfnSetPrimaryDisplay */ + vbcl_hlp_generic_subscribe_display_offset_changed, /* .pfnSubscribeDisplayOffsetChangeNotification */ + vbcl_hlp_generic_unsubscribe_display_offset_changed, /* .pfnUnsubscribeDisplayOffsetChangeNotification */ +}; diff --git a/src/VBox/Additions/x11/VBoxClient/display-helper.h b/src/VBox/Additions/x11/VBoxClient/display-helper.h new file mode 100644 index 00000000..81fae51c --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-helper.h @@ -0,0 +1,130 @@ +/* $Id: display-helper.h $ */ +/** @file + * Guest Additions - Definitions for Desktop Environment helpers. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_x11_VBoxClient_display_helper_h +#define GA_INCLUDED_SRC_x11_VBoxClient_display_helper_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "display-ipc.h" + +/** + * Display offsets change notification callback. + * + * @returns IPRT status code. + * @param cDisplays Number of displays which have changed their offset. + * @param aDisplays Displays offset data. + */ +typedef DECLCALLBACKTYPE(int, FNDISPLAYOFFSETCHANGE, (uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays)); + +/** + * Desktop Environment helper definition structure. + */ +typedef struct +{ + /** A short helper name. 16 chars maximum (RTTHREAD_NAME_LEN). */ + const char *pszName; + + /** + * Probing callback. + * + * Called in attempt to detect if user is currently running Desktop Environment + * which is compatible with the helper. + * + * @returns IPRT status code. + */ + DECLCALLBACKMEMBER(int, pfnProbe, (void)); + + /** + * Initialization callback. + * + * @returns IPRT status code. + */ + DECLCALLBACKMEMBER(int, pfnInit, (void)); + + /** + * Termination callback. + * + * @returns IPRT status code. + */ + DECLCALLBACKMEMBER(int, pfnTerm, (void)); + + /** + * Set primary display in Desktop Environment specific way. + * + * @returns IPRT status code. + * @param idDisplay Display ID which should be set as primary. + */ + DECLCALLBACKMEMBER(int, pfnSetPrimaryDisplay, (uint32_t idDisplay)); + + /** + * Register notification callback for display offsets change event. + * + * @param pfnCb Notification callback. + */ + DECLCALLBACKMEMBER(void, pfnSubscribeDisplayOffsetChangeNotification, (FNDISPLAYOFFSETCHANGE *pfnCb)); + + /** + * Unregister notification callback for display offsets change event. + */ + DECLCALLBACKMEMBER(void, pfnUnsubscribeDisplayOffsetChangeNotification, (void)); + +} VBCLDISPLAYHELPER; + +/** + * Initialization callback for generic Desktop Environment helper. + * + * @returns IPRT status code. + */ +RTDECL(int) vbcl_hlp_generic_init(void); + +/** + * Termination callback for generic Desktop Environment helper. + * + * @returns IPRT status code. + */ +RTDECL(int) vbcl_hlp_generic_term(void); + +/** + * Subscribe to display offset change notifications emitted by Generic Desktop Environment helper. + * + * @param pfnCb A pointer to callback function which will be triggered when event arrives. + */ +RTDECL(void) vbcl_hlp_generic_subscribe_display_offset_changed(FNDISPLAYOFFSETCHANGE *pfnCb); + +/** + * Unsubscribe from display offset change notifications emitted by Generic Desktop Environment helper. + */ +RTDECL(void) vbcl_hlp_generic_unsubscribe_display_offset_changed(void); + +/** GNOME3 helper private data. */ +extern const VBCLDISPLAYHELPER g_DisplayHelperGnome3; +/** Generic helper private data. */ +extern const VBCLDISPLAYHELPER g_DisplayHelperGeneric; + +#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_display_helper_h */ diff --git a/src/VBox/Additions/x11/VBoxClient/display-ipc.cpp b/src/VBox/Additions/x11/VBoxClient/display-ipc.cpp new file mode 100644 index 00000000..5f5d5c8b --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-ipc.cpp @@ -0,0 +1,451 @@ +/* $Id: display-ipc.cpp $ */ +/** @file + * Guest Additions - DRM IPC communication core functions. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/* + * This module implements connection handling routine which is common for + * both IPC server and client (see vbDrmIpcConnectionHandler()). This function + * at first tries to read incoming command from IPC socket and if no data has + * arrived within VBOX_DRMIPC_RX_TIMEOUT_MS, it checks is there is some data in + * TX queue and sends it. TX queue and IPC connection handle is unique per IPC + * client and handled in a separate thread of either server or client process. + * + * Logging is implemented in a way that errors are always printed out, + * VBClLogVerbose(2) is used for debugging purposes and reflects what is related to + * IPC communication. In order to see logging on a host side it is enough to do: + * + * echo 1 > /sys/module/vboxguest/parameters/r3_log_to_host. + */ + +#include "VBoxClient.h" +#include "display-ipc.h" + +#include <VBox/VBoxGuestLib.h> + +#include <iprt/localipc.h> +#include <iprt/err.h> +#include <iprt/crc.h> +#include <iprt/mem.h> +#include <iprt/asm.h> +#include <iprt/critsect.h> +#include <iprt/assert.h> + +#include <grp.h> +#include <pwd.h> +#include <errno.h> +#include <limits.h> +#include <unistd.h> + +/** + * Calculate size of TX list entry. + * + * TX list entry consists of RTLISTNODE, DRM IPC message header and message payload. + * Given IpcCmd already includes message header and payload. So, TX list entry size + * equals to size of IpcCmd plus size of RTLISTNODE. + * + * @param IpcCmd A structure which represents DRM IPC command. + */ +#define DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(IpcCmd) (sizeof(IpcCmd) + RT_UOFFSETOF(VBOX_DRMIPC_TX_LIST_ENTRY, Hdr)) + +/** + * Initialize IPC client private data. + * + * @return IPRT status code. + * @param pClient IPC client private data to be initialized. + * @param hThread A thread which server IPC client connection. + * @param hClientSession IPC session handle obtained from RTLocalIpcSessionXXX(). + * @param cTxListCapacity Maximum number of messages which can be queued for TX for this IPC session. + * @param pfnRxCb IPC RX callback function pointer. + */ +RTDECL(int) vbDrmIpcClientInit(PVBOX_DRMIPC_CLIENT pClient, RTTHREAD hThread, RTLOCALIPCSESSION hClientSession, + uint32_t cTxListCapacity, PFNDRMIPCRXCB pfnRxCb) +{ + AssertReturn(pClient, VERR_INVALID_PARAMETER); + AssertReturn(hThread, VERR_INVALID_PARAMETER); + AssertReturn(hClientSession, VERR_INVALID_PARAMETER); + AssertReturn(cTxListCapacity, VERR_INVALID_PARAMETER); + AssertReturn(pfnRxCb, VERR_INVALID_PARAMETER); + + pClient->hThread = hThread; + pClient->hClientSession = hClientSession; + + RT_ZERO(pClient->TxList); + RTListInit(&pClient->TxList.Node); + + pClient->cTxListCapacity = cTxListCapacity; + ASMAtomicWriteU32(&pClient->cTxListSize, 0); + + pClient->pfnRxCb = pfnRxCb; + + return RTCritSectInit(&pClient->CritSect); +} + +/** + * Releases IPC client private data resources. + * + * @return IPRT status code. + * @param pClient IPC session private data to be initialized. + */ +RTDECL(int) vbDrmIpcClientReleaseResources(PVBOX_DRMIPC_CLIENT pClient) +{ + PVBOX_DRMIPC_TX_LIST_ENTRY pEntry, pNextEntry; + int rc; + + pClient->hClientSession = 0; + + rc = RTCritSectEnter(&pClient->CritSect); + if (RT_SUCCESS(rc)) + { + if (!RTListIsEmpty(&pClient->TxList.Node)) + { + RTListForEachSafe(&pClient->TxList.Node, pEntry, pNextEntry, VBOX_DRMIPC_TX_LIST_ENTRY, Node) + { + RTListNodeRemove(&pEntry->Node); + RTMemFree(pEntry); + ASMAtomicDecU32(&pClient->cTxListSize); + } + } + + rc = RTCritSectLeave(&pClient->CritSect); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectDelete(&pClient->CritSect); + if (RT_FAILURE(rc)) + VBClLogError("vbDrmIpcClientReleaseResources: unable to delete critical section, rc=%Rrc\n", rc); + } + else + VBClLogError("vbDrmIpcClientReleaseResources: unable to leave critical section, rc=%Rrc\n", rc); + } + else + VBClLogError("vbDrmIpcClientReleaseResources: unable to enter critical section, rc=%Rrc\n", rc); + + Assert(ASMAtomicReadU32(&pClient->cTxListSize) == 0); + + RT_ZERO(*pClient); + + return rc; +} + +/** + * Add message to IPC session TX queue. + * + * @return IPRT status code. + * @param pClient IPC session private data. + * @param pEntry Pointer to the message. + */ +static int vbDrmIpcSessionScheduleTx(PVBOX_DRMIPC_CLIENT pClient, PVBOX_DRMIPC_TX_LIST_ENTRY pEntry) +{ + int rc; + + AssertReturn(pClient, VERR_INVALID_PARAMETER); + AssertReturn(pEntry, VERR_INVALID_PARAMETER); + + rc = RTCritSectEnter(&pClient->CritSect); + if (RT_SUCCESS(rc)) + { + if (pClient->cTxListSize < pClient->cTxListCapacity) + { + RTListAppend(&pClient->TxList.Node, &pEntry->Node); + pClient->cTxListSize++; + } + else + VBClLogError("vbDrmIpcSessionScheduleTx: TX queue is full\n"); + + int rc2 = RTCritSectLeave(&pClient->CritSect); + if (RT_FAILURE(rc2)) + VBClLogError("vbDrmIpcSessionScheduleTx: cannot leave critical section, rc=%Rrc\n", rc2); + } + else + VBClLogError("vbDrmIpcSessionScheduleTx: cannot enter critical section, rc=%Rrc\n", rc); + + return rc; +} + +/** + * Pick up message from TX queue if available. + * + * @return Pointer to list entry or NULL if queue is empty. + */ +static PVBOX_DRMIPC_TX_LIST_ENTRY vbDrmIpcSessionPickupTxMessage(PVBOX_DRMIPC_CLIENT pClient) +{ + PVBOX_DRMIPC_TX_LIST_ENTRY pEntry = NULL; + int rc; + + AssertReturn(pClient, NULL); + + rc = RTCritSectEnter(&pClient->CritSect); + if (RT_SUCCESS(rc)) + { + if (!RTListIsEmpty(&pClient->TxList.Node)) + { + pEntry = (PVBOX_DRMIPC_TX_LIST_ENTRY)RTListRemoveFirst(&pClient->TxList.Node, VBOX_DRMIPC_TX_LIST_ENTRY, Node); + pClient->cTxListSize--; + Assert(pEntry); + } + + int rc2 = RTCritSectLeave(&pClient->CritSect); + if (RT_FAILURE(rc2)) + VBClLogError("vbDrmIpcSessionPickupTxMessage: cannot leave critical section, rc=%Rrc\n", rc2); + } + else + VBClLogError("vbDrmIpcSessionPickupTxMessage: cannot enter critical section, rc=%Rrc\n", rc); + + return pEntry; +} + +RTDECL(int) vbDrmIpcAuth(RTLOCALIPCSESSION hClientSession) +{ + int rc = VERR_ACCESS_DENIED; + RTUID uUid; + struct group *pAllowedGroup; + + AssertReturn(hClientSession, VERR_INVALID_PARAMETER); + + /* Get DRM IPC user group entry from system database. */ + pAllowedGroup = getgrnam(VBOX_DRMIPC_USER_GROUP); + if (!pAllowedGroup) + return RTErrConvertFromErrno(errno); + + /* Get remote user ID and check if it is in allowed user group. */ + rc = RTLocalIpcSessionQueryUserId(hClientSession, &uUid); + if (RT_SUCCESS(rc)) + { + /* Get user record from system database and look for it in group's members list. */ + struct passwd *UserRecord = getpwuid(uUid); + + if (UserRecord && UserRecord->pw_name) + { + while (*pAllowedGroup->gr_mem) + { + if (RTStrNCmp(*pAllowedGroup->gr_mem, UserRecord->pw_name, LOGIN_NAME_MAX) == 0) + return VINF_SUCCESS; + + pAllowedGroup->gr_mem++; + } + } + } + + return rc; +} + +RTDECL(int) vbDrmIpcSetPrimaryDisplay(PVBOX_DRMIPC_CLIENT pClient, uint32_t idDisplay) +{ + int rc = VERR_GENERAL_FAILURE; + + PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry = + (PVBOX_DRMIPC_TX_LIST_ENTRY)RTMemAllocZ(DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)); + + if (pTxListEntry) + { + PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)(&pTxListEntry->Hdr); + + pCmd->Hdr.idCmd = VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY; + pCmd->Hdr.cbData = sizeof(VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY); + pCmd->idDisplay = idDisplay; + pCmd->Hdr.u64Crc = RTCrc64(pCmd, pCmd->Hdr.cbData); + Assert(pCmd->Hdr.u64Crc); + + /* Put command into queue and trigger TX. */ + rc = vbDrmIpcSessionScheduleTx(pClient, pTxListEntry); + if (RT_SUCCESS(rc)) + { + VBClLogVerbose(2, "vbDrmIpcSetPrimaryDisplay: %u bytes scheduled for TX, crc=0x%x\n", pCmd->Hdr.cbData, pCmd->Hdr.u64Crc); + } + else + { + RTMemFree(pTxListEntry); + VBClLogError("vbDrmIpcSetPrimaryDisplay: unable to schedule TX, rc=%Rrc\n", rc); + } + } + else + { + VBClLogInfo("cannot allocate SET_PRIMARY_DISPLAY command\n"); + rc = VERR_NO_MEMORY; + } + + return rc; +} + +/** + * Report to IPC server that display layout offsets have been changed (called by IPC client). + * + * @return IPRT status code. + * @param pClient IPC session private data. + * @param cDisplays Number of monitors which have offsets changed. + * @param aDisplays Offsets data. + */ +RTDECL(int) vbDrmIpcReportDisplayOffsets(PVBOX_DRMIPC_CLIENT pClient, uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays) +{ + int rc = VERR_GENERAL_FAILURE; + + PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry = + (PVBOX_DRMIPC_TX_LIST_ENTRY)RTMemAllocZ( + DRMIPCCOMMAND_TX_LIST_ENTRY_SIZE(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS)); + + if (pTxListEntry) + { + PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS pCmd = (PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS)(&pTxListEntry->Hdr); + + pCmd->Hdr.idCmd = VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS; + pCmd->Hdr.cbData = sizeof(VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS); + pCmd->cDisplays = cDisplays; + memcpy(pCmd->aDisplays, aDisplays, cDisplays * sizeof(struct VBOX_DRMIPC_VMWRECT)); + pCmd->Hdr.u64Crc = RTCrc64(pCmd, pCmd->Hdr.cbData); + Assert(pCmd->Hdr.u64Crc); + + /* Put command into queue and trigger TX. */ + rc = vbDrmIpcSessionScheduleTx(pClient, pTxListEntry); + if (RT_SUCCESS(rc)) + { + VBClLogVerbose(2, "vbDrmIpcReportDisplayOffsets: %u bytes scheduled for TX, crc=0x%x\n", pCmd->Hdr.cbData, pCmd->Hdr.u64Crc); + } + else + { + RTMemFree(pTxListEntry); + VBClLogError("vbDrmIpcReportDisplayOffsets: unable to schedule TX, rc=%Rrc\n", rc); + } + } + else + { + VBClLogInfo("cannot allocate REPORT_DISPLAY_OFFSETS command\n"); + rc = VERR_NO_MEMORY; + } + + return rc; +} + +/** + * Common function for both IPC server and client which is responsible + * for handling IPC communication flow. + * + * @return IPRT status code. + * @param pClient IPC connection private data. + */ +RTDECL(int) vbDrmIpcConnectionHandler(PVBOX_DRMIPC_CLIENT pClient) +{ + int rc; + static uint8_t aInputBuf[VBOX_DRMIPC_RX_BUFFER_SIZE]; + size_t cbRead = 0; + PVBOX_DRMIPC_TX_LIST_ENTRY pTxListEntry; + + AssertReturn(pClient, VERR_INVALID_PARAMETER); + + /* Make sure we are still connected to IPC server. */ + if (!pClient->hClientSession) + { + VBClLogVerbose(2, "connection to IPC server lost\n"); + return VERR_NET_CONNECTION_RESET_BY_PEER; + } + + AssertReturn(pClient->pfnRxCb, VERR_INVALID_PARAMETER); + + /* Make sure we have valid connection handle. By reporting VERR_BROKEN_PIPE, + * we trigger reconnect to IPC server. */ + if (!RT_VALID_PTR(pClient->hClientSession)) + return VERR_BROKEN_PIPE; + + rc = RTLocalIpcSessionWaitForData(pClient->hClientSession, VBOX_DRMIPC_RX_TIMEOUT_MS); + if (RT_SUCCESS(rc)) + { + /* Read IPC message header. */ + rc = RTLocalIpcSessionRead(pClient->hClientSession, aInputBuf, sizeof(VBOX_DRMIPC_COMMAND_HEADER), &cbRead); + if (RT_SUCCESS(rc)) + { + if (cbRead == sizeof(VBOX_DRMIPC_COMMAND_HEADER)) + { + PVBOX_DRMIPC_COMMAND_HEADER pHdr = (PVBOX_DRMIPC_COMMAND_HEADER)aInputBuf; + if (pHdr) + { + AssertReturn(pHdr->cbData <= sizeof(aInputBuf) - sizeof(VBOX_DRMIPC_COMMAND_HEADER), VERR_INVALID_PARAMETER); + + /* Read the rest of a message. */ + rc = RTLocalIpcSessionRead(pClient->hClientSession, aInputBuf + sizeof(VBOX_DRMIPC_COMMAND_HEADER), pHdr->cbData - sizeof(VBOX_DRMIPC_COMMAND_HEADER), &cbRead); + AssertRCReturn(rc, rc); + AssertReturn(cbRead == (pHdr->cbData - sizeof(VBOX_DRMIPC_COMMAND_HEADER)), VERR_INVALID_PARAMETER); + + uint64_t u64Crc = pHdr->u64Crc; + + /* Verify checksum. */ + pHdr->u64Crc = 0; + if (u64Crc != 0 && RTCrc64(pHdr, pHdr->cbData) == u64Crc) + { + /* Restore original CRC. */ + pHdr->u64Crc = u64Crc; + + /* Trigger RX callback. */ + rc = pClient->pfnRxCb(pHdr->idCmd, (void *)pHdr, pHdr->cbData); + VBClLogVerbose(2, "command 0x%X executed, rc=%Rrc\n", pHdr->idCmd, rc); + } + else + { + VBClLogError("unable to read from IPC: CRC mismatch, provided crc=0x%X, cmd=0x%X\n", u64Crc, pHdr->idCmd); + rc = VERR_NOT_EQUAL; + } + } + else + { + VBClLogError("unable to read from IPC: zero data received\n"); + rc = VERR_INVALID_PARAMETER; + } + } + else + { + VBClLogError("received partial IPC message header (%u bytes)\n", cbRead); + rc = VERR_INVALID_PARAMETER; + } + + VBClLogVerbose(2, "received %u bytes from IPC\n", cbRead); + } + else + { + VBClLogError("unable to read from IPC, rc=%Rrc\n", rc); + } + } + + /* Check if TX queue has some messages to transfer. */ + while ((pTxListEntry = vbDrmIpcSessionPickupTxMessage(pClient)) != NULL) + { + PVBOX_DRMIPC_COMMAND_HEADER pMessageHdr = (PVBOX_DRMIPC_COMMAND_HEADER)(&pTxListEntry->Hdr); + Assert(pMessageHdr); + + rc = RTLocalIpcSessionWrite( + pClient->hClientSession, (void *)(&pTxListEntry->Hdr), pMessageHdr->cbData); + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcSessionFlush(pClient->hClientSession); + if (RT_SUCCESS(rc)) + VBClLogVerbose(2, "vbDrmIpcConnectionHandler: transferred %u bytes\n", pMessageHdr->cbData); + else + VBClLogError("vbDrmIpcConnectionHandler: cannot flush IPC connection, transfer of %u bytes failed\n", pMessageHdr->cbData); + } + else + VBClLogError("vbDrmIpcConnectionHandler: cannot TX, rc=%Rrc\n", rc); + + RTMemFree(pTxListEntry); + } + + return rc; +} diff --git a/src/VBox/Additions/x11/VBoxClient/display-ipc.h b/src/VBox/Additions/x11/VBoxClient/display-ipc.h new file mode 100644 index 00000000..39e9760e --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-ipc.h @@ -0,0 +1,242 @@ +/* $Id: display-ipc.h $ */ +/** @file + * Guest Additions - DRM IPC communication core function definitions. + * + * Definitions for IPC communication in between VBoxDRMClient and VBoxClient. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_x11_VBoxClient_display_ipc_h +#define GA_INCLUDED_SRC_x11_VBoxClient_display_ipc_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +# include <iprt/assert.h> +# include <iprt/localipc.h> +# include <iprt/critsect.h> +# include <iprt/list.h> + +/** Name of DRM IPC server.*/ +# define VBOX_DRMIPC_SERVER_NAME "DRMIpcServer" +/** A user group which is allowed to connect to IPC server. */ +#define VBOX_DRMIPC_USER_GROUP "vboxdrmipc" +/** Time in milliseconds to wait for host events. */ +#define VBOX_DRMIPC_RX_TIMEOUT_MS (500) +/** Time in milliseconds to relax in between unsuccessful connect attempts. */ +#define VBOX_DRMIPC_RX_RELAX_MS (500) +/** Size of RX buffer for IPC communication. */ +#define VBOX_DRMIPC_RX_BUFFER_SIZE (1024) +/** Maximum amount of TX messages which can be queued. */ +#define VBOX_DRMIPC_TX_QUEUE_SIZE (64) +/** Maximum number of physical monitor configurations we can process. */ +#define VBOX_DRMIPC_MONITORS_MAX (32) + +/** Rectangle structure for geometry of a single screen. */ +struct VBOX_DRMIPC_VMWRECT +{ + /** Monitor X offset. */ + int32_t x; + /** Monitor Y offset. */ + int32_t y; + /** Monitor width. */ + uint32_t w; + /** Monitor height. */ + uint32_t h; +}; +AssertCompileSize(struct VBOX_DRMIPC_VMWRECT, 16); + +/** List of IPC commands issued by client to server. */ +typedef enum VBOXDRMIPCSRVCMD +{ + /** Separate server and client commands by starting index. */ + VBOXDRMIPCSRVCMD_INVALID = 0x00, + /** Client reports list of current display offsets. */ + VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS, + /** Termination of commands list. */ + VBOXDRMIPCSRVCMD_MAX +} VBOXDRMIPCSRVCMD; + +/** List of IPC commands issued by server to client. */ +typedef enum VBOXDRMIPCCLTCMD +{ + /** Separate server and client commands by starting index. */ + VBOXDRMIPCCLTCMD_INVALID = 0x7F, + /** Server requests client to set primary screen. */ + VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY, + /** Termination of commands list. */ + VBOXDRMIPCCLTCMD_MAX +} VBOXDRMIPCCLTCMD; + +/** IPC command header. */ +typedef struct VBOX_DRMIPC_COMMAND_HEADER +{ + /** IPC command structure checksum, includes header and payload. */ + uint64_t u64Crc; + /** IPC command identificator (opaque). */ + uint8_t idCmd; + /** Size of payload data. */ + uint64_t cbData; + +} VBOX_DRMIPC_COMMAND_HEADER; + +/** Pointer to IPC command header. */ +typedef VBOX_DRMIPC_COMMAND_HEADER *PVBOX_DRMIPC_COMMAND_HEADER; + +/** IPC command VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY payload. */ +typedef struct VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY +{ + /* IPC command header. */ + VBOX_DRMIPC_COMMAND_HEADER Hdr; + /** ID of display to be set as primary. */ + uint32_t idDisplay; + +} VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY; + +/** Pointer to IPC command DRMIPCCOMMAND_SET_PRIMARY_DISPLAY payload. */ +typedef VBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY *PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY; + +/** IPC command VBOXDRMIPCSRVCMD_REPORT_DISPLAY_OFFSETS payload. */ +typedef struct VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS +{ + /* IPC command header. */ + VBOX_DRMIPC_COMMAND_HEADER Hdr; + /** Number of displays which have changed offsets. */ + uint32_t cDisplays; + /** Offsets data. */ + struct VBOX_DRMIPC_VMWRECT aDisplays[VBOX_DRMIPC_MONITORS_MAX]; +} VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS; + +/** Pointer to IPC command DRMIPCCOMMAND_SET_PRIMARY_DISPLAY payload. */ +typedef VBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS *PVBOX_DRMIPC_COMMAND_REPORT_DISPLAY_OFFSETS; + +/** DRM IPC TX list entry. */ +typedef struct VBOX_DRMIPC_TX_LIST_ENTRY +{ + /** The list node. */ + RTLISTNODE Node; + /* IPC command header. */ + VBOX_DRMIPC_COMMAND_HEADER Hdr; +} VBOX_DRMIPC_TX_LIST_ENTRY; + +/** Pointer to DRM IPC TX list entry. */ +typedef VBOX_DRMIPC_TX_LIST_ENTRY *PVBOX_DRMIPC_TX_LIST_ENTRY; + +/** + * A callback function which is called by IPC client session thread when new message arrives. + * + * @returns IPRT status code. + * @param idCmd Command ID to be executed (opaque). + * @param pvData Command specific argument data. + * @param cbData Size of command argument data as received over IPC. + */ +typedef DECLCALLBACKTYPE(int, FNDRMIPCRXCB, (uint8_t idCmd, void *pvData, uint32_t cbData)); + +/** Pointer to FNDRMIPCRXCB. */ +typedef FNDRMIPCRXCB *PFNDRMIPCRXCB; + +/** IPC session private data. */ +typedef struct VBOX_DRMIPC_CLIENT +{ + /** Thread handle which dispatches this IPC client session. */ + RTTHREAD hThread; + /** IPC session handle. */ + RTLOCALIPCSESSION hClientSession; + /** TX message queue mutex. */ + RTCRITSECT CritSect; + /** TX message queue (accessed under critsect). */ + VBOX_DRMIPC_TX_LIST_ENTRY TxList; + /** Maximum number of messages which can be queued to TX message queue. */ + uint32_t cTxListCapacity; + /** Actual number of messages currently queued to TX message queue (accessed under critsect). */ + uint32_t cTxListSize; + /** IPC RX callback. */ + PFNDRMIPCRXCB pfnRxCb; +} VBOX_DRMIPC_CLIENT; + +/** Pointer to IPC session private data. */ +typedef VBOX_DRMIPC_CLIENT *PVBOX_DRMIPC_CLIENT; + +/** Static initializer for VBOX_DRMIPC_CLIENT. */ +#define VBOX_DRMIPC_CLIENT_INITIALIZER { NIL_RTTHREAD, 0, { 0 }, { { NULL, NULL }, {0, 0, 0} }, 0, 0, NULL } + +/** + * Initialize IPC client private data. + * + * @return IPRT status code. + * @param pClient IPC client private data to be initialized. + * @param hThread A thread which server IPC client connection. + * @param hClientSession IPC session handle obtained from RTLocalIpcSessionXXX(). + * @param cTxListCapacity Maximum number of messages which can be queued for TX for this IPC session. + * @param pfnRxCb IPC RX callback function pointer. + */ +RTDECL(int) vbDrmIpcClientInit(PVBOX_DRMIPC_CLIENT pClient, RTTHREAD hThread, RTLOCALIPCSESSION hClientSession, + uint32_t cTxListCapacity, PFNDRMIPCRXCB pfnRxCb); + +/** + * Releases IPC client private data resources. + * + * @return IPRT status code. + * @param pClient IPC session private data to be initialized. + */ +RTDECL(int) vbDrmIpcClientReleaseResources(PVBOX_DRMIPC_CLIENT pClient); + +/** + * Verify if remote IPC peer corresponds to a process which is running + * from allowed user. + * + * @return IPRT status code. + * @param hClientSession IPC session handle. + */ +RTDECL(int) vbDrmIpcAuth(RTLOCALIPCSESSION hClientSession); + +/** + * Common function for both IPC server and client which is responsible + * for handling IPC communication flow. + * + * @return IPRT status code. + * @param pClient IPC connection private data. + */ +RTDECL(int) vbDrmIpcConnectionHandler(PVBOX_DRMIPC_CLIENT pClient); + +/** + * Request remote IPC peer to set primary display. + * + * @return IPRT status code. + * @param pClient IPC session private data. + * @param idDisplay ID of display to be set as primary. + */ +RTDECL(int) vbDrmIpcSetPrimaryDisplay(PVBOX_DRMIPC_CLIENT pClient, uint32_t idDisplay); + +/** + * Report to IPC server that display layout offsets have been changed (called by IPC client). + * + * @return IPRT status code. + * @param pClient IPC session private data. + * @param cDisplays Number of monitors which have offsets changed. + * @param aDisplays Offsets data. + */ +RTDECL(int) vbDrmIpcReportDisplayOffsets(PVBOX_DRMIPC_CLIENT pClient, uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays); + +#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_display_ipc_h */ diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp b/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp new file mode 100644 index 00000000..168adb9b --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp @@ -0,0 +1,460 @@ +/* $Id: display-svga-session.cpp $ */ +/** @file + * Guest Additions - VMSVGA Desktop Environment user session assistant. + * + * This service connects to VBoxDRMClient IPC server, listens for + * its commands and reports current display offsets to it. If IPC + * server is not available, it forks legacy 'VBoxClient --vmsvga + * service and terminates. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/* + * This service is an IPC client for VBoxDRMClient daemon. It is also + * a proxy bridge to a Desktop Environment specific code (so called + * Desktop Environment helpers). + * + * Once started, it will try to enumerate and probe all the registered + * helpers and if appropriate helper found, it will forward incoming IPC + * commands to it as well as send helper's commands back to VBoxDRMClient. + * Generic helper is a special one. It will be used by default if all the + * other helpers are failed on probe. Moreover, generic helper provides + * helper functions that can be used by other helpers as well. For example, + * once Gnome3 Desktop Environment is running on X11, it will be also use + * display offsets change notification monitor of a generic helper. + * + * Multiple instances of this daemon are allowed to run in parallel + * with the following limitations. + * A single user cannot run multiple daemon instances per single TTY device, + * however, multiple instances are allowed for the user on different + * TTY devices (i.e. in case if user runs multiple X servers on different + * terminals). On multiple TTY devices multiple users can run multiple + * daemon instances (i.e. in case of "switch user" DE configuration when + * multiple X/Wayland servers are running on separate TTY devices). + */ + +#include "VBoxClient.h" +#include "display-ipc.h" +#include "display-helper.h" + +#include <VBox/VBoxGuestLib.h> + +#include <iprt/localipc.h> +#include <iprt/asm.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include <iprt/linux/sysfs.h> + + +/** Handle to IPC client connection. */ +VBOX_DRMIPC_CLIENT g_hClient = VBOX_DRMIPC_CLIENT_INITIALIZER; + +/** IPC client handle critical section. */ +static RTCRITSECT g_hClientCritSect; + +/** List of available Desktop Environment specific display helpers. */ +static const VBCLDISPLAYHELPER *g_apDisplayHelpers[] = +{ + &g_DisplayHelperGnome3, /* GNOME3 helper. */ + &g_DisplayHelperGeneric, /* Generic helper. */ + NULL, /* Terminate list. */ +}; + +/** Selected Desktop Environment specific display helper. */ +static const VBCLDISPLAYHELPER *g_pDisplayHelper = NULL; + +/** IPC connection session handle. */ +static RTLOCALIPCSESSION g_hSession = 0; + +/** + * Callback for display offsets change events provided by Desktop Environment specific display helper. + * + * @returns IPRT status code. + * @param cDisplays Number of displays which have changed offset. + * @param aDisplays Display data. + */ +static DECLCALLBACK(int) vbclSVGASessionDisplayOffsetChanged(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays) +{ + int rc = RTCritSectEnter(&g_hClientCritSect); + + if (RT_SUCCESS(rc)) + { + rc = vbDrmIpcReportDisplayOffsets(&g_hClient, cDisplays, aDisplays); + int rc2 = RTCritSectLeave(&g_hClientCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to leave critical session, rc=%Rrc\n", rc2); + } + else + VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to enter critical session, rc=%Rrc\n", rc); + + return rc; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vbclSVGASessionInit(void) +{ + int rc; + RTLOCALIPCSESSION hSession; + int idxDisplayHelper = 0; + + /** Custom log prefix to be used for logger instance of this process. */ + static const char *pszLogPrefix = "VBoxClient VMSVGA:"; + + VBClLogSetLogPrefix(pszLogPrefix); + + rc = RTCritSectInit(&g_hClientCritSect); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to init locking, rc=%Rrc\n", rc); + return rc; + } + + /* Go through list of available Desktop Environment specific helpers and try to pick up one. */ + while (g_apDisplayHelpers[idxDisplayHelper]) + { + if (g_apDisplayHelpers[idxDisplayHelper]->pfnProbe) + { + VBClLogInfo("probing Desktop Environment helper '%s'\n", + g_apDisplayHelpers[idxDisplayHelper]->pszName); + + rc = g_apDisplayHelpers[idxDisplayHelper]->pfnProbe(); + + /* Found compatible helper. */ + if (RT_SUCCESS(rc)) + { + /* Initialize it. */ + if (g_apDisplayHelpers[idxDisplayHelper]->pfnInit) + { + rc = g_apDisplayHelpers[idxDisplayHelper]->pfnInit(); + } + + /* Some helpers might have no .pfnInit(), that's ok. */ + if (RT_SUCCESS(rc)) + { + /* Subscribe to display offsets change event. */ + if (g_apDisplayHelpers[idxDisplayHelper]->pfnSubscribeDisplayOffsetChangeNotification) + { + g_apDisplayHelpers[idxDisplayHelper]-> + pfnSubscribeDisplayOffsetChangeNotification( + vbclSVGASessionDisplayOffsetChanged); + } + + g_pDisplayHelper = g_apDisplayHelpers[idxDisplayHelper]; + break; + } + else + VBClLogError("compatible Desktop Environment " + "helper has been found, but it cannot be initialized, rc=%Rrc\n", rc); + } + } + + idxDisplayHelper++; + } + + /* Make sure we found compatible Desktop Environment specific helper. */ + if (g_pDisplayHelper) + { + VBClLogInfo("using Desktop Environment specific display helper '%s'\n", + g_pDisplayHelper->pszName); + } + else + { + VBClLogError("unable to find Desktop Environment specific display helper\n"); + return VERR_NOT_IMPLEMENTED; + } + + /* Attempt to connect to VBoxDRMClient IPC server. */ + rc = RTLocalIpcSessionConnect(&hSession, VBOX_DRMIPC_SERVER_NAME, 0); + if (RT_SUCCESS(rc)) + { + g_hSession = hSession; + } + else + VBClLogError("unable to connect to IPC server, rc=%Rrc\n", rc); + + /* We cannot initialize ourselves, start legacy service and terminate. */ + if (RT_FAILURE(rc)) + { + /* Free helper resources. */ + if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification) + g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification(); + + if (g_pDisplayHelper->pfnTerm) + { + rc = g_pDisplayHelper->pfnTerm(); + VBClLogInfo("helper service terminated, rc=%Rrc\n", rc); + } + + rc = VbglR3DrmLegacyClientStart(); + VBClLogInfo("starting legacy service, rc=%Rrc\n", rc); + /* Force return status, so parent thread wont be trying to start worker thread. */ + rc = VERR_NOT_AVAILABLE; + } + + return rc; +} + +/** + * A callback function which is triggered on IPC data receive. + * + * @returns IPRT status code. + * @param idCmd DRM IPC command ID. + * @param pvData DRM IPC command payload. + * @param cbData Size of DRM IPC command payload. + */ +static DECLCALLBACK(int) vbclSVGASessionRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData) +{ + VBOXDRMIPCCLTCMD enmCmd = + (idCmd > VBOXDRMIPCCLTCMD_INVALID && idCmd < VBOXDRMIPCCLTCMD_MAX) ? + (VBOXDRMIPCCLTCMD)idCmd : VBOXDRMIPCCLTCMD_INVALID; + + int rc = VERR_INVALID_PARAMETER; + + AssertReturn(pvData, VERR_INVALID_PARAMETER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + AssertReturn(g_pDisplayHelper, VERR_INVALID_PARAMETER); + + switch (enmCmd) + { + case VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY: + { + if (g_pDisplayHelper->pfnSetPrimaryDisplay) + { + PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)pvData; + static uint32_t idPrimaryDisplayCached = VBOX_DRMIPC_MONITORS_MAX; + + if ( pCmd->idDisplay < VBOX_DRMIPC_MONITORS_MAX + && idPrimaryDisplayCached != pCmd->idDisplay) + { + rc = g_pDisplayHelper->pfnSetPrimaryDisplay(pCmd->idDisplay); + /* Update cache. */ + idPrimaryDisplayCached = pCmd->idDisplay; + } + else + VBClLogVerbose(1, "do not set %u as a primary display\n", pCmd->idDisplay); + } + + break; + } + default: + { + VBClLogError("received unknown IPC command 0x%x\n", idCmd); + break; + } + } + + return rc; +} + +/** + * Reconnect to DRM IPC server. + */ +static int vbclSVGASessionReconnect(void) +{ + int rc = VERR_GENERAL_FAILURE; + + rc = RTCritSectEnter(&g_hClientCritSect); + if (RT_FAILURE(rc)) + { + VBClLogError("unable to enter critical section on reconnect, rc=%Rrc\n", rc); + return rc; + } + + /* Check if session was not closed before. */ + if (RT_VALID_PTR(g_hSession)) + { + rc = RTLocalIpcSessionClose(g_hSession); + if (RT_FAILURE(rc)) + VBClLogError("unable to release IPC connection on reconnect, rc=%Rrc\n", rc); + + rc = vbDrmIpcClientReleaseResources(&g_hClient); + if (RT_FAILURE(rc)) + VBClLogError("unable to release IPC session resources, rc=%Rrc\n", rc); + } + + rc = RTLocalIpcSessionConnect(&g_hSession, VBOX_DRMIPC_SERVER_NAME, 0); + if (RT_SUCCESS(rc)) + { + rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack); + if (RT_FAILURE(rc)) + VBClLogError("unable to re-initialize IPC session, rc=%Rrc\n", rc); + } + else + VBClLogError("unable to reconnect to IPC server, rc=%Rrc\n", rc); + + int rc2 = RTCritSectLeave(&g_hClientCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("unable to leave critical section on reconnect, rc=%Rrc\n", rc); + + return rc; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vbclSVGASessionWorker(bool volatile *pfShutdown) +{ + int rc = VINF_SUCCESS; + + /* Notify parent thread that we started successfully. */ + rc = RTThreadUserSignal(RTThreadSelf()); + if (RT_FAILURE(rc)) + VBClLogError("unable to notify parent thread about successful start\n"); + + rc = RTCritSectEnter(&g_hClientCritSect); + + if (RT_FAILURE(rc)) + { + VBClLogError("unable to enter critical section on worker start, rc=%Rrc\n", rc); + return rc; + } + + rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack); + int rc2 = RTCritSectLeave(&g_hClientCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("unable to leave critical section on worker start, rc=%Rrc\n", rc); + + if (RT_FAILURE(rc)) + { + VBClLogError("cannot initialize IPC session, rc=%Rrc\n", rc); + return rc; + } + + for (;;) + { + rc = vbDrmIpcConnectionHandler(&g_hClient); + + /* Try to shutdown thread as soon as possible. */ + if (ASMAtomicReadBool(pfShutdown)) + { + /* Shutdown requested. */ + break; + } + + /* Normal case, there was no incoming messages for a while. */ + if (rc == VERR_TIMEOUT) + { + continue; + } + else if (RT_FAILURE(rc)) + { + VBClLogError("unable to handle IPC connection, rc=%Rrc\n", rc); + + /* Relax a bit before spinning the loop. */ + RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS); + /* Try to reconnect to server. */ + rc = vbclSVGASessionReconnect(); + } + } + + /* Check if session was not closed before. */ + if (RT_VALID_PTR(g_hSession)) + { + rc2 = RTCritSectEnter(&g_hClientCritSect); + if (RT_SUCCESS(rc2)) + { + rc2 = vbDrmIpcClientReleaseResources(&g_hClient); + if (RT_FAILURE(rc2)) + VBClLogError("cannot release IPC session resources, rc=%Rrc\n", rc2); + + rc2 = RTCritSectLeave(&g_hClientCritSect); + if (RT_FAILURE(rc2)) + VBClLogError("unable to leave critical section on worker end, rc=%Rrc\n", rc); + } + else + VBClLogError("unable to enter critical section on worker end, rc=%Rrc\n", rc); + } + + return rc; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vbclSVGASessionStop(void) +{ + int rc; + + /* Check if session was not closed before. */ + if (!RT_VALID_PTR(g_hSession)) + return; + + /* Attempt to release any waiting syscall related to RTLocalIpcSessionXXX(). */ + rc = RTLocalIpcSessionFlush(g_hSession); + if (RT_FAILURE(rc)) + VBClLogError("unable to flush data to IPC connection, rc=%Rrc\n", rc); + + rc = RTLocalIpcSessionCancel(g_hSession); + if (RT_FAILURE(rc)) + VBClLogError("unable to cancel IPC session, rc=%Rrc\n", rc); +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnTerm} + */ +static DECLCALLBACK(int) vbclSVGASessionTerm(void) +{ + int rc = VINF_SUCCESS; + + if (g_hSession) + { + rc = RTLocalIpcSessionClose(g_hSession); + g_hSession = 0; + + if (RT_FAILURE(rc)) + VBClLogError("unable to close IPC connection, rc=%Rrc\n", rc); + } + + if (g_pDisplayHelper) + { + if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification) + g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification(); + + if (g_pDisplayHelper->pfnTerm) + { + rc = g_pDisplayHelper->pfnTerm(); + if (RT_FAILURE(rc)) + VBClLogError("unable to terminate Desktop Environment helper '%s', rc=%Rrc\n", + rc, g_pDisplayHelper->pszName); + } + } + + return VINF_SUCCESS; +} + +VBCLSERVICE g_SvcDisplaySVGASession = +{ + "vmsvga-session", /* szName */ + "VMSVGA display assistant", /* pszDescription */ + ".vboxclient-vmsvga-session", /* pszPidFilePathTemplate */ + NULL, /* pszUsage */ + NULL, /* pszOptions */ + NULL, /* pfnOption */ + vbclSVGASessionInit, /* pfnInit */ + vbclSVGASessionWorker, /* pfnWorker */ + vbclSVGASessionStop, /* pfnStop */ + vbclSVGASessionTerm, /* pfnTerm */ +}; diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp b/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp new file mode 100644 index 00000000..90005ab1 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-svga-x11.cpp @@ -0,0 +1,1402 @@ +/* $Id: display-svga-x11.cpp $ */ +/** @file + * X11 guest client - VMSVGA emulation resize event pass-through to X.Org + * guest driver. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/* + * Known things to test when changing this code. All assume a guest with VMSVGA + * active and controlled by X11 or Wayland, and Guest Additions installed and + * running, unless otherwise stated. + * - On Linux 4.6 and later guests, VBoxClient --vmsvga should be running as + * root and not as the logged-in user. Dynamic resizing should work for all + * screens in any environment which handles kernel resize notifications, + * including at log-in screens. Test GNOME Shell Wayland and GNOME Shell + * under X.Org or Unity or KDE at the log-in screen and after log-in. + * - Linux 4.10 changed the user-kernel-ABI introduced in 4.6: test both. + * - On other guests (than Linux 4.6 or later) running X.Org Server 1.3 or + * later, VBoxClient --vmsvga should never be running as root, and should run + * (and dynamic resizing and screen enable/disable should work for all + * screens) whenever a user is logged in to a supported desktop environment. + * - On guests running X.Org Server 1.2 or older, VBoxClient --vmsvga should + * never run as root and should run whenever a user is logged in to a + * supported desktop environment. Dynamic resizing should work for the first + * screen, and enabling others should not be possible. + * - When VMSVGA is not enabled, VBoxClient --vmsvga should never stay running. + * - The following assumptions are done and should be taken into account when reading/chaning the code: + * # The order of the outputs (monitors) is assumed to be the same in RANDROUTPUT array and + * XRRScreenResources.outputs array. + * - This code does 2 related but separate things: 1- It resizes and enables/disables monitors upon host's + * requests (see the infinite loop in run()). 2- it listens to RandR events (caused by this or any other X11 client) + * on a different thread and notifies host about the new monitor positions. See sendMonitorPositions(...). This is + * mainly a work around since we have realized that vmsvga does not convey correct monitor positions thru FIFO. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <stdio.h> +#include <dlfcn.h> +/** For sleep(..) */ +#include <unistd.h> +#include "VBoxClient.h" + +#include <VBox/VBoxGuestLib.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/env.h> + +#include <X11/Xlibint.h> +#include <X11/extensions/Xrandr.h> +#include <X11/extensions/panoramiXproto.h> + +#include "display-svga-xf86cvt.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define MILLIS_PER_INCH (25.4) +#define DEFAULT_DPI (96.0) + +/* Time in milliseconds to relax if no X11 events available. */ +#define VBOX_SVGA_X11_RELAX_TIME_MS (500) +/* Time in milliseconds to wait for host events. */ +#define VBOX_SVGA_HOST_EVENT_RX_TIMEOUT_MS (500) + +/** Maximum number of supported screens. DRM and X11 both limit this to 32. */ +/** @todo if this ever changes, dynamically allocate resizeable arrays in the + * context structure. */ +#define VMW_MAX_HEADS 32 + +#define checkFunctionPtrReturn(pFunction) \ + do { \ + if (pFunction) { } \ + else \ + { \ + VBClLogFatalError("Could not find symbol address (%s)\n", #pFunction); \ + dlclose(x11Context.pRandLibraryHandle); \ + x11Context.pRandLibraryHandle = NULL; \ + return VERR_NOT_FOUND; \ + } \ + } while (0) + +#define checkFunctionPtr(pFunction) \ + do { \ + if (pFunction) {} \ + else VBClLogError("Could not find symbol address (%s)\n", #pFunction);\ + } while (0) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +#define X_VMwareCtrlSetRes 1 + +typedef struct +{ + CARD8 reqType; + CARD8 VMwareCtrlReqType; + CARD16 length B16; + CARD32 screen B32; + CARD32 x B32; + CARD32 y B32; +} xVMwareCtrlSetResReq; +#define sz_xVMwareCtrlSetResReq 16 + +typedef struct +{ + BYTE type; + BYTE pad1; + CARD16 sequenceNumber B16; + CARD32 length B32; + CARD32 screen B32; + CARD32 x B32; + CARD32 y B32; + CARD32 pad2 B32; + CARD32 pad3 B32; + CARD32 pad4 B32; +} xVMwareCtrlSetResReply; +#define sz_xVMwareCtrlSetResReply 32 + +typedef struct { + CARD8 reqType; /* always X_VMwareCtrlReqCode */ + CARD8 VMwareCtrlReqType; /* always X_VMwareCtrlSetTopology */ + CARD16 length B16; + CARD32 screen B32; + CARD32 number B32; + CARD32 pad1 B32; +} xVMwareCtrlSetTopologyReq; +#define sz_xVMwareCtrlSetTopologyReq 16 + +#define X_VMwareCtrlSetTopology 2 + +typedef struct { + BYTE type; /* X_Reply */ + BYTE pad1; + CARD16 sequenceNumber B16; + CARD32 length B32; + CARD32 screen B32; + CARD32 pad2 B32; + CARD32 pad3 B32; + CARD32 pad4 B32; + CARD32 pad5 B32; + CARD32 pad6 B32; +} xVMwareCtrlSetTopologyReply; +#define sz_xVMwareCtrlSetTopologyReply 32 + +struct X11VMWRECT +{ + int16_t x; + int16_t y; + uint16_t w; + uint16_t h; +}; +AssertCompileSize(struct X11VMWRECT, 8); + +struct X11CONTEXT +{ + Display *pDisplay; + /* We use a separate connection for randr event listening since sharing a + single display object with resizing (main) and event listening threads ends up having a deadlock.*/ + Display *pDisplayRandRMonitoring; + Window rootWindow; + int iDefaultScreen; + XRRScreenResources *pScreenResources; + int hRandRMajor; + int hRandRMinor; + int hRandREventBase; + int hRandRErrorBase; + int hEventMask; + bool fMonitorInfoAvailable; + /** The number of outputs (monitors, including disconnect ones) xrandr reports. */ + int hOutputCount; + void *pRandLibraryHandle; + bool fWmwareCtrlExtention; + int hVMWCtrlMajorOpCode; + /** Function pointers we used if we dlopen libXrandr instead of linking. */ + void (*pXRRSelectInput) (Display *, Window, int); + Bool (*pXRRQueryExtension) (Display *, int *, int *); + Status (*pXRRQueryVersion) (Display *, int *, int*); + XRRMonitorInfo* (*pXRRGetMonitors)(Display *, Window, Bool, int *); + XRRScreenResources* (*pXRRGetScreenResources)(Display *, Window); + Status (*pXRRSetCrtcConfig)(Display *, XRRScreenResources *, RRCrtc, + Time, int, int, RRMode, Rotation, RROutput *, int); + void (*pXRRFreeMonitors)(XRRMonitorInfo *); + void (*pXRRFreeScreenResources)(XRRScreenResources *); + void (*pXRRFreeModeInfo)(XRRModeInfo *); + void (*pXRRFreeOutputInfo)(XRROutputInfo *); + void (*pXRRSetScreenSize)(Display *, Window, int, int, int, int); + int (*pXRRUpdateConfiguration)(XEvent *event); + XRRModeInfo* (*pXRRAllocModeInfo)(_Xconst char *, int); + RRMode (*pXRRCreateMode) (Display *, Window, XRRModeInfo *); + XRROutputInfo* (*pXRRGetOutputInfo) (Display *, XRRScreenResources *, RROutput); + XRRCrtcInfo* (*pXRRGetCrtcInfo) (Display *, XRRScreenResources *, RRCrtc crtc); + void (*pXRRFreeCrtcInfo)(XRRCrtcInfo *); + void (*pXRRAddOutputMode)(Display *, RROutput, RRMode); + void (*pXRRDeleteOutputMode)(Display *, RROutput, RRMode); + void (*pXRRDestroyMode)(Display *, RRMode); + void (*pXRRSetOutputPrimary)(Display *, Window, RROutput); +}; + +static X11CONTEXT x11Context; + +struct RANDROUTPUT +{ + int32_t x; + int32_t y; + uint32_t width; + uint32_t height; + bool fEnabled; + bool fPrimary; +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void x11Connect(); +static int determineOutputCount(); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Monitor positions array. Allocated here and deallocated in the class descructor. */ +RTPOINT *mpMonitorPositions; +/** Thread to listen to some of the X server events. */ +RTTHREAD mX11MonitorThread = NIL_RTTHREAD; +/** Shutdown indicator for the monitor thread. */ +static bool g_fMonitorThreadShutdown = false; + + + +#ifdef RT_OS_SOLARIS +static bool VMwareCtrlSetRes( + Display *dpy, int hExtensionMajorOpcode, int screen, int x, int y) +{ + xVMwareCtrlSetResReply rep; + xVMwareCtrlSetResReq *pReq; + bool fResult = false; + + LockDisplay(dpy); + + GetReq(VMwareCtrlSetRes, pReq); + AssertPtrReturn(pReq, false); + + pReq->reqType = hExtensionMajorOpcode; + pReq->VMwareCtrlReqType = X_VMwareCtrlSetRes; + pReq->screen = screen; + pReq->x = x; + pReq->y = y; + + fResult = !!_XReply(dpy, (xReply *)&rep, (SIZEOF(xVMwareCtrlSetResReply) - SIZEOF(xReply)) >> 2, xFalse); + + UnlockDisplay(dpy); + + return fResult; +} +#endif /* RT_OS_SOLARIS */ + +/** Makes a call to vmwarectrl extension. This updates the + * connection information and possible resolutions (modes) + * of each monitor on the driver. Also sets the preferred mode + * of each output (monitor) to currently selected one. */ +bool VMwareCtrlSetTopology(Display *dpy, int hExtensionMajorOpcode, + int screen, xXineramaScreenInfo extents[], int number) +{ + xVMwareCtrlSetTopologyReply rep; + xVMwareCtrlSetTopologyReq *req; + + long len; + + LockDisplay(dpy); + + GetReq(VMwareCtrlSetTopology, req); + req->reqType = hExtensionMajorOpcode; + req->VMwareCtrlReqType = X_VMwareCtrlSetTopology; + req->screen = screen; + req->number = number; + + len = ((long) number) << 1; + SetReqLen(req, len, len); + len <<= 2; + _XSend(dpy, (char *)extents, len); + + if (!_XReply(dpy, (xReply *)&rep, + (SIZEOF(xVMwareCtrlSetTopologyReply) - SIZEOF(xReply)) >> 2, + xFalse)) + { + UnlockDisplay(dpy); + SyncHandle(); + return false; + } + UnlockDisplay(dpy); + SyncHandle(); + return true; +} + +/** This function assumes monitors are named as from Virtual1 to VirtualX. */ +static int getMonitorIdFromName(const char *sMonitorName) +{ + if (!sMonitorName) + return -1; +#ifdef RT_OS_SOLARIS + if (!strcmp(sMonitorName, "default")) + return 1; +#endif + int iLen = strlen(sMonitorName); + if (iLen <= 0) + return -1; + int iBase = 10; + int iResult = 0; + for (int i = iLen - 1; i >= 0; --i) + { + /* Stop upon seeing the first non-numeric char. */ + if (sMonitorName[i] < 48 || sMonitorName[i] > 57) + break; + iResult += (sMonitorName[i] - 48) * iBase / 10; + iBase *= 10; + } + return iResult; +} + +static void sendMonitorPositions(RTPOINT *pPositions, size_t cPositions) +{ + if (cPositions && !pPositions) + { + VBClLogError(("Monitor position update called with NULL pointer!\n")); + return; + } + int rc = VbglR3SeamlessSendMonitorPositions(cPositions, pPositions); + if (RT_SUCCESS(rc)) + VBClLogInfo("Sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc); + else + VBClLogError("Error during sending monitor positions (%u of them) to the host: %Rrc\n", cPositions, rc); +} + +static void queryMonitorPositions() +{ + static const int iSentinelPosition = -1; + if (mpMonitorPositions) + { + free(mpMonitorPositions); + mpMonitorPositions = NULL; + } + + int iMonitorCount = 0; + XRRMonitorInfo *pMonitorInfo = NULL; +#ifdef WITH_DISTRO_XRAND_XINERAMA + pMonitorInfo = XRRGetMonitors(x11Context.pDisplayRandRMonitoring, + DefaultRootWindow(x11Context.pDisplayRandRMonitoring), true, &iMonitorCount); +#else + if (x11Context.pXRRGetMonitors) + pMonitorInfo = x11Context.pXRRGetMonitors(x11Context.pDisplayRandRMonitoring, + DefaultRootWindow(x11Context.pDisplayRandRMonitoring), true, &iMonitorCount); +#endif + if (!pMonitorInfo) + return; + if (iMonitorCount == -1) + VBClLogError("Could not get monitor info\n"); + else + { + mpMonitorPositions = (RTPOINT*)malloc(x11Context.hOutputCount * sizeof(RTPOINT)); + /** @todo memset? */ + for (int i = 0; i < x11Context.hOutputCount; ++i) + { + mpMonitorPositions[i].x = iSentinelPosition; + mpMonitorPositions[i].y = iSentinelPosition; + } + for (int i = 0; i < iMonitorCount; ++i) + { + char *pszMonitorName = XGetAtomName(x11Context.pDisplayRandRMonitoring, pMonitorInfo[i].name); + if (!pszMonitorName) + { + VBClLogError("queryMonitorPositions: skip monitor with unknown name %d\n", i); + continue; + } + + int iMonitorID = getMonitorIdFromName(pszMonitorName) - 1; + XFree((void *)pszMonitorName); + pszMonitorName = NULL; + + if (iMonitorID >= x11Context.hOutputCount || iMonitorID == -1) + { + VBClLogInfo("queryMonitorPositions: skip monitor %d (id %d) (w,h)=(%d,%d) (x,y)=(%d,%d)\n", + i, iMonitorID, + pMonitorInfo[i].width, pMonitorInfo[i].height, + pMonitorInfo[i].x, pMonitorInfo[i].y); + continue; + } + VBClLogInfo("Monitor %d (w,h)=(%d,%d) (x,y)=(%d,%d)\n", + i, + pMonitorInfo[i].width, pMonitorInfo[i].height, + pMonitorInfo[i].x, pMonitorInfo[i].y); + mpMonitorPositions[iMonitorID].x = pMonitorInfo[i].x; + mpMonitorPositions[iMonitorID].y = pMonitorInfo[i].y; + } + if (iMonitorCount > 0) + sendMonitorPositions(mpMonitorPositions, x11Context.hOutputCount); + } +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRFreeMonitors(pMonitorInfo); +#else + if (x11Context.pXRRFreeMonitors) + x11Context.pXRRFreeMonitors(pMonitorInfo); +#endif +} + +static void monitorRandREvents() +{ + XEvent event; + + if (XPending(x11Context.pDisplayRandRMonitoring) > 0) + { + XNextEvent(x11Context.pDisplayRandRMonitoring, &event); + int eventTypeOffset = event.type - x11Context.hRandREventBase; + VBClLogInfo("received X11 event (%d)\n", event.type); + switch (eventTypeOffset) + { + case RRScreenChangeNotify: + VBClLogInfo("RRScreenChangeNotify event received\n"); + queryMonitorPositions(); + break; + default: + break; + } + } else + { + RTThreadSleep(VBOX_SVGA_X11_RELAX_TIME_MS); + } +} + +/** + * @callback_method_impl{FNRTTHREAD} + */ +static DECLCALLBACK(int) x11MonitorThreadFunction(RTTHREAD ThreadSelf, void *pvUser) +{ + RT_NOREF(ThreadSelf, pvUser); + while (!ASMAtomicReadBool(&g_fMonitorThreadShutdown)) + { + monitorRandREvents(); + } + + VBClLogInfo("X11 thread gracefully terminated\n"); + + return 0; +} + +static int startX11MonitorThread() +{ + int rc; + Assert(g_fMonitorThreadShutdown == false); + if (mX11MonitorThread == NIL_RTTHREAD) + { + rc = RTThreadCreate(&mX11MonitorThread, x11MonitorThreadFunction, NULL /*pvUser*/, 0 /*cbStack*/, + RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "X11 events"); + if (RT_FAILURE(rc)) + VBClLogFatalError("Warning: failed to start X11 monitor thread (VBoxClient) rc=%Rrc!\n", rc); + } + else + rc = VINF_ALREADY_INITIALIZED; + return rc; +} + +static int stopX11MonitorThread(void) +{ + int rc = VINF_SUCCESS; + if (mX11MonitorThread != NIL_RTTHREAD) + { + ASMAtomicWriteBool(&g_fMonitorThreadShutdown, true); + /** @todo Send event to thread to get it out of XNextEvent. */ + //???????? + //mX11Monitor.interruptEventWait(); + rc = RTThreadWait(mX11MonitorThread, RT_MS_1SEC, NULL /*prc*/); + if (RT_SUCCESS(rc)) + { + mX11MonitorThread = NIL_RTTHREAD; + g_fMonitorThreadShutdown = false; + } + else + VBClLogError("Failed to stop X11 monitor thread, rc=%Rrc!\n", rc); + } + return rc; +} + +static bool callVMWCTRL(struct RANDROUTPUT *paOutputs) +{ + int hHeight = 600; + int hWidth = 800; + bool fResult = false; + int idxDefaultScreen = DefaultScreen(x11Context.pDisplay); + + AssertReturn(idxDefaultScreen >= 0, false); + AssertReturn(idxDefaultScreen < x11Context.hOutputCount, false); + + xXineramaScreenInfo *extents = (xXineramaScreenInfo *)malloc(x11Context.hOutputCount * sizeof(xXineramaScreenInfo)); + if (!extents) + return false; + int hRunningOffset = 0; + for (int i = 0; i < x11Context.hOutputCount; ++i) + { + if (paOutputs[i].fEnabled) + { + hHeight = paOutputs[i].height; + hWidth = paOutputs[i].width; + } + else + { + hHeight = 0; + hWidth = 0; + } + extents[i].x_org = hRunningOffset; + extents[i].y_org = 0; + extents[i].width = hWidth; + extents[i].height = hHeight; + hRunningOffset += hWidth; + } +#ifdef RT_OS_SOLARIS + fResult = VMwareCtrlSetRes(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode, + idxDefaultScreen, extents[idxDefaultScreen].width, + extents[idxDefaultScreen].height); +#else + fResult = VMwareCtrlSetTopology(x11Context.pDisplay, x11Context.hVMWCtrlMajorOpCode, + idxDefaultScreen, extents, x11Context.hOutputCount); +#endif + free(extents); + return fResult; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vbclSVGAInit(void) +{ + int rc; + + /* In 32-bit guests GAs build on our release machines causes an xserver hang. + * So for 32-bit GAs we use our DRM client. */ +#if ARCH_BITS == 32 + rc = VbglR3DrmClientStart(); + if (RT_FAILURE(rc)) + VBClLogError("Starting DRM resizing client (32-bit) failed with %Rrc\n", rc); + return VERR_NOT_AVAILABLE; /** @todo r=andy Why ignoring rc here? */ +#endif + + /* If DRM client is already running don't start this service. */ + if (VbglR3DrmClientIsRunning()) + { + VBClLogInfo("DRM resizing is already running. Exiting this service\n"); + return VERR_NOT_AVAILABLE; + } + + if (VBClHasWayland()) + { + rc = VbglR3DrmClientStart(); + if (RT_SUCCESS(rc)) + { + VBClLogInfo("VBoxDrmClient has been successfully started, exitting parent process\n"); + exit(0); + } + else + { + VBClLogError("Starting DRM resizing client failed with %Rrc\n", rc); + } + return rc; + } + + x11Connect(); + + if (x11Context.pDisplay == NULL) + return VERR_NOT_AVAILABLE; + + /* don't start the monitoring thread if related randr functionality is not available. */ + if (x11Context.fMonitorInfoAvailable) + { + if (RT_FAILURE(startX11MonitorThread())) + return VERR_NOT_AVAILABLE; + } + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vbclSVGAStop(void) +{ + int rc; + + rc = stopX11MonitorThread(); + if (RT_FAILURE(rc)) + { + VBClLogError("cannot stop X11 monitor thread (%Rrc)\n", rc); + return; + } + + if (mpMonitorPositions) + { + free(mpMonitorPositions); + mpMonitorPositions = NULL; + } + + if (x11Context.pDisplayRandRMonitoring) + { +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, 0); +#else + if (x11Context.pXRRSelectInput) + x11Context.pXRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, 0); +#endif + } + + if (x11Context.pDisplay) + { + XCloseDisplay(x11Context.pDisplay); + x11Context.pDisplay = NULL; + } + + if (x11Context.pDisplayRandRMonitoring) + { + XCloseDisplay(x11Context.pDisplayRandRMonitoring); + x11Context.pDisplayRandRMonitoring = NULL; + } + + if (x11Context.pRandLibraryHandle) + { + dlclose(x11Context.pRandLibraryHandle); + x11Context.pRandLibraryHandle = NULL; + } +} + +#ifndef WITH_DISTRO_XRAND_XINERAMA +static int openLibRandR() +{ + x11Context.pRandLibraryHandle = dlopen("libXrandr.so", RTLD_LAZY /*| RTLD_LOCAL */); + if (!x11Context.pRandLibraryHandle) + x11Context.pRandLibraryHandle = dlopen("libXrandr.so.2", RTLD_LAZY /*| RTLD_LOCAL */); + if (!x11Context.pRandLibraryHandle) + x11Context.pRandLibraryHandle = dlopen("libXrandr.so.2.2.0", RTLD_LAZY /*| RTLD_LOCAL */); + + if (!x11Context.pRandLibraryHandle) + { + VBClLogFatalError("Could not locate libXrandr for dlopen\n"); + return VERR_NOT_FOUND; + } + + *(void **)(&x11Context.pXRRSelectInput) = dlsym(x11Context.pRandLibraryHandle, "XRRSelectInput"); + checkFunctionPtrReturn(x11Context.pXRRSelectInput); + + *(void **)(&x11Context.pXRRQueryExtension) = dlsym(x11Context.pRandLibraryHandle, "XRRQueryExtension"); + checkFunctionPtrReturn(x11Context.pXRRQueryExtension); + + *(void **)(&x11Context.pXRRQueryVersion) = dlsym(x11Context.pRandLibraryHandle, "XRRQueryVersion"); + checkFunctionPtrReturn(x11Context.pXRRQueryVersion); + + /* Don't bail out when XRRGetMonitors XRRFreeMonitors are missing as in Oracle Solaris 10. It is not crucial esp. for single monitor. */ + *(void **)(&x11Context.pXRRGetMonitors) = dlsym(x11Context.pRandLibraryHandle, "XRRGetMonitors"); + checkFunctionPtr(x11Context.pXRRGetMonitors); + + *(void **)(&x11Context.pXRRFreeMonitors) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeMonitors"); + checkFunctionPtr(x11Context.pXRRFreeMonitors); + + x11Context.fMonitorInfoAvailable = x11Context.pXRRGetMonitors && x11Context.pXRRFreeMonitors; + + *(void **)(&x11Context.pXRRGetScreenResources) = dlsym(x11Context.pRandLibraryHandle, "XRRGetScreenResources"); + checkFunctionPtr(x11Context.pXRRGetScreenResources); + + *(void **)(&x11Context.pXRRSetCrtcConfig) = dlsym(x11Context.pRandLibraryHandle, "XRRSetCrtcConfig"); + checkFunctionPtr(x11Context.pXRRSetCrtcConfig); + + *(void **)(&x11Context.pXRRFreeScreenResources) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeScreenResources"); + checkFunctionPtr(x11Context.pXRRFreeScreenResources); + + *(void **)(&x11Context.pXRRFreeModeInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeModeInfo"); + checkFunctionPtr(x11Context.pXRRFreeModeInfo); + + *(void **)(&x11Context.pXRRFreeOutputInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeOutputInfo"); + checkFunctionPtr(x11Context.pXRRFreeOutputInfo); + + *(void **)(&x11Context.pXRRSetScreenSize) = dlsym(x11Context.pRandLibraryHandle, "XRRSetScreenSize"); + checkFunctionPtr(x11Context.pXRRSetScreenSize); + + *(void **)(&x11Context.pXRRUpdateConfiguration) = dlsym(x11Context.pRandLibraryHandle, "XRRUpdateConfiguration"); + checkFunctionPtr(x11Context.pXRRUpdateConfiguration); + + *(void **)(&x11Context.pXRRAllocModeInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRAllocModeInfo"); + checkFunctionPtr(x11Context.pXRRAllocModeInfo); + + *(void **)(&x11Context.pXRRCreateMode) = dlsym(x11Context.pRandLibraryHandle, "XRRCreateMode"); + checkFunctionPtr(x11Context.pXRRCreateMode); + + *(void **)(&x11Context.pXRRGetOutputInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRGetOutputInfo"); + checkFunctionPtr(x11Context.pXRRGetOutputInfo); + + *(void **)(&x11Context.pXRRGetCrtcInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRGetCrtcInfo"); + checkFunctionPtr(x11Context.pXRRGetCrtcInfo); + + *(void **)(&x11Context.pXRRFreeCrtcInfo) = dlsym(x11Context.pRandLibraryHandle, "XRRFreeCrtcInfo"); + checkFunctionPtr(x11Context.pXRRFreeCrtcInfo); + + *(void **)(&x11Context.pXRRAddOutputMode) = dlsym(x11Context.pRandLibraryHandle, "XRRAddOutputMode"); + checkFunctionPtr(x11Context.pXRRAddOutputMode); + + *(void **)(&x11Context.pXRRDeleteOutputMode) = dlsym(x11Context.pRandLibraryHandle, "XRRDeleteOutputMode"); + checkFunctionPtr(x11Context.pXRRDeleteOutputMode); + + *(void **)(&x11Context.pXRRDestroyMode) = dlsym(x11Context.pRandLibraryHandle, "XRRDestroyMode"); + checkFunctionPtr(x11Context.pXRRDestroyMode); + + *(void **)(&x11Context.pXRRSetOutputPrimary) = dlsym(x11Context.pRandLibraryHandle, "XRRSetOutputPrimary"); + checkFunctionPtr(x11Context.pXRRSetOutputPrimary); + + return VINF_SUCCESS; +} +#endif + +static void x11Connect() +{ + x11Context.pScreenResources = NULL; + x11Context.pXRRSelectInput = NULL; + x11Context.pRandLibraryHandle = NULL; + x11Context.pXRRQueryExtension = NULL; + x11Context.pXRRQueryVersion = NULL; + x11Context.pXRRGetMonitors = NULL; + x11Context.pXRRGetScreenResources = NULL; + x11Context.pXRRSetCrtcConfig = NULL; + x11Context.pXRRFreeMonitors = NULL; + x11Context.pXRRFreeScreenResources = NULL; + x11Context.pXRRFreeOutputInfo = NULL; + x11Context.pXRRFreeModeInfo = NULL; + x11Context.pXRRSetScreenSize = NULL; + x11Context.pXRRUpdateConfiguration = NULL; + x11Context.pXRRAllocModeInfo = NULL; + x11Context.pXRRCreateMode = NULL; + x11Context.pXRRGetOutputInfo = NULL; + x11Context.pXRRGetCrtcInfo = NULL; + x11Context.pXRRFreeCrtcInfo = NULL; + x11Context.pXRRAddOutputMode = NULL; + x11Context.pXRRDeleteOutputMode = NULL; + x11Context.pXRRDestroyMode = NULL; + x11Context.pXRRSetOutputPrimary = NULL; + x11Context.fWmwareCtrlExtention = false; + x11Context.fMonitorInfoAvailable = false; + x11Context.hRandRMajor = 0; + x11Context.hRandRMinor = 0; + + int dummy; + if (x11Context.pDisplay != NULL) + VBClLogFatalError("%s called with bad argument\n", __func__); + x11Context.pDisplay = XOpenDisplay(NULL); + x11Context.pDisplayRandRMonitoring = XOpenDisplay(NULL); + if (x11Context.pDisplay == NULL) + return; +#ifndef WITH_DISTRO_XRAND_XINERAMA + if (openLibRandR() != VINF_SUCCESS) + { + XCloseDisplay(x11Context.pDisplay); + XCloseDisplay(x11Context.pDisplayRandRMonitoring); + x11Context.pDisplay = NULL; + x11Context.pDisplayRandRMonitoring = NULL; + return; + } +#endif + + x11Context.fWmwareCtrlExtention = XQueryExtension(x11Context.pDisplay, "VMWARE_CTRL", + &x11Context.hVMWCtrlMajorOpCode, &dummy, &dummy); + if (!x11Context.fWmwareCtrlExtention) + VBClLogError("VMWARE's ctrl extension is not available! Multi monitor management is not possible\n"); + else + VBClLogInfo("VMWARE's ctrl extension is available. Major Opcode is %d.\n", x11Context.hVMWCtrlMajorOpCode); + + /* Check Xrandr stuff. */ + bool fSuccess = false; +#ifdef WITH_DISTRO_XRAND_XINERAMA + fSuccess = XRRQueryExtension(x11Context.pDisplay, &x11Context.hRandREventBase, &x11Context.hRandRErrorBase); +#else + if (x11Context.pXRRQueryExtension) + fSuccess = x11Context.pXRRQueryExtension(x11Context.pDisplay, &x11Context.hRandREventBase, &x11Context.hRandRErrorBase); +#endif + if (fSuccess) + { + fSuccess = false; +#ifdef WITH_DISTRO_XRAND_XINERAMA + fSuccess = XRRQueryVersion(x11Context.pDisplay, &x11Context.hRandRMajor, &x11Context.hRandRMinor); +#else + if (x11Context.pXRRQueryVersion) + fSuccess = x11Context.pXRRQueryVersion(x11Context.pDisplay, &x11Context.hRandRMajor, &x11Context.hRandRMinor); +#endif + if (!fSuccess) + { + XCloseDisplay(x11Context.pDisplay); + x11Context.pDisplay = NULL; + return; + } + if (x11Context.hRandRMajor < 1 || x11Context.hRandRMinor <= 3) + { + VBClLogError("Resizing service requires libXrandr Version >= 1.4. Detected version is %d.%d\n", x11Context.hRandRMajor, x11Context.hRandRMinor); + XCloseDisplay(x11Context.pDisplay); + x11Context.pDisplay = NULL; + + int rc = VbglR3DrmLegacyX11AgentStart(); + VBClLogInfo("Attempt to start legacy X11 resize agent, rc=%Rrc\n", rc); + + return; + } + } + x11Context.rootWindow = DefaultRootWindow(x11Context.pDisplay); + x11Context.hEventMask = RRScreenChangeNotifyMask; + + /* Select the XEvent types we want to listen to. */ +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, x11Context.hEventMask); +#else + if (x11Context.pXRRSelectInput) + x11Context.pXRRSelectInput(x11Context.pDisplayRandRMonitoring, x11Context.rootWindow, x11Context.hEventMask); +#endif + x11Context.iDefaultScreen = DefaultScreen(x11Context.pDisplay); + +#ifdef WITH_DISTRO_XRAND_XINERAMA + x11Context.pScreenResources = XRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow); +#else + if (x11Context.pXRRGetScreenResources) + x11Context.pScreenResources = x11Context.pXRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow); +#endif + x11Context.hOutputCount = RT_VALID_PTR(x11Context.pScreenResources) ? determineOutputCount() : 0; +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRFreeScreenResources(x11Context.pScreenResources); +#else + if (x11Context.pXRRFreeScreenResources) + x11Context.pXRRFreeScreenResources(x11Context.pScreenResources); +#endif +} + +static int determineOutputCount() +{ + if (!x11Context.pScreenResources) + return 0; + return x11Context.pScreenResources->noutput; +} + +static int findExistingModeIndex(unsigned iXRes, unsigned iYRes) +{ + if (!x11Context.pScreenResources) + return -1; + for (int i = 0; i < x11Context.pScreenResources->nmode; ++i) + { + if (x11Context.pScreenResources->modes[i].width == iXRes && x11Context.pScreenResources->modes[i].height == iYRes) + return i; + } + return -1; +} + +static bool disableCRTC(RRCrtc crtcID) +{ + XRRCrtcInfo *pCrctInfo = NULL; + +#ifdef WITH_DISTRO_XRAND_XINERAMA + pCrctInfo = XRRGetCrtcInfo(x11Context.pDisplay, x11Context.pScreenResources, crtcID); +#else + if (x11Context.pXRRGetCrtcInfo) + pCrctInfo = x11Context.pXRRGetCrtcInfo(x11Context.pDisplay, x11Context.pScreenResources, crtcID); +#endif + + if (!pCrctInfo) + return false; + + Status ret = Success; +#ifdef WITH_DISTRO_XRAND_XINERAMA + ret = XRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcID, + CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); +#else + if (x11Context.pXRRSetCrtcConfig) + ret = x11Context.pXRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcID, + CurrentTime, 0, 0, None, RR_Rotate_0, NULL, 0); +#endif + +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRFreeCrtcInfo(pCrctInfo); +#else + if (x11Context.pXRRFreeCrtcInfo) + x11Context.pXRRFreeCrtcInfo(pCrctInfo); +#endif + + /** @todo In case of unsuccesful crtc config set we have to revert frame buffer size and crtc sizes. */ + if (ret == Success) + return true; + else + return false; +} + +static XRRScreenSize currentSize() +{ + XRRScreenSize cSize; + cSize.width = DisplayWidth(x11Context.pDisplay, x11Context.iDefaultScreen); + cSize.mwidth = DisplayWidthMM(x11Context.pDisplay, x11Context.iDefaultScreen); + cSize.height = DisplayHeight(x11Context.pDisplay, x11Context.iDefaultScreen); + cSize.mheight = DisplayHeightMM(x11Context.pDisplay, x11Context.iDefaultScreen); + return cSize; +} + +static unsigned int computeDpi(unsigned int pixels, unsigned int mm) +{ + unsigned int dpi = 0; + if (mm > 0) + dpi = (unsigned int)((double)pixels * MILLIS_PER_INCH / (double)mm + 0.5); + return dpi > 0 ? dpi : (unsigned int)DEFAULT_DPI; +} + +static bool resizeFrameBuffer(struct RANDROUTPUT *paOutputs) +{ + unsigned int iXRes = 0; + unsigned int iYRes = 0; + /* Don't care about the output positions for now. */ + for (int i = 0; i < x11Context.hOutputCount; ++i) + { + if (!paOutputs[i].fEnabled) + continue; + iXRes += paOutputs[i].width; + iYRes = iYRes < paOutputs[i].height ? paOutputs[i].height : iYRes; + } + XRRScreenSize cSize= currentSize(); + unsigned int xdpi = computeDpi(cSize.width, cSize.mwidth); + unsigned int ydpi = computeDpi(cSize.height, cSize.mheight); + unsigned int xmm; + unsigned int ymm; + xmm = (int)(MILLIS_PER_INCH * iXRes / ((double)xdpi) + 0.5); + ymm = (int)(MILLIS_PER_INCH * iYRes / ((double)ydpi) + 0.5); +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, RRScreenChangeNotifyMask); + XRRSetScreenSize(x11Context.pDisplay, x11Context.rootWindow, iXRes, iYRes, xmm, ymm); +#else + if (x11Context.pXRRSelectInput) + x11Context.pXRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, RRScreenChangeNotifyMask); + if (x11Context.pXRRSetScreenSize) + x11Context.pXRRSetScreenSize(x11Context.pDisplay, x11Context.rootWindow, iXRes, iYRes, xmm, ymm); +#endif + XSync(x11Context.pDisplay, False); + XEvent configEvent; + bool event = false; + while (XCheckTypedEvent(x11Context.pDisplay, RRScreenChangeNotify + x11Context.hRandREventBase, &configEvent)) + { +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRUpdateConfiguration(&configEvent); +#else + if (x11Context.pXRRUpdateConfiguration) + x11Context.pXRRUpdateConfiguration(&configEvent); +#endif + event = true; + } +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, 0); +#else + if (x11Context.pXRRSelectInput) + x11Context.pXRRSelectInput(x11Context.pDisplay, x11Context.rootWindow, 0); +#endif + XRRScreenSize newSize = currentSize(); + + /* On Solaris guest, new screen size is not reported properly despite + * RRScreenChangeNotify event arrives. Hense, only check for event here. + * Linux guests do report new size correctly. */ + if ( !event +#ifndef RT_OS_SOLARIS + || newSize.width != (int)iXRes || newSize.height != (int)iYRes +#endif + ) + { + VBClLogError("Resizing frame buffer to %d %d has failed, current mode %d %d\n", + iXRes, iYRes, newSize.width, newSize.height); + return false; + } + return true; +} + +static XRRModeInfo *createMode(int iXRes, int iYRes) +{ + XRRModeInfo *pModeInfo = NULL; + char sModeName[126]; + sprintf(sModeName, "%dx%d_vbox", iXRes, iYRes); +#ifdef WITH_DISTRO_XRAND_XINERAMA + pModeInfo = XRRAllocModeInfo(sModeName, strlen(sModeName)); +#else + if (x11Context.pXRRAllocModeInfo) + pModeInfo = x11Context.pXRRAllocModeInfo(sModeName, strlen(sModeName)); +#endif + pModeInfo->width = iXRes; + pModeInfo->height = iYRes; + + DisplayModeR const mode = VBoxClient_xf86CVTMode(iXRes, iYRes, 60 /*VRefresh */, true /*Reduced */, false /* Interlaced */); + + /* Convert kHz to Hz: f86CVTMode returns clock value in units of kHz, + * XRRCreateMode will expect it in units of Hz. */ + pModeInfo->dotClock = mode.Clock * 1000; + + pModeInfo->hSyncStart = mode.HSyncStart; + pModeInfo->hSyncEnd = mode.HSyncEnd; + pModeInfo->hTotal = mode.HTotal; + pModeInfo->hSkew = mode.HSkew; + pModeInfo->vSyncStart = mode.VSyncStart; + pModeInfo->vSyncEnd = mode.VSyncEnd; + pModeInfo->vTotal = mode.VTotal; + + RRMode newMode = None; +#ifdef WITH_DISTRO_XRAND_XINERAMA + newMode = XRRCreateMode(x11Context.pDisplay, x11Context.rootWindow, pModeInfo); +#else + if (x11Context.pXRRCreateMode) + newMode = x11Context.pXRRCreateMode(x11Context.pDisplay, x11Context.rootWindow, pModeInfo); +#endif + if (newMode == None) + { +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRFreeModeInfo(pModeInfo); +#else + if (x11Context.pXRRFreeModeInfo) + x11Context.pXRRFreeModeInfo(pModeInfo); +#endif + return NULL; + } + pModeInfo->id = newMode; + return pModeInfo; +} + +static bool configureOutput(int iOutputIndex, struct RANDROUTPUT *paOutputs) +{ + if (iOutputIndex >= x11Context.hOutputCount) + { + VBClLogError("Output index %d is greater than # of oputputs %d\n", iOutputIndex, x11Context.hOutputCount); + return false; + } + + AssertReturn(iOutputIndex >= 0, false); + AssertReturn(iOutputIndex < VMW_MAX_HEADS, false); + + /* Remember the last instantiated display mode ID here. This mode will be replaced with the + * new one on the next guest screen resize event. */ + static RRMode aPrevMode[VMW_MAX_HEADS]; + + RROutput outputId = x11Context.pScreenResources->outputs[iOutputIndex]; + XRROutputInfo *pOutputInfo = NULL; +#ifdef WITH_DISTRO_XRAND_XINERAMA + pOutputInfo = XRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, outputId); +#else + if (x11Context.pXRRGetOutputInfo) + pOutputInfo = x11Context.pXRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, outputId); +#endif + if (!pOutputInfo) + return false; + XRRModeInfo *pModeInfo = NULL; + bool fNewMode = false; + /* Index of the mode within the XRRScreenResources.modes array. -1 if such a mode with required resolution does not exists*/ + int iModeIndex = findExistingModeIndex(paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height); + if (iModeIndex != -1 && iModeIndex < x11Context.pScreenResources->nmode) + pModeInfo = &(x11Context.pScreenResources->modes[iModeIndex]); + else + { + /* A mode with required size was not found. Create a new one. */ + pModeInfo = createMode(paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height); + VBClLogInfo("create mode %s (%u) on output %d\n", pModeInfo->name, pModeInfo->id, iOutputIndex); + fNewMode = true; + } + if (!pModeInfo) + { + VBClLogError("Could not create mode for the resolution (%d, %d)\n", + paOutputs[iOutputIndex].width, paOutputs[iOutputIndex].height); + return false; + } + +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRAddOutputMode(x11Context.pDisplay, outputId, pModeInfo->id); +#else + if (x11Context.pXRRAddOutputMode) + x11Context.pXRRAddOutputMode(x11Context.pDisplay, outputId, pModeInfo->id); +#endif + + /* If mode has been newly created, destroy and forget mode created on previous guest screen resize event. */ + if ( aPrevMode[iOutputIndex] > 0 + && pModeInfo->id != aPrevMode[iOutputIndex] + && fNewMode) + { + VBClLogInfo("removing unused mode %u from output %d\n", aPrevMode[iOutputIndex], iOutputIndex); +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRDeleteOutputMode(x11Context.pDisplay, outputId, aPrevMode[iOutputIndex]); + XRRDestroyMode(x11Context.pDisplay, aPrevMode[iOutputIndex]); +#else + if (x11Context.pXRRDeleteOutputMode) + x11Context.pXRRDeleteOutputMode(x11Context.pDisplay, outputId, aPrevMode[iOutputIndex]); + if (x11Context.pXRRDestroyMode) + x11Context.pXRRDestroyMode(x11Context.pDisplay, aPrevMode[iOutputIndex]); +#endif + /* Forget destroyed mode. */ + aPrevMode[iOutputIndex] = 0; + } + + /* Only cache modes created "by us". XRRDestroyMode will complain if provided mode + * was not created by XRRCreateMode call. */ + if (fNewMode) + aPrevMode[iOutputIndex] = pModeInfo->id; + + if (paOutputs[iOutputIndex].fPrimary) + { +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRSetOutputPrimary(x11Context.pDisplay, x11Context.rootWindow, outputId); +#else + if (x11Context.pXRRSetOutputPrimary) + x11Context.pXRRSetOutputPrimary(x11Context.pDisplay, x11Context.rootWindow, outputId); +#endif + } + + /* Make sure outputs crtc is set. */ + pOutputInfo->crtc = pOutputInfo->crtcs[0]; + + RRCrtc crtcId = pOutputInfo->crtcs[0]; + Status ret = Success; +#ifdef WITH_DISTRO_XRAND_XINERAMA + ret = XRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcId, CurrentTime, + paOutputs[iOutputIndex].x, paOutputs[iOutputIndex].y, + pModeInfo->id, RR_Rotate_0, &(outputId), 1 /*int noutputs*/); +#else + if (x11Context.pXRRSetCrtcConfig) + ret = x11Context.pXRRSetCrtcConfig(x11Context.pDisplay, x11Context.pScreenResources, crtcId, CurrentTime, + paOutputs[iOutputIndex].x, paOutputs[iOutputIndex].y, + pModeInfo->id, RR_Rotate_0, &(outputId), 1 /*int noutputs*/); +#endif + if (ret != Success) + VBClLogError("crtc set config failed for output %d\n", iOutputIndex); + +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRFreeOutputInfo(pOutputInfo); +#else + if (x11Context.pXRRFreeOutputInfo) + x11Context.pXRRFreeOutputInfo(pOutputInfo); +#endif + + if (fNewMode) + { +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRFreeModeInfo(pModeInfo); +#else + if (x11Context.pXRRFreeModeInfo) + x11Context.pXRRFreeModeInfo(pModeInfo); +#endif + } + return true; +} + +/** Construct the xrandr command which sets the whole monitor topology each time. */ +static void setXrandrTopology(struct RANDROUTPUT *paOutputs) +{ + if (!x11Context.pDisplay) + { + VBClLogInfo("not connected to X11\n"); + return; + } + + XGrabServer(x11Context.pDisplay); + if (x11Context.fWmwareCtrlExtention) + callVMWCTRL(paOutputs); + +#ifdef WITH_DISTRO_XRAND_XINERAMA + x11Context.pScreenResources = XRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow); +#else + if (x11Context.pXRRGetScreenResources) + x11Context.pScreenResources = x11Context.pXRRGetScreenResources(x11Context.pDisplay, x11Context.rootWindow); +#endif + + x11Context.hOutputCount = RT_VALID_PTR(x11Context.pScreenResources) ? determineOutputCount() : 0; + if (!x11Context.pScreenResources) + { + XUngrabServer(x11Context.pDisplay); + XFlush(x11Context.pDisplay); + return; + } + + /* Disable crtcs. */ + for (int i = 0; i < x11Context.pScreenResources->noutput; ++i) + { + XRROutputInfo *pOutputInfo = NULL; +#ifdef WITH_DISTRO_XRAND_XINERAMA + pOutputInfo = XRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, x11Context.pScreenResources->outputs[i]); +#else + if (x11Context.pXRRGetOutputInfo) + pOutputInfo = x11Context.pXRRGetOutputInfo(x11Context.pDisplay, x11Context.pScreenResources, x11Context.pScreenResources->outputs[i]); +#endif + if (!pOutputInfo) + continue; + if (pOutputInfo->crtc == None) + continue; + + if (!disableCRTC(pOutputInfo->crtc)) + { + VBClLogFatalError("Crtc disable failed %lu\n", pOutputInfo->crtc); + XUngrabServer(x11Context.pDisplay); + XSync(x11Context.pDisplay, False); +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRFreeScreenResources(x11Context.pScreenResources); +#else + if (x11Context.pXRRFreeScreenResources) + x11Context.pXRRFreeScreenResources(x11Context.pScreenResources); +#endif + XFlush(x11Context.pDisplay); + return; + } +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRFreeOutputInfo(pOutputInfo); +#else + if (x11Context.pXRRFreeOutputInfo) + x11Context.pXRRFreeOutputInfo(pOutputInfo); +#endif + } + /* Resize the frame buffer. */ + if (!resizeFrameBuffer(paOutputs)) + { + XUngrabServer(x11Context.pDisplay); + XSync(x11Context.pDisplay, False); +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRFreeScreenResources(x11Context.pScreenResources); +#else + if (x11Context.pXRRFreeScreenResources) + x11Context.pXRRFreeScreenResources(x11Context.pScreenResources); +#endif + XFlush(x11Context.pDisplay); + return; + } + + /* Configure the outputs. */ + for (int i = 0; i < x11Context.hOutputCount; ++i) + { + /* be paranoid. */ + if (i >= x11Context.pScreenResources->noutput) + break; + if (!paOutputs[i].fEnabled) + continue; + if (configureOutput(i, paOutputs)) + VBClLogInfo("output[%d] successfully configured\n", i); + else + VBClLogError("failed to configure output[%d]\n", i); + } + XSync(x11Context.pDisplay, False); +#ifdef WITH_DISTRO_XRAND_XINERAMA + XRRFreeScreenResources(x11Context.pScreenResources); +#else + if (x11Context.pXRRFreeScreenResources) + x11Context.pXRRFreeScreenResources(x11Context.pScreenResources); +#endif + XUngrabServer(x11Context.pDisplay); + XFlush(x11Context.pDisplay); +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vbclSVGAWorker(bool volatile *pfShutdown) +{ + /* Do not acknowledge the first event we query for to pick up old events, + * e.g. from before a guest reboot. */ + bool fAck = false; + bool fFirstRun = true; + static struct VMMDevDisplayDef aMonitors[VMW_MAX_HEADS]; + + int rc = VbglR3CtlFilterMask(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, 0); + if (RT_FAILURE(rc)) + VBClLogFatalError("Failed to request display change events, rc=%Rrc\n", rc); + rc = VbglR3AcquireGuestCaps(VMMDEV_GUEST_SUPPORTS_GRAPHICS, 0, false); + if (RT_FAILURE(rc)) + VBClLogFatalError("Failed to register resizing support, rc=%Rrc\n", rc); + if (rc == VERR_RESOURCE_BUSY) /* Someone else has already acquired it. */ + return VERR_RESOURCE_BUSY; + + /* Let the main thread know that it can continue spawning services. */ + RTThreadUserSignal(RTThreadSelf()); + + for (;;) + { + struct VMMDevDisplayDef aDisplays[VMW_MAX_HEADS]; + uint32_t cDisplaysOut; + /* Query the first size without waiting. This lets us e.g. pick up + * the last event before a guest reboot when we start again after. */ + rc = VbglR3GetDisplayChangeRequestMulti(VMW_MAX_HEADS, &cDisplaysOut, aDisplays, fAck); + fAck = true; + if (RT_FAILURE(rc)) + VBClLogError("Failed to get display change request, rc=%Rrc\n", rc); + if (cDisplaysOut > VMW_MAX_HEADS) + VBClLogError("Display change request contained, rc=%Rrc\n", rc); + if (cDisplaysOut > 0) + { + for (unsigned i = 0; i < cDisplaysOut && i < VMW_MAX_HEADS; ++i) + { + uint32_t idDisplay = aDisplays[i].idDisplay; + if (idDisplay >= VMW_MAX_HEADS) + continue; + aMonitors[idDisplay].fDisplayFlags = aDisplays[i].fDisplayFlags; + if (!(aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_DISABLED)) + { + if (idDisplay == 0 || (aDisplays[i].fDisplayFlags & VMMDEV_DISPLAY_ORIGIN)) + { + aMonitors[idDisplay].xOrigin = aDisplays[i].xOrigin; + aMonitors[idDisplay].yOrigin = aDisplays[i].yOrigin; + } else { + aMonitors[idDisplay].xOrigin = aMonitors[idDisplay - 1].xOrigin + aMonitors[idDisplay - 1].cx; + aMonitors[idDisplay].yOrigin = aMonitors[idDisplay - 1].yOrigin; + } + aMonitors[idDisplay].cx = aDisplays[i].cx; + aMonitors[idDisplay].cy = aDisplays[i].cy; + } + } + /* Create a whole topology and send it to xrandr. */ + struct RANDROUTPUT aOutputs[VMW_MAX_HEADS]; + int iRunningX = 0; + for (int j = 0; j < x11Context.hOutputCount; ++j) + { + aOutputs[j].x = iRunningX; + aOutputs[j].y = aMonitors[j].yOrigin; + aOutputs[j].width = aMonitors[j].cx; + aOutputs[j].height = aMonitors[j].cy; + aOutputs[j].fEnabled = !(aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_DISABLED); + aOutputs[j].fPrimary = (aMonitors[j].fDisplayFlags & VMMDEV_DISPLAY_PRIMARY); + if (aOutputs[j].fEnabled) + iRunningX += aOutputs[j].width; + } + /* In 32-bit guests GAs build on our release machines causes an xserver lock during vmware_ctrl extention + if we do the call withing XGrab. We make the call the said extension only once (to connect the outputs) + rather than at each resize iteration. */ +#if ARCH_BITS == 32 + if (fFirstRun) + callVMWCTRL(aOutputs); +#endif + setXrandrTopology(aOutputs); + /* Wait for some seconds and set toplogy again after the boot. In some desktop environments (cinnamon) where + DE get into our resizing our first resize is reverted by the DE. Sleeping for some secs. helps. Setting + topology a 2nd time resolves the black screen I get after resizing.*/ + if (fFirstRun) + { + sleep(4); + setXrandrTopology(aOutputs); + fFirstRun = false; + } + } + uint32_t events; + do + { + rc = VbglR3WaitEvent(VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST, VBOX_SVGA_HOST_EVENT_RX_TIMEOUT_MS, &events); + } while (rc == VERR_TIMEOUT && !ASMAtomicReadBool(pfShutdown)); + + if (ASMAtomicReadBool(pfShutdown)) + { + /* Shutdown requested. */ + break; + } + else if (RT_FAILURE(rc)) + { + VBClLogFatalError("Failure waiting for event, rc=%Rrc\n", rc); + } + + }; + + return VINF_SUCCESS; +} + +VBCLSERVICE g_SvcDisplaySVGA = +{ + "dp-svga-x11", /* szName */ + "SVGA X11 display", /* pszDescription */ + ".vboxclient-display-svga-x11", /* pszPidFilePathTemplate */ + NULL, /* pszUsage */ + NULL, /* pszOptions */ + NULL, /* pfnOption */ + vbclSVGAInit, /* pfnInit */ + vbclSVGAWorker, /* pfnWorker */ + vbclSVGAStop, /* pfnStop*/ + NULL /* pfnTerm */ +}; + diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.cpp b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.cpp new file mode 100644 index 00000000..5263ffbf --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.cpp @@ -0,0 +1,310 @@ +/* $Id: display-svga-xf86cvt.cpp $ */ +/** @file + * Guest Additions - Our version of xf86CVTMode. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * This file is based on x.org server 1.18.0 file xf86cvt.c: + * + * Copyright 2005-2006 Luc Verhaegen. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#if 0 +/* + * The reason for having this function in a file of its own is + * so that ../utils/cvt/cvt can link to it, and that xf86CVTMode + * code is shared directly. + */ + +#ifdef HAVE_XORG_CONFIG_H +#include <xorg-config.h> +#else +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#endif + +#include "xf86.h" +#include "xf86Modes.h" + +#include <string.h> +#else +# include "VBoxClient.h" +# include "display-svga-xf86cvt.h" +#endif + +/* + * This is a slightly modified version of the xf86CVTMode function from + * xf86cvt.c from the xorg xserver source code. Computes several parameters + * of a display mode out of horizontal and vertical resolutions. Replicated + * here to avoid further dependencies. + * + *---------------------------------------------------------------------------- + * + * Generate a CVT standard mode from HDisplay, VDisplay and VRefresh. + * + * These calculations are stolen from the CVT calculation spreadsheet written + * by Graham Loveridge. He seems to be claiming no copyright and there seems to + * be no license attached to this. He apparently just wants to see his name + * mentioned. + * + * This file can be found at http://www.vesa.org/Public/CVT/CVTd6r1.xls + * + * Comments and structure corresponds to the comments and structure of the xls. + * This should ease importing of future changes to the standard (not very + * likely though). + * + * About margins; i'm sure that they are to be the bit between HDisplay and + * HBlankStart, HBlankEnd and HTotal, VDisplay and VBlankStart, VBlankEnd and + * VTotal, where the overscan colour is shown. FB seems to call _all_ blanking + * outside sync "margin" for some reason. Since we prefer seeing proper + * blanking instead of the overscan colour, and since the Crtc* values will + * probably get altered after us, we will disable margins altogether. With + * these calculations, Margins will plainly expand H/VDisplay, and we don't + * want that. -- libv + * + */ +DisplayModeR VBoxClient_xf86CVTMode(int HDisplay, int VDisplay, float VRefresh /* Herz */, bool Reduced, bool Interlaced) +{ + DisplayModeR Mode; + + /* 1) top/bottom margin size (% of height) - default: 1.8 */ +#define CVT_MARGIN_PERCENTAGE 1.8 + + /* 2) character cell horizontal granularity (pixels) - default 8 */ +#define CVT_H_GRANULARITY 8 + + /* 4) Minimum vertical porch (lines) - default 3 */ +#define CVT_MIN_V_PORCH 3 + + /* 4) Minimum number of vertical back porch lines - default 6 */ +#define CVT_MIN_V_BPORCH 6 + + /* Pixel Clock step (kHz) */ +#define CVT_CLOCK_STEP 250 + + bool Margins = false; + float VFieldRate, HPeriod; + int HDisplayRnd, HMargin; + int VDisplayRnd, VMargin, VSync; + float Interlace; /* Please rename this */ + + /* CVT default is 60.0Hz */ + if (!VRefresh) + VRefresh = 60.0; + + /* 1. Required field rate */ + if (Interlaced) + VFieldRate = VRefresh * 2; + else + VFieldRate = VRefresh; + + /* 2. Horizontal pixels */ + HDisplayRnd = HDisplay - (HDisplay % CVT_H_GRANULARITY); + + /* 3. Determine left and right borders */ + if (Margins) { + /* right margin is actually exactly the same as left */ + HMargin = (int)((float)HDisplayRnd * CVT_MARGIN_PERCENTAGE / 100.0); + HMargin -= HMargin % CVT_H_GRANULARITY; + } + else + HMargin = 0; + + /* 4. Find total active pixels */ + Mode.HDisplay = HDisplayRnd + 2 * HMargin; + + /* 5. Find number of lines per field */ + if (Interlaced) + VDisplayRnd = VDisplay / 2; + else + VDisplayRnd = VDisplay; + + /* 6. Find top and bottom margins */ + /* nope. */ + if (Margins) + /* top and bottom margins are equal again. */ + VMargin = (int)((float)VDisplayRnd * CVT_MARGIN_PERCENTAGE / 100.0); + else + VMargin = 0; + + Mode.VDisplay = VDisplay + 2 * VMargin; + + /* 7. Interlace */ + if (Interlaced) + Interlace = 0.5; + else + Interlace = 0.0; + + /* Determine VSync Width from aspect ratio */ + if (!(VDisplay % 3) && ((VDisplay * 4 / 3) == HDisplay)) + VSync = 4; + else if (!(VDisplay % 9) && ((VDisplay * 16 / 9) == HDisplay)) + VSync = 5; + else if (!(VDisplay % 10) && ((VDisplay * 16 / 10) == HDisplay)) + VSync = 6; + else if (!(VDisplay % 4) && ((VDisplay * 5 / 4) == HDisplay)) + VSync = 7; + else if (!(VDisplay % 9) && ((VDisplay * 15 / 9) == HDisplay)) + VSync = 7; + else /* Custom */ + VSync = 10; + + if (!Reduced) { /* simplified GTF calculation */ + + /* 4) Minimum time of vertical sync + back porch interval (µs) + * default 550.0 */ +#define CVT_MIN_VSYNC_BP 550.0 + + /* 3) Nominal HSync width (% of line period) - default 8 */ +#define CVT_HSYNC_PERCENTAGE 8 + + float HBlankPercentage; + int VSyncAndBackPorch, VBackPorch; + int HBlank; + + /* 8. Estimated Horizontal period */ + HPeriod = ((float)(1000000.0 / VFieldRate - CVT_MIN_VSYNC_BP)) + / (VDisplayRnd + 2 * VMargin + CVT_MIN_V_PORCH + Interlace); + + /* 9. Find number of lines in sync + backporch */ + if ((int)(CVT_MIN_VSYNC_BP / HPeriod) + 1 < VSync + CVT_MIN_V_PORCH) + VSyncAndBackPorch = VSync + CVT_MIN_V_PORCH; + else + VSyncAndBackPorch = (int)(CVT_MIN_VSYNC_BP / HPeriod) + 1; + + /* 10. Find number of lines in back porch */ + VBackPorch = VSyncAndBackPorch - VSync; + (void) VBackPorch; + + /* 11. Find total number of lines in vertical field */ + Mode.VTotal = (int)(VDisplayRnd + 2 * VMargin + VSyncAndBackPorch + Interlace + CVT_MIN_V_PORCH); + + /* 5) Definition of Horizontal blanking time limitation */ + /* Gradient (%/kHz) - default 600 */ +#define CVT_M_FACTOR 600 + + /* Offset (%) - default 40 */ +#define CVT_C_FACTOR 40 + + /* Blanking time scaling factor - default 128 */ +#define CVT_K_FACTOR 128 + + /* Scaling factor weighting - default 20 */ +#define CVT_J_FACTOR 20 + +#define CVT_M_PRIME (CVT_M_FACTOR * CVT_K_FACTOR / 256) +#define CVT_C_PRIME ((CVT_C_FACTOR - CVT_J_FACTOR) * CVT_K_FACTOR / 256 + CVT_J_FACTOR) + + /* 12. Find ideal blanking duty cycle from formula */ + HBlankPercentage = CVT_C_PRIME - CVT_M_PRIME * HPeriod / 1000.0; + + /* 13. Blanking time */ + if (HBlankPercentage < 20) + HBlankPercentage = 20; + + HBlank = (int)(Mode.HDisplay * HBlankPercentage / (100.0 - HBlankPercentage)); + HBlank -= HBlank % (2 * CVT_H_GRANULARITY); + + /* 14. Find total number of pixels in a line. */ + Mode.HTotal = Mode.HDisplay + HBlank; + + /* Fill in HSync values */ + Mode.HSyncEnd = Mode.HDisplay + HBlank / 2; + + Mode.HSyncStart = Mode.HSyncEnd - (Mode.HTotal * CVT_HSYNC_PERCENTAGE) / 100; + Mode.HSyncStart += CVT_H_GRANULARITY - Mode.HSyncStart % CVT_H_GRANULARITY; + + /* Fill in VSync values */ + Mode.VSyncStart = Mode.VDisplay + CVT_MIN_V_PORCH; + Mode.VSyncEnd = Mode.VSyncStart + VSync; + + } + else { /* Reduced blanking */ + /* Minimum vertical blanking interval time (µs) - default 460 */ +#define CVT_RB_MIN_VBLANK 460.0 + + /* Fixed number of clocks for horizontal sync */ +#define CVT_RB_H_SYNC 32.0 + + /* Fixed number of clocks for horizontal blanking */ +#define CVT_RB_H_BLANK 160.0 + + /* Fixed number of lines for vertical front porch - default 3 */ +#define CVT_RB_VFPORCH 3 + + int VBILines; + + /* 8. Estimate Horizontal period. */ + HPeriod = ((float)(1000000.0 / VFieldRate - CVT_RB_MIN_VBLANK)) / (VDisplayRnd + 2 * VMargin); + + /* 9. Find number of lines in vertical blanking */ + VBILines = (int)((float)CVT_RB_MIN_VBLANK / HPeriod + 1); + + /* 10. Check if vertical blanking is sufficient */ + if (VBILines < CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH) + VBILines = CVT_RB_VFPORCH + VSync + CVT_MIN_V_BPORCH; + + /* 11. Find total number of lines in vertical field */ + Mode.VTotal = (int)(VDisplayRnd + 2 * VMargin + Interlace + VBILines); + + /* 12. Find total number of pixels in a line */ + Mode.HTotal = (int)(Mode.HDisplay + CVT_RB_H_BLANK); + + /* Fill in HSync values */ + Mode.HSyncEnd = (int)(Mode.HDisplay + CVT_RB_H_BLANK / 2); + Mode.HSyncStart = (int)(Mode.HSyncEnd - CVT_RB_H_SYNC); + + /* Fill in VSync values */ + Mode.VSyncStart = Mode.VDisplay + CVT_RB_VFPORCH; + Mode.VSyncEnd = Mode.VSyncStart + VSync; + } + /* 15/13. Find pixel clock frequency (kHz for xf86) */ + Mode.Clock = (int)(Mode.HTotal * 1000.0 / HPeriod); + Mode.Clock -= Mode.Clock % CVT_CLOCK_STEP; + + /* 16/14. Find actual Horizontal Frequency (kHz) */ + Mode.HSync = (float)Mode.Clock / (float)Mode.HTotal; + + /* 17/15. Find actual Field rate */ + Mode.VRefresh = (1000.0 * (float)Mode.Clock) / (float)(Mode.HTotal * Mode.VTotal); + + /* 18/16. Find actual vertical frame frequency */ + /* ignore - just set the mode flag for interlaced */ + if (Interlaced) + Mode.VTotal *= 2; + +#if 0 + XNFasprintf(&tmp, "%dx%d", HDisplay, VDisplay); + Mode->name = tmp; + + if (Reduced) + Mode->Flags |= V_PHSYNC | V_NVSYNC; + else + Mode->Flags |= V_NHSYNC | V_PVSYNC; + + if (Interlaced) + Mode->Flags |= V_INTERLACE; +#endif + + return Mode; +} diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.h b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.h new file mode 100644 index 00000000..431f17c7 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display-svga-xf86cvt.h @@ -0,0 +1,56 @@ +/* $Id: display-svga-xf86cvt.h $ */ +/** @file + * Guest Additions - Header for display-svga-xf86ctv.cpp. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_x11_VBoxClient_display_svga_xf86cvt_h +#define GA_INCLUDED_SRC_x11_VBoxClient_display_svga_xf86cvt_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + + +typedef struct DisplayModeR +{ + int Clock; + int HDisplay; + int HSyncStart; + int HSyncEnd; + int HTotal; + int HSkew; + int VDisplay; + int VSyncStart; + int VSyncEnd; + int VTotal; + int VScan; + float HSync; + float VRefresh; +} DisplayModeR; + +DisplayModeR VBoxClient_xf86CVTMode(int HDisplay, int VDisplay, float VRefresh /* Herz */, bool Reduced, bool Interlaced); + + +#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_display_svga_xf86cvt_h */ + diff --git a/src/VBox/Additions/x11/VBoxClient/display.cpp b/src/VBox/Additions/x11/VBoxClient/display.cpp new file mode 100644 index 00000000..3edbfd31 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/display.cpp @@ -0,0 +1,315 @@ +/* $Id: display.cpp $ */ +/** @file + * X11 guest client - display management. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "VBoxClient.h" + +#include <iprt/errcore.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/asm.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/extensions/Xrandr.h> + +/** @todo this should probably be replaced by something IPRT */ +/* For system() and WEXITSTATUS() */ +#include <stdlib.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <errno.h> +#include <limits.h> +#include <poll.h> +#include <time.h> +#include <dlfcn.h> + +/* TESTING: Dynamic resizing and mouse integration toggling should work + * correctly with a range of X servers (pre-1.3, 1.3 and later under Linux, 1.3 + * and later under Solaris) with Guest Additions installed. Switching to a + * virtual terminal while a user session is in place should disable dynamic + * resizing and cursor integration, switching back should re-enable them. */ + +/** State information needed for the service. The main VBoxClient code provides + * the daemon logic needed by all services. */ +struct DISPLAYSTATE +{ + /** Are we initialised yet? */ + bool mfInit; + /** The connection to the server. */ + Display *pDisplay; + /** The RandR extension base event number. */ + int cRREventBase; + /** Can we use version 1.2 or later of the RandR protocol here? */ + bool fHaveRandR12; + /** The command argument to use for the xrandr binary. Currently only + * used to support the non-standard location on some Solaris systems - + * would it make sense to use absolute paths on all systems? */ + const char *pcszXrandr; + /** Was there a recent mode hint with no following root window resize, and + * if so, have we waited for a reasonable time? */ + time_t timeLastModeHint; + /** Handle to libXrandr. */ + void *pRandLibraryHandle; + /** Handle to pXRRSelectInput. */ + void (*pXRRSelectInput) (Display *, Window, int); + /** Handle to pXRRQueryExtension. */ + Bool (*pXRRQueryExtension) (Display *, int *, int *); +}; + +static struct DISPLAYSTATE g_DisplayState; + +static unsigned char *getRootProperty(struct DISPLAYSTATE *pState, const char *pszName, + long cItems, Atom type) +{ + Atom actualType = None; + int iFormat = 0; + unsigned long cReturned = 0; + unsigned long cAfter = 0; + unsigned char *pData = 0; + + if (XGetWindowProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), + XInternAtom(pState->pDisplay, pszName, 0), 0, cItems, + False /* delete */, type, &actualType, &iFormat, + &cReturned, &cAfter, &pData)) + return NULL; + return pData; +} + +static void doResize(struct DISPLAYSTATE *pState) +{ + /** @note The xrandr command can fail if something else accesses RandR at + * the same time. We just ignore failure for now as we do not know what + * someone else is doing. */ + if (!pState->fHaveRandR12) + { + char szCommand[256]; + unsigned char *pData; + + pData = getRootProperty(pState, "VBOXVIDEO_PREFERRED_MODE", 1, XA_INTEGER); + if (pData != NULL) + { + RTStrPrintf(szCommand, sizeof(szCommand), "%s -s %ux%u", + pState->pcszXrandr, ((unsigned long *)pData)[0] >> 16, ((unsigned long *)pData)[0] & 0xFFFF); + int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc); + XFree(pData); + } + } + else + { + const char szCommandBase[] = + "%s --output VGA-0 --auto --output VGA-1 --auto --right-of VGA-0 " + "--output VGA-2 --auto --right-of VGA-1 --output VGA-3 --auto --right-of VGA-2 " + "--output VGA-4 --auto --right-of VGA-3 --output VGA-5 --auto --right-of VGA-4 " + "--output VGA-6 --auto --right-of VGA-5 --output VGA-7 --auto --right-of VGA-6 " + "--output VGA-8 --auto --right-of VGA-7 --output VGA-9 --auto --right-of VGA-8 " + "--output VGA-10 --auto --right-of VGA-9 --output VGA-11 --auto --right-of VGA-10 " + "--output VGA-12 --auto --right-of VGA-11 --output VGA-13 --auto --right-of VGA-12 " + "--output VGA-14 --auto --right-of VGA-13 --output VGA-15 --auto --right-of VGA-14 " + "--output VGA-16 --auto --right-of VGA-15 --output VGA-17 --auto --right-of VGA-16 " + "--output VGA-18 --auto --right-of VGA-17 --output VGA-19 --auto --right-of VGA-18 " + "--output VGA-20 --auto --right-of VGA-19 --output VGA-21 --auto --right-of VGA-20 " + "--output VGA-22 --auto --right-of VGA-21 --output VGA-23 --auto --right-of VGA-22 " + "--output VGA-24 --auto --right-of VGA-23 --output VGA-25 --auto --right-of VGA-24 " + "--output VGA-26 --auto --right-of VGA-25 --output VGA-27 --auto --right-of VGA-26 " + "--output VGA-28 --auto --right-of VGA-27 --output VGA-29 --auto --right-of VGA-28 " + "--output VGA-30 --auto --right-of VGA-29 --output VGA-31 --auto --right-of VGA-30"; + char szCommand[sizeof(szCommandBase) + 256]; + RTStrPrintf(szCommand, sizeof(szCommand), szCommandBase, pState->pcszXrandr); + int rcShutUpGcc = system(szCommand); RT_NOREF_PV(rcShutUpGcc); + } +} + +/** Main loop: handle display hot-plug events, property updates (which can + * signal VT switches hot-plug in old X servers). */ +static void runDisplay(struct DISPLAYSTATE *pState, bool volatile *pfShutdown) +{ + Display *pDisplay = pState->pDisplay; + long cValue = 1; + + /* One way or another we want the preferred mode at server start-up. */ + doResize(pState); + XSelectInput(pDisplay, DefaultRootWindow(pDisplay), PropertyChangeMask | StructureNotifyMask); + if (pState->fHaveRandR12) + pState->pXRRSelectInput(pDisplay, DefaultRootWindow(pDisplay), RRScreenChangeNotifyMask); + /* Semantics: when VBOXCLIENT_STARTED is set, pre-1.3 X.Org Server driver + * assumes that a client capable of handling mode hints will be present for the + * rest of the X session. If we crash things will not work as they should. + * I thought that preferable to implementing complex crash-handling logic. + */ + XChangeProperty(pState->pDisplay, DefaultRootWindow(pState->pDisplay), XInternAtom(pState->pDisplay, "VBOXCLIENT_STARTED", 0), + XA_INTEGER, 32, PropModeReplace, (unsigned char *)&cValue, 1); + /* Interrupting this cleanly will be more work than making it robust + * against spontaneous termination, especially as it will never get + * properly tested, so I will go for the second. */ + while (!ASMAtomicReadBool(pfShutdown)) + { + XEvent event; + struct pollfd PollFd; + int pollTimeOut = -1; + int cFds; + + /* Do not handle overflow. */ + if (pState->timeLastModeHint > 0 && pState->timeLastModeHint < INT_MAX - 2) + pollTimeOut = 2 - (time(0) - pState->timeLastModeHint); + PollFd.fd = ConnectionNumber(pDisplay); + PollFd.events = POLLIN; /* Hang-up is always reported. */ + XFlush(pDisplay); + cFds = poll(&PollFd, 1, pollTimeOut >= 0 ? pollTimeOut * 1000 : -1); + while (XPending(pDisplay)) + { + XNextEvent(pDisplay, &event); + /* This property is deleted when the server regains the virtual + * terminal. Force the main thread to call xrandr again, as old X + * servers could not handle it while switched out. */ + if ( !pState->fHaveRandR12 + && event.type == PropertyNotify + && event.xproperty.state == PropertyDelete + && event.xproperty.window == DefaultRootWindow(pDisplay) + && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_NO_VT", False)) + doResize(pState); + if ( !pState->fHaveRandR12 + && event.type == PropertyNotify + && event.xproperty.state == PropertyNewValue + && event.xproperty.window == DefaultRootWindow(pDisplay) + && event.xproperty.atom == XInternAtom(pDisplay, "VBOXVIDEO_PREFERRED_MODE", False)) + doResize(pState); + if ( pState->fHaveRandR12 + && event.type == pState->cRREventBase + RRScreenChangeNotify) + pState->timeLastModeHint = time(0); + if ( event.type == ConfigureNotify + && event.xproperty.window == DefaultRootWindow(pDisplay)) + pState->timeLastModeHint = 0; + } + if (cFds == 0 && pState->timeLastModeHint > 0) + doResize(pState); + } +} + +static int initDisplay(struct DISPLAYSTATE *pState) +{ + char szCommand[256]; + int status; + + pState->pRandLibraryHandle = dlopen("libXrandr.so", RTLD_LAZY /*| RTLD_LOCAL */); + if (!pState->pRandLibraryHandle) + pState->pRandLibraryHandle = dlopen("libXrandr.so.2", RTLD_LAZY /*| RTLD_LOCAL */); + if (!pState->pRandLibraryHandle) + pState->pRandLibraryHandle = dlopen("libXrandr.so.2.2.0", RTLD_LAZY /*| RTLD_LOCAL */); + + if (!RT_VALID_PTR(pState->pRandLibraryHandle)) + { + VBClLogFatalError("Could not locate libXrandr for dlopen\n"); + return VERR_NOT_FOUND; + } + + *(void **)(&pState->pXRRSelectInput) = dlsym(pState->pRandLibraryHandle, "XRRSelectInput"); + *(void **)(&pState->pXRRQueryExtension) = dlsym(pState->pRandLibraryHandle, "XRRQueryExtension"); + + if ( !RT_VALID_PTR(pState->pXRRSelectInput) + || !RT_VALID_PTR(pState->pXRRQueryExtension)) + { + VBClLogFatalError("Could not load required libXrandr symbols\n"); + dlclose(pState->pRandLibraryHandle); + pState->pRandLibraryHandle = NULL; + return VERR_NOT_FOUND; + } + + pState->pDisplay = XOpenDisplay(NULL); + if (!pState->pDisplay) + return VERR_NOT_FOUND; + if (!pState->pXRRQueryExtension(pState->pDisplay, &pState->cRREventBase, &status)) + return VERR_NOT_FOUND; + pState->fHaveRandR12 = false; + pState->pcszXrandr = "xrandr"; + if (RTFileExists("/usr/X11/bin/xrandr")) + pState->pcszXrandr = "/usr/X11/bin/xrandr"; + status = system(pState->pcszXrandr); + if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */ + VBClLogFatalError("Failed to execute the xrandr utility\n"); + RTStrPrintf(szCommand, sizeof(szCommand), "%s --q12", pState->pcszXrandr); + status = system(szCommand); + if (WEXITSTATUS(status) == 0) + pState->fHaveRandR12 = true; + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnInit} + */ +static DECLCALLBACK(int) init(void) +{ + struct DISPLAYSTATE *pSelf = &g_DisplayState; + int rc; + + if (pSelf->mfInit) + return VERR_WRONG_ORDER; + rc = initDisplay(pSelf); + if (RT_FAILURE(rc)) + return rc; + if (RT_SUCCESS(rc)) + pSelf->mfInit = true; + return rc; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) run(bool volatile *pfShutdown) +{ + struct DISPLAYSTATE *pSelf = &g_DisplayState; + + if (!pSelf->mfInit) + return VERR_WRONG_ORDER; + + runDisplay(pSelf, pfShutdown); + + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnStop} + */ +static DECLCALLBACK(void) stop(void) +{ + /* Nothing to do here. Implement empty callback, so + * main thread can set pfShutdown=true on process termination. */ +} + +VBCLSERVICE g_SvcDisplayLegacy = +{ + "dp-legacy-x11", /* szName */ + "Legacy display assistant", /* pszDescription */ + ".vboxclient-display", /* pszPidFilePathTemplate */ + NULL, /* pszUsage */ + NULL, /* pszOptions */ + NULL, /* pfnOption */ + init, /* pfnInit */ + run, /* pfnWorker */ + stop, /* pfnStop */ + NULL, /* pfnTerm */ +}; diff --git a/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp b/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp new file mode 100644 index 00000000..3edb7e98 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/draganddrop.cpp @@ -0,0 +1,3877 @@ +/* $Id: draganddrop.cpp $ */ +/** @file + * X11 guest client - Drag and drop implementation. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/Xatom.h> +#ifdef VBOX_DND_WITH_XTEST +# include <X11/extensions/XTest.h> +#endif + +#include <iprt/asm.h> +#include <iprt/buildconfig.h> +#include <iprt/critsect.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include <iprt/cpp/mtlist.h> +#include <iprt/cpp/ministring.h> + +#include <limits.h> + +#ifdef LOG_GROUP +# undef LOG_GROUP +#endif +#define LOG_GROUP LOG_GROUP_GUEST_DND +#include <VBox/log.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/version.h> + +#include "VBox/HostServices/DragAndDropSvc.h" +#include "VBoxClient.h" + + +/* Enable this to handle drag'n drop "promises". + * This is needed for supporting certain applications (i.e. PcManFM on LXDE), + * which require the drag'n drop meta data a lot earlier than actually needed. + * That behavior is similar to macOS' drag'n drop promises, hence the name. + * + * Those applications query the data right while dragging over them (see GtkWidget::drag-motion), + * instead of when the source dropped the data (GtkWidget::drag-drop). + * + * This might be entirely implementation-specific, so not being a bug in GTK/GDK. Also see #9820. + */ +#ifdef VBOX_WITH_DRAG_AND_DROP_PROMISES +# undef VBOX_WITH_DRAG_AND_DROP_PROMISES +#endif + +/** + * For X11 guest Xdnd is used. See http://www.acc.umu.se/~vatten/XDND.html for + * a walk trough. + * + * Also useful pages: + * - https://www.freedesktop.org/wiki/Draganddropwarts/ + * - https://www.freedesktop.org/wiki/Specifications/XDNDRevision/ + * + * Host -> Guest: + * For X11 this means mainly forwarding all the events from HGCM to the + * appropriate X11 events. There exists a proxy window, which is invisible and + * used for all the X11 communication. On a HGCM Enter event, we set our proxy + * window as XdndSelection owner with the given mime-types. On every HGCM move + * event, we move the X11 mouse cursor to the new position and query for the + * window below that position. Depending on if it is XdndAware, a new window or + * a known window, we send the appropriate X11 messages to it. On HGCM drop, we + * send a XdndDrop message to the current window and wait for a X11 + * SelectionMessage from the target window. Because we didn't have the data in + * the requested mime-type, yet, we save that message and ask the host for the + * data. When the data is successfully received from the host, we put the data + * as a property to the window and send a X11 SelectionNotify event to the + * target window. + * + * Guest -> Host: + * This is a lot more trickery than H->G. When a pending event from HGCM + * arrives, we ask if there currently is an owner of the XdndSelection + * property. If so, our proxy window is shown (1x1, but without backing store) + * and some mouse event is triggered. This should be followed by an XdndEnter + * event send to the proxy window. From this event we can fetch the necessary + * info of the MIME types and allowed actions and send this back to the host. + * On a drop request from the host, we query for the selection and should get + * the data in the specified mime-type. This data is send back to the host. + * After that we send a XdndLeave event to the source window. + * + ** @todo Cancelling (e.g. with ESC key) doesn't work. + ** @todo INCR (incremental transfers) support. + ** @todo Really check for the Xdnd version and the supported features. + ** @todo Either get rid of the xHelpers class or properly unify the code with the drag instance class. + */ + +/********************************************************************************************************************************* + * Definitions * + ********************************************************************************************************************************/ + +/** The Xdnd protocol version we support. */ +#define VBOX_XDND_VERSION (5) + +/** No flags specified. */ +#define VBOX_XDND_STATUS_FLAG_NONE 0 +/** Whether the target window accepts the data being dragged over or not. */ +#define VBOX_XDND_STATUS_FLAG_ACCEPT RT_BIT(0) +/** Whether the target window wants XdndPosition messages while dragging stuff over it. */ +#define VBOX_XDND_STATUS_FLAG_WANTS_POS RT_BIT(1) + +/** Whether the target window accepted the drop data or not. */ +#define VBOX_XDND_FINISHED_FLAG_SUCCEEDED RT_BIT(0) + +/** How many X properties our proxy window can hold. */ +#define VBOX_MAX_XPROPERTIES (LONG_MAX-1) + +/** The notification header text for VBClShowNotify(). */ +#define VBOX_DND_SHOWNOTIFY_HEADER VBOX_PRODUCT " Drag'n Drop" + +/** + * Structure for storing new X11 events and HGCM messages + * into a single event queue. + */ +typedef struct DNDEVENT +{ + enum DnDEventType + { + /** Unknown event, do not use. */ + DnDEventType_Unknown = 0, + /** VBGLR3DNDEVENT event. */ + DnDEventType_HGCM, + /** X11 event. */ + DnDEventType_X11, + /** Blow the type up to 32-bit. */ + DnDEventType_32BIT_HACK = 0x7fffffff + }; + /** Event type. */ + DnDEventType enmType; + union + { + PVBGLR3DNDEVENT hgcm; + XEvent x11; + }; +#ifdef IN_GUEST + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#endif +} DNDEVENT; +/** Pointer to a DnD event. */ +typedef DNDEVENT *PDNDEVENT; + +enum XA_Type +{ + /* States */ + XA_WM_STATE = 0, + /* Properties */ + XA_TARGETS, + XA_MULTIPLE, + XA_INCR, + /* Mime Types */ + XA_image_bmp, + XA_image_jpg, + XA_image_tiff, + XA_image_png, + XA_text_uri_list, + XA_text_uri, + XA_text_plain, + XA_TEXT, + /* Xdnd */ + XA_XdndSelection, + XA_XdndAware, + XA_XdndEnter, + XA_XdndLeave, + XA_XdndTypeList, + XA_XdndActionList, + XA_XdndPosition, + XA_XdndActionCopy, + XA_XdndActionMove, + XA_XdndActionLink, + XA_XdndStatus, + XA_XdndDrop, + XA_XdndFinished, + /* Our own stop marker */ + XA_dndstop, + /* End marker */ + XA_End +}; + +/** + * Xdnd message value indices, sorted by message type. + */ +typedef enum XdndMsg +{ + /** XdndEnter. */ + XdndEnterTypeCount = 3, /* Maximum number of types in XdndEnter message. */ + + XdndEnterWindow = 0, /* Source window (sender). */ + XdndEnterFlags, /* Version in high byte, bit 0 => more data types. */ + XdndEnterType1, /* First available data type. */ + XdndEnterType2, /* Second available data type. */ + XdndEnterType3, /* Third available data type. */ + + XdndEnterMoreTypesFlag = 1, /* Set if there are more than XdndEnterTypeCount. */ + XdndEnterVersionRShift = 24, /* Right shift to position version number. */ + XdndEnterVersionMask = 0xFF, /* Mask to get version after shifting. */ + + /** XdndHere. */ + XdndHereWindow = 0, /* Source window (sender). */ + XdndHereFlags, /* Reserved. */ + XdndHerePt, /* X + Y coordinates of mouse (root window coords). */ + XdndHereTimeStamp, /* Timestamp for requesting data. */ + XdndHereAction, /* Action requested by user. */ + + /** XdndPosition. */ + XdndPositionWindow = 0, /* Source window (sender). */ + XdndPositionFlags, /* Flags. */ + XdndPositionXY, /* X/Y coordinates of the mouse position relative to the root window. */ + XdndPositionTimeStamp, /* Time stamp for retrieving the data. */ + XdndPositionAction, /* Action requested by the user. */ + + /** XdndStatus. */ + XdndStatusWindow = 0, /* Target window (sender).*/ + XdndStatusFlags, /* Flags returned by target. */ + XdndStatusNoMsgXY, /* X + Y of "no msg" rectangle (root window coords). */ + XdndStatusNoMsgWH, /* Width + height of "no msg" rectangle. */ + XdndStatusAction, /* Action accepted by target. */ + + XdndStatusAcceptDropFlag = 1, /* Set if target will accept the drop. */ + XdndStatusSendHereFlag = 2, /* Set if target wants a stream of XdndPosition. */ + + /** XdndLeave. */ + XdndLeaveWindow = 0, /* Source window (sender). */ + XdndLeaveFlags, /* Reserved. */ + + /** XdndDrop. */ + XdndDropWindow = 0, /* Source window (sender). */ + XdndDropFlags, /* Reserved. */ + XdndDropTimeStamp, /* Timestamp for requesting data. */ + + /** XdndFinished. */ + XdndFinishedWindow = 0, /* Target window (sender). */ + XdndFinishedFlags, /* Since version 5: Bit 0 is set if the current target accepted the drop. */ + XdndFinishedAction /* Since version 5: Contains the action performed by the target. */ + +} XdndMsg; + +class DragAndDropService; + +/** List of Atoms. */ +#define VBoxDnDAtomList RTCList<Atom> + +class xHelpers +{ +public: + + static xHelpers *getInstance(Display *pDisplay = 0) + { + if (!m_pInstance) + { + AssertPtrReturn(pDisplay, NULL); + m_pInstance = new xHelpers(pDisplay); + } + + return m_pInstance; + } + + static void destroyInstance(void) + { + if (m_pInstance) + { + delete m_pInstance; + m_pInstance = NULL; + } + } + + inline Display *display() const { return m_pDisplay; } + inline Atom xAtom(XA_Type e) const { return m_xAtoms[e]; } + + inline Atom stringToxAtom(const char *pcszString) const + { + return XInternAtom(m_pDisplay, pcszString, False); + } + inline RTCString xAtomToString(Atom atom) const + { + if (atom == None) return "None"; + + char* pcsAtom = XGetAtomName(m_pDisplay, atom); + RTCString strAtom(pcsAtom); + XFree(pcsAtom); + + return strAtom; + } + + inline RTCString xAtomListToString(const VBoxDnDAtomList &formatList, const RTCString &strSep = DND_FORMATS_SEPARATOR_STR) + { + RTCString format; + for (size_t i = 0; i < formatList.size(); ++i) + format += xAtomToString(formatList.at(i)) + strSep; + return format; + } + + /** + * Returns a filtered X11 atom list. + * + * @returns Filtered list. + * @param formatList Atom list to convert. + * @param filterList Atom list to filter out. + */ + inline VBoxDnDAtomList xAtomListFiltered(const VBoxDnDAtomList &formatList, const VBoxDnDAtomList &filterList) + { + VBoxDnDAtomList tempList = formatList; + tempList.filter(filterList); + return tempList; + } + + RTCString xErrorToString(int xRc) const; + Window applicationWindowBelowCursor(Window parentWin) const; + +private: +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#endif + xHelpers(Display *pDisplay) + : m_pDisplay(pDisplay) + { + /* Not all x11 atoms we use are defined in the headers. Create the + * additional one we need here. */ + for (int i = 0; i < XA_End; ++i) + m_xAtoms[i] = XInternAtom(m_pDisplay, m_xAtomNames[i], False); + }; + + /* Private member vars */ + static xHelpers *m_pInstance; + Display *m_pDisplay; + Atom m_xAtoms[XA_End]; + static const char *m_xAtomNames[XA_End]; +}; + +/* Some xHelpers convenience defines. */ +#define gX11 xHelpers::getInstance() +#define xAtom(xa) xHelpers::getInstance()->xAtom((xa)) +#define xAtomToString(xa) xHelpers::getInstance()->xAtomToString((xa)) + +/********************************************************************************************************************************* + * xHelpers implementation. * + ********************************************************************************************************************************/ + +xHelpers *xHelpers::m_pInstance = NULL; + +/* Has to be in sync with the XA_Type enum. */ +const char *xHelpers::m_xAtomNames[] = +{ + /* States */ + "WM_STATE", + /* Properties */ + "TARGETS", + "MULTIPLE", + "INCR", + /* Mime Types */ + "image/bmp", + "image/jpg", + "image/tiff", + "image/png", + "text/uri-list", + "text/uri", + "text/plain", + "TEXT", + /* Xdnd */ + "XdndSelection", + "XdndAware", + "XdndEnter", + "XdndLeave", + "XdndTypeList", + "XdndActionList", + "XdndPosition", + "XdndActionCopy", + "XdndActionMove", + "XdndActionLink", + "XdndStatus", + "XdndDrop", + "XdndFinished", + /* Our own stop marker */ + "dndstop" +}; + +RTCString xHelpers::xErrorToString(int xRc) const +{ + switch (xRc) + { + case Success: return RTCStringFmt("%d (Success)", xRc); break; + case BadRequest: return RTCStringFmt("%d (BadRequest)", xRc); break; + case BadValue: return RTCStringFmt("%d (BadValue)", xRc); break; + case BadWindow: return RTCStringFmt("%d (BadWindow)", xRc); break; + case BadPixmap: return RTCStringFmt("%d (BadPixmap)", xRc); break; + case BadAtom: return RTCStringFmt("%d (BadAtom)", xRc); break; + case BadCursor: return RTCStringFmt("%d (BadCursor)", xRc); break; + case BadFont: return RTCStringFmt("%d (BadFont)", xRc); break; + case BadMatch: return RTCStringFmt("%d (BadMatch)", xRc); break; + case BadDrawable: return RTCStringFmt("%d (BadDrawable)", xRc); break; + case BadAccess: return RTCStringFmt("%d (BadAccess)", xRc); break; + case BadAlloc: return RTCStringFmt("%d (BadAlloc)", xRc); break; + case BadColor: return RTCStringFmt("%d (BadColor)", xRc); break; + case BadGC: return RTCStringFmt("%d (BadGC)", xRc); break; + case BadIDChoice: return RTCStringFmt("%d (BadIDChoice)", xRc); break; + case BadName: return RTCStringFmt("%d (BadName)", xRc); break; + case BadLength: return RTCStringFmt("%d (BadLength)", xRc); break; + case BadImplementation: return RTCStringFmt("%d (BadImplementation)", xRc); break; + } + return RTCStringFmt("%d (unknown)", xRc); +} + +/** @todo Make this iterative. */ +Window xHelpers::applicationWindowBelowCursor(Window wndParent) const +{ + /* No parent, nothing to do. */ + if(wndParent == 0) + return 0; + + Window wndApp = 0; + int cProps = -1; + + /* Fetch all x11 window properties of the parent window. */ + Atom *pProps = XListProperties(m_pDisplay, wndParent, &cProps); + if (cProps > 0) + { + /* We check the window for the WM_STATE property. */ + for (int i = 0; i < cProps; ++i) + { + if (pProps[i] == xAtom(XA_WM_STATE)) + { + /* Found it. */ + wndApp = wndParent; + break; + } + } + + /* Cleanup */ + XFree(pProps); + } + + if (!wndApp) + { + Window wndChild, wndTemp; + int tmp; + unsigned int utmp; + + /* Query the next child window of the parent window at the current + * mouse position. */ + XQueryPointer(m_pDisplay, wndParent, &wndTemp, &wndChild, &tmp, &tmp, &tmp, &tmp, &utmp); + + /* Recursive call our self to dive into the child tree. */ + wndApp = applicationWindowBelowCursor(wndChild); + } + + return wndApp; +} + +#ifdef DEBUG +# define VBOX_DND_FN_DECL_LOG(x) inline x /* For LogFlowXXX logging. */ +#else +# define VBOX_DND_FN_DECL_LOG(x) x +#endif + +/** + * Class which handles a single drag'n drop proxy window. + ** @todo Move all proxy window-related stuff into this class! Clean up this mess. + */ +class VBoxDnDProxyWnd +{ + +public: +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#endif + VBoxDnDProxyWnd(void); + virtual ~VBoxDnDProxyWnd(void); + +public: + + int init(Display *pDisplay); + void destroy(); + + int sendFinished(Window hWndSource, VBOXDNDACTION dndAction); + +public: + + Display *pDisp; + /** Proxy window handle. */ + Window hWnd; + int iX; + int iY; + int iWidth; + int iHeight; +}; + +/** This class only serve to avoid dragging in generic new() and delete(). */ +class WrappedXEvent +{ +public: + XEvent m_Event; + +public: +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#endif + WrappedXEvent(const XEvent &a_rSrcEvent) + { + m_Event = a_rSrcEvent; + } + + WrappedXEvent() + { + RT_ZERO(m_Event); + } + + WrappedXEvent &operator=(const XEvent &a_rSrcEvent) + { + m_Event = a_rSrcEvent; + return *this; + } +}; + +/** + * Class for handling a single drag and drop operation, that is, + * one source and one target at a time. + * + * For now only one DragInstance will exits when the app is running. + */ +class DragInstance +{ +public: + + enum State + { + Uninitialized = 0, + Initialized, + Dragging, + Dropped, + State_32BIT_Hack = 0x7fffffff + }; + + enum Mode + { + Unknown = 0, + HG, + GH, + Mode_32Bit_Hack = 0x7fffffff + }; + +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#endif + DragInstance(Display *pDisplay, DragAndDropService *pParent); + +public: + + int init(uint32_t uScreenID); + int term(void); + void stop(void); + void reset(void); + + /* X11 message processing. */ + int onX11ClientMessage(const XEvent &e); + int onX11MotionNotify(const XEvent &e); + int onX11SelectionClear(const XEvent &e); + int onX11SelectionNotify(const XEvent &e); + int onX11SelectionRequest(const XEvent &evReq); + int onX11Event(const XEvent &e); + int waitForStatusChange(uint32_t enmState, RTMSINTERVAL uTimeoutMS = 30000); + bool waitForX11Msg(XEvent &evX, int iType, RTMSINTERVAL uTimeoutMS = 100); + bool waitForX11ClientMsg(XClientMessageEvent &evMsg, Atom aType, RTMSINTERVAL uTimeoutMS = 100); + + /* Session handling. */ + int checkForSessionChange(void); + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + /* Guest -> Host handling. */ + int ghIsDnDPending(void); + int ghDropped(const RTCString &strFormat, VBOXDNDACTION dndActionRequested); +#endif + + /* Host -> Guest handling. */ + int hgEnter(const RTCList<RTCString> &formats, VBOXDNDACTIONLIST dndListActionsAllowed); + int hgLeave(void); + int hgMove(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault); + int hgDrop(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault); + int hgDataReceive(PVBGLR3GUESTDNDMETADATA pMeta); + + /* X11 helpers. */ + int mouseCursorFakeMove(void); + int mouseCursorMove(int iPosX, int iPosY); + void mouseButtonSet(Window wndDest, int rx, int ry, int iButton, bool fPress); + int proxyWinShow(int *piRootX = NULL, int *piRootY = NULL) const; + int proxyWinHide(void); + + /* X11 window helpers. */ + char *wndX11GetNameA(Window wndThis) const; + + /* Xdnd protocol helpers. */ + void wndXDnDClearActionList(Window wndThis) const; + void wndXDnDClearFormatList(Window wndThis) const; + int wndXDnDGetActionList(Window wndThis, VBoxDnDAtomList &lstActions) const; + int wndXDnDGetFormatList(Window wndThis, VBoxDnDAtomList &lstTypes) const; + int wndXDnDSetActionList(Window wndThis, const VBoxDnDAtomList &lstActions) const; + int wndXDnDSetFormatList(Window wndThis, Atom atmProp, const VBoxDnDAtomList &lstFormats) const; + + /* Atom / HGCM formatting helpers. */ + int appendFormatsToList(const RTCList<RTCString> &lstFormats, VBoxDnDAtomList &lstAtoms) const; + int appendDataToList(const void *pvData, uint32_t cbData, VBoxDnDAtomList &lstAtoms) const; + static Atom toAtomAction(VBOXDNDACTION dndAction); + static int toAtomActions(VBOXDNDACTIONLIST dndActionList, VBoxDnDAtomList &lstAtoms); + static uint32_t toHGCMAction(Atom atom); + static uint32_t toHGCMActions(const VBoxDnDAtomList &actionsList); + +protected: + + /** The instance's own DnD context. */ + VBGLR3GUESTDNDCMDCTX m_dndCtx; + /** Pointer to service instance. */ + DragAndDropService *m_pParent; + /** Pointer to X display operating on. */ + Display *m_pDisplay; + /** X screen ID to operate on. */ + int m_screenID; + /** Pointer to X screen operating on. */ + Screen *m_pScreen; + /** Root window handle. */ + Window m_wndRoot; + /** Proxy window. */ + VBoxDnDProxyWnd m_wndProxy; + /** Current source/target window handle. */ + Window m_wndCur; + /** The XDnD protocol version the current source/target window is using. + * Set to 0 if not available / not set yet. */ + uint8_t m_uXdndVer; + /** Last mouse X position (in pixels, absolute to root window). + * Set to -1 if not set yet. */ + int m_lastMouseX; + /** Last mouse Y position (in pixels, absolute to root window). + * Set to -1 if not set yet. */ + int m_lastMouseY; + /** List of default (Atom) formats required for X11 Xdnd handling. + * This list will be included by \a m_lstAtomFormats. */ + VBoxDnDAtomList m_lstAtomFormatsX11; + /** List of (Atom) formats the current source/target window supports. */ + VBoxDnDAtomList m_lstAtomFormats; + /** List of (Atom) actions the current source/target window supports. */ + VBoxDnDAtomList m_lstAtomActions; + /** Buffer for answering the target window's selection request. */ + void *m_pvSelReqData; + /** Size (in bytes) of selection request data buffer. */ + uint32_t m_cbSelReqData; + /** Current operation mode. */ + volatile uint32_t m_enmMode; + /** Current state of operation mode. */ + volatile uint32_t m_enmState; + /** The instance's own X event queue. */ + RTCMTList<WrappedXEvent> m_eventQueueList; + /** Critical section for providing serialized access to list event queue's contents. */ + RTCRITSECT m_eventQueueCS; + /** Event for notifying this instance in case of a new event. */ + RTSEMEVENT m_eventQueueEvent; + /** Critical section for data access. */ + RTCRITSECT m_dataCS; + /** List of allowed formats. */ + RTCList<RTCString> m_lstAllowedFormats; + /** Number of failed attempts by the host + * to query for an active drag and drop operation on the guest. */ + uint16_t m_cFailedPendingAttempts; +}; + +/** + * Service class which implements drag'n drop. + */ +class DragAndDropService +{ +public: + DragAndDropService(void) + : m_pDisplay(NULL) + , m_hHGCMThread(NIL_RTTHREAD) + , m_hX11Thread(NIL_RTTHREAD) + , m_hEventSem(NIL_RTSEMEVENT) + , m_pCurDnD(NULL) + , m_fStop(false) + { + RT_ZERO(m_dndCtx); + } + + int init(void); + int worker(bool volatile *pfShutdown); + void reset(void); + void stop(void); + int term(void); + +private: + + static DECLCALLBACK(int) hgcmEventThread(RTTHREAD hThread, void *pvUser); + static DECLCALLBACK(int) x11EventThread(RTTHREAD hThread, void *pvUser); + + /* Private member vars */ + Display *m_pDisplay; + /** Our (thread-safe) event queue with mixed events (DnD HGCM / X11). */ + RTCMTList<DNDEVENT> m_eventQueue; + /** Critical section for providing serialized access to list + * event queue's contents. */ + RTCRITSECT m_eventQueueCS; + /** Thread handle for the HGCM message pumping thread. */ + RTTHREAD m_hHGCMThread; + /** Thread handle for the X11 message pumping thread. */ + RTTHREAD m_hX11Thread; + /** This service' DnD command context. */ + VBGLR3GUESTDNDCMDCTX m_dndCtx; + /** Event semaphore for new DnD events. */ + RTSEMEVENT m_hEventSem; + /** Pointer to the allocated DnD instance. + Currently we only support and handle one instance at a time. */ + DragInstance *m_pCurDnD; + /** Stop indicator flag to signal the thread that it should shut down. */ + bool m_fStop; + + friend class DragInstance; +} g_Svc; + +/********************************************************************************************************************************* + * DragInstanc implementation. * + ********************************************************************************************************************************/ + +DragInstance::DragInstance(Display *pDisplay, DragAndDropService *pParent) + : m_pParent(pParent) + , m_pDisplay(pDisplay) + , m_pScreen(0) + , m_wndRoot(0) + , m_wndCur(0) + , m_uXdndVer(0) + , m_pvSelReqData(NULL) + , m_cbSelReqData(0) + , m_enmMode(Unknown) + , m_enmState(Uninitialized) +{ + /* Append default targets we support. + * Note: The order is sorted by preference; be careful when changing this. */ + m_lstAtomFormatsX11.append(xAtom(XA_TARGETS)); + m_lstAtomFormatsX11.append(xAtom(XA_MULTIPLE)); + /** @todo Support INC (incremental transfers). */ +} + +/** + * Stops this drag instance. + */ +void DragInstance::stop(void) +{ + LogFlowFuncEnter(); + + int rc2 = VbglR3DnDDisconnect(&m_dndCtx); + AssertRC(rc2); + + LogFlowFuncLeave(); +} + +/** + * Terminates (destroys) this drag instance. + * + * @return VBox status code. + */ +int DragInstance::term(void) +{ + LogFlowFuncEnter(); + + if (m_wndProxy.hWnd != 0) + XDestroyWindow(m_pDisplay, m_wndProxy.hWnd); + + int rc = VbglR3DnDDisconnect(&m_dndCtx); + AssertRCReturn(rc, rc); + + if (m_pvSelReqData) + RTMemFree(m_pvSelReqData); + + rc = RTSemEventDestroy(m_eventQueueEvent); + AssertRCReturn(rc, rc); + + rc = RTCritSectDelete(&m_eventQueueCS); + AssertRCReturn(rc, rc); + + rc = RTCritSectDelete(&m_dataCS); + AssertRCReturn(rc, rc); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Resets this drag instance. + */ +void DragInstance::reset(void) +{ + LogFlowFuncEnter(); + + /* Hide the proxy win. */ + proxyWinHide(); + + int rc2 = RTCritSectEnter(&m_dataCS); + if (RT_SUCCESS(rc2)) + { + /* If we are currently the Xdnd selection owner, clear that. */ + Window pWnd = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection)); + if (pWnd == m_wndProxy.hWnd) + XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), None, CurrentTime); + + /* Clear any other DnD specific data on the proxy window. */ + wndXDnDClearFormatList(m_wndProxy.hWnd); + wndXDnDClearActionList(m_wndProxy.hWnd); + + m_lstAtomActions.clear(); + + /* First, clear the formats list and apply the X11-specific default formats, + * required for making Xdnd to work. */ + m_lstAtomFormats.clear(); + m_lstAtomFormats.append(m_lstAtomFormatsX11); + + m_wndCur = 0; + m_uXdndVer = 0; + m_lastMouseX = -1; + m_lastMouseY = -1; + m_enmState = Initialized; + m_enmMode = Unknown; + m_cFailedPendingAttempts = 0; + + /* Reset the selection request buffer. */ + if (m_pvSelReqData) + { + RTMemFree(m_pvSelReqData); + m_pvSelReqData = NULL; + + Assert(m_cbSelReqData); + m_cbSelReqData = 0; + } + + rc2 = RTCritSectEnter(&m_eventQueueCS); + if (RT_SUCCESS(rc2)) + { + m_eventQueueList.clear(); + + rc2 = RTCritSectLeave(&m_eventQueueCS); + AssertRC(rc2); + } + + RTCritSectLeave(&m_dataCS); + } + + LogFlowFuncLeave(); +} + +/** + * Initializes this drag instance. + * + * @return IPRT status code. + * @param uScreenID X' screen ID to use. + */ +int DragInstance::init(uint32_t uScreenID) +{ + int rc = VbglR3DnDConnect(&m_dndCtx); + /* Note: Can return VINF_PERMISSION_DENIED if HGCM host service is not available. */ + if (rc != VINF_SUCCESS) + return rc; + + if (g_cVerbosity) + { + RTCString strBody = RTCStringFmt("Connected (screen %RU32, verbosity %u)", uScreenID, g_cVerbosity); + VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, strBody.c_str()); + } + + do + { + rc = RTSemEventCreate(&m_eventQueueEvent); + if (RT_FAILURE(rc)) + break; + + rc = RTCritSectInit(&m_eventQueueCS); + if (RT_FAILURE(rc)) + break; + + rc = RTCritSectInit(&m_dataCS); + if (RT_FAILURE(rc)) + break; + + /* + * Enough screens configured in the x11 server? + */ + if ((int)uScreenID > ScreenCount(m_pDisplay)) + { + rc = VERR_INVALID_PARAMETER; + break; + } +#if 0 + /* Get the screen number from the x11 server. */ + pDrag->screen = ScreenOfDisplay(m_pDisplay, uScreenID); + if (!pDrag->screen) + { + rc = VERR_GENERAL_FAILURE; + break; + } +#endif + m_screenID = uScreenID; + + /* Now query the corresponding root window of this screen. */ + m_wndRoot = RootWindow(m_pDisplay, m_screenID); + if (!m_wndRoot) + { + rc = VERR_GENERAL_FAILURE; + break; + } + + /* + * Create an invisible window which will act as proxy for the DnD + * operation. This window will be used for both the GH and HG + * direction. + */ + XSetWindowAttributes attr; + RT_ZERO(attr); + attr.event_mask = EnterWindowMask | LeaveWindowMask + | ButtonMotionMask | ButtonPressMask | ButtonReleaseMask; + attr.override_redirect = True; + attr.do_not_propagate_mask = NoEventMask; + + if (g_cVerbosity >= 3) + { + attr.background_pixel = XWhitePixel(m_pDisplay, m_screenID); + attr.border_pixel = XBlackPixel(m_pDisplay, m_screenID); + m_wndProxy.hWnd = XCreateWindow(m_pDisplay, m_wndRoot /* Parent */, + 100, 100, /* Position */ + 100, 100, /* Width + height */ + 2, /* Border width */ + CopyFromParent, /* Depth */ + InputOutput, /* Class */ + CopyFromParent, /* Visual */ + CWBackPixel + | CWBorderPixel + | CWOverrideRedirect + | CWDontPropagate, /* Value mask */ + &attr); /* Attributes for value mask */ + } + + m_wndProxy.hWnd = XCreateWindow(m_pDisplay, m_wndRoot /* Parent */, + 0, 0, /* Position */ + 1, 1, /* Width + height */ + 0, /* Border width */ + CopyFromParent, /* Depth */ + InputOnly, /* Class */ + CopyFromParent, /* Visual */ + CWOverrideRedirect | CWDontPropagate, /* Value mask */ + &attr); /* Attributes for value mask */ + + if (!m_wndProxy.hWnd) + { + VBClLogError("Error creating proxy window\n"); + rc = VERR_GENERAL_FAILURE; + break; + } + + rc = m_wndProxy.init(m_pDisplay); + if (RT_FAILURE(rc)) + { + VBClLogError("Error initializing proxy window, rc=%Rrc\n", rc); + break; + } + + if (g_cVerbosity >= 3) /* Make debug window visible. */ + { + XFlush(m_pDisplay); + XMapWindow(m_pDisplay, m_wndProxy.hWnd); + XRaiseWindow(m_pDisplay, m_wndProxy.hWnd); + XFlush(m_pDisplay); + } + + VBClLogInfo("Proxy window=%#x (debug mode: %RTbool), root window=%#x ...\n", + m_wndProxy.hWnd, RT_BOOL(g_cVerbosity >= 3), m_wndRoot); + + /* Set the window's name for easier lookup. */ + XStoreName(m_pDisplay, m_wndProxy.hWnd, "VBoxClientWndDnD"); + + /* Make the new window Xdnd aware. */ + Atom atmVer = VBOX_XDND_VERSION; + XChangeProperty(m_pDisplay, m_wndProxy.hWnd, xAtom(XA_XdndAware), XA_ATOM, 32, PropModeReplace, + reinterpret_cast<unsigned char*>(&atmVer), 1); + } while (0); + + if (RT_SUCCESS(rc)) + { + reset(); + } + else + VBClLogError("Initializing drag instance for screen %RU32 failed with rc=%Rrc\n", uScreenID, rc); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Callback handler for a generic client message from a window. + * + * @return IPRT status code. + * @param e X11 event to handle. + */ +int DragInstance::onX11ClientMessage(const XEvent &e) +{ + AssertReturn(e.type == ClientMessage, VERR_INVALID_PARAMETER); + + LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState)); + LogFlowThisFunc(("Event wnd=%#x, msg=%s\n", e.xclient.window, xAtomToString(e.xclient.message_type).c_str())); + + int rc = VINF_SUCCESS; + + char *pszWndCurName = wndX11GetNameA(m_wndCur); + AssertPtrReturn(pszWndCurName, VERR_NO_MEMORY); + + switch (m_enmMode) + { + case HG: + { + /* + * Client messages are used to inform us about the status of a XdndAware + * window, in response of some events we send to them. + */ + + /* The target window informs us of the current Xdnd status. */ + if (e.xclient.message_type == xAtom(XA_XdndStatus)) + { + Window wndTgt = static_cast<Window>(e.xclient.data.l[XdndStatusWindow]); + + char *pszWndTgtName = wndX11GetNameA(wndTgt); + AssertPtrBreakStmt(pszWndTgtName, VERR_NO_MEMORY); + + /* Does the target accept the drop? */ + bool const fAcceptDrop = RT_BOOL(e.xclient.data.l[XdndStatusFlags] & VBOX_XDND_STATUS_FLAG_ACCEPT); + /* Does the target want XdndPosition messages? */ + bool const fWantsPosition = RT_BOOL(e.xclient.data.l[XdndStatusFlags] & VBOX_XDND_STATUS_FLAG_WANTS_POS); + + /* + * The XdndStatus message tell us if the window will accept the DnD + * event and with which action. We immediately send this info down to + * the host as a response of a previous DnD message. + */ + RTCString strActions = xAtomToString(e.xclient.data.l[XdndStatusAction]); + + VBClLogInfo("Target window %#x ('%s')\n", wndTgt, pszWndTgtName); + VBClLogInfo(" - %s accept data (actions '%s')\n", fAcceptDrop ? "does" : "does not", strActions.c_str()); + VBClLogInfo(" - %s want position messages\n", fWantsPosition ? "does" : "does not"); + + uint16_t const x = RT_HI_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgXY]); + uint16_t const y = RT_LO_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgXY]); + uint16_t const cx = RT_HI_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgWH]); + uint16_t const cy = RT_LO_U16((uint32_t)e.xclient.data.l[XdndStatusNoMsgWH]); + + if (cx && cy) + { + VBClLogInfo("Target window %#x ('%s') reported dead area at %RU16,%RU16 (%RU16 x %RU16)\n", + wndTgt, pszWndTgtName, x, y, cx, cy); + /** @todo Save dead area and don't send XdndPosition messages anymore into it. */ + } + + if (m_wndCur == wndTgt) + { + VBOXDNDACTION dndAction = VBOX_DND_ACTION_IGNORE; /* Default is ignoring. */ + /** @todo Compare this with the allowed actions. */ + if (fAcceptDrop) + dndAction = toHGCMAction(static_cast<Atom>(e.xclient.data.l[XdndStatusAction])); + + rc = VbglR3DnDHGSendAckOp(&m_dndCtx, dndAction); + } + else + VBClLogInfo("Target window %#x ('%s') is not our current window, skipping\n", wndTgt, pszWndTgtName); + + RTStrFree(pszWndTgtName); + } + /* The target window informs us that it finished the Xdnd operation and that we may free all data. */ + else if (e.xclient.message_type == xAtom(XA_XdndFinished)) + { + Window wndTarget = static_cast<Window>(e.xclient.data.l[XdndFinishedWindow]); + + char *pszWndTgtName = wndX11GetNameA(wndTarget); + AssertPtrBreakStmt(pszWndTgtName, VERR_NO_MEMORY); + + if (m_uXdndVer >= 5) + { + const bool fSucceeded = e.xclient.data.l[XdndFinishedFlags] & VBOX_XDND_FINISHED_FLAG_SUCCEEDED; + #if 0 /** @todo Returns garbage -- investigate this! */ + //const char *pcszAction = fSucceeded ? xAtomToString(e.xclient.data.l[XdndFinishedAction]).c_str() : NULL; + #endif + VBClLogInfo("Target window %#x ('%s') has %s the data\n", + wndTarget, pszWndTgtName, fSucceeded ? "accepted" : "rejected"); + } + else /* Xdnd < version 5 did not have the XdndFinishedFlags / XdndFinishedAction properties. */ + VBClLogInfo("Target window %#x ('%s') has accepted the data\n", wndTarget, pszWndTgtName); + + RTStrFree(pszWndTgtName); + + reset(); + } + else + { + LogFlowThisFunc(("Unhandled client message '%s'\n", xAtomToString(e.xclient.message_type).c_str())); + rc = VERR_NOT_SUPPORTED; + } + + break; + } + + case Unknown: /* Mode not set (yet). */ + RT_FALL_THROUGH(); + case GH: + { + /* + * This message marks the beginning of a new drag and drop + * operation on the guest. + */ + if (e.xclient.message_type == xAtom(XA_XdndEnter)) + { + /* + * Get the window which currently has the XA_XdndSelection + * bit set. + */ + Window wndSel = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection)); + char *pszWndSelName = wndX11GetNameA(wndSel); + AssertPtrBreakStmt(pszWndSelName, VERR_NO_MEMORY); + + mouseButtonSet(m_wndProxy.hWnd, -1, -1, 1, true /* fPress */); + + /* + * Update our state and the window handle to process. + */ + rc = RTCritSectEnter(&m_dataCS); + if (RT_SUCCESS(rc)) + { + uint8_t const uXdndVer = (uint8_t)e.xclient.data.l[XdndEnterFlags] >> XdndEnterVersionRShift; + + VBClLogInfo("Entered new source window %#x ('%s'), supports Xdnd version %u\n", wndSel, pszWndSelName, uXdndVer); +#ifdef DEBUG + XWindowAttributes xwa; + XGetWindowAttributes(m_pDisplay, m_wndCur, &xwa); + LogFlowThisFunc(("wndCur=%#x, x=%d, y=%d, width=%d, height=%d\n", m_wndCur, xwa.x, xwa.y, xwa.width, xwa.height)); +#endif + /* + * Retrieve supported formats. + */ + + /* Check if the MIME types are in the message itself or if we need + * to fetch the XdndTypeList property from the window. */ + bool fMoreTypes = e.xclient.data.l[XdndEnterFlags] & XdndEnterMoreTypesFlag; + if (!fMoreTypes) + { + /* Only up to 3 format types supported. */ + /* Start with index 2 (first item). */ + for (int i = 2; i < 5; i++) + { + LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(e.xclient.data.l[i]).c_str())); + m_lstAtomFormats.append(e.xclient.data.l[i]); + } + } + else + { + /* More than 3 format types supported. */ + rc = wndXDnDGetFormatList(wndSel, m_lstAtomFormats); + } + + if (RT_FAILURE(rc)) + { + VBClLogError("Error retrieving supported formats, rc=%Rrc\n", rc); + break; + } + + /* + * Retrieve supported actions. + */ + if (uXdndVer >= 2) /* More than one action allowed since protocol version 2. */ + { + rc = wndXDnDGetActionList(wndSel, m_lstAtomActions); + } + else /* Only "copy" action allowed on legacy applications. */ + m_lstAtomActions.append(XA_XdndActionCopy); + + if (RT_FAILURE(rc)) + { + VBClLogError("Error retrieving supported actions, rc=%Rrc\n", rc); + break; + } + + VBClLogInfo("Source window %#x ('%s')\n", wndSel, pszWndSelName); + VBClLogInfo(" - supports the formats "); + for (size_t i = 0; i < m_lstAtomFormats.size(); i++) + { + if (i > 0) + VBClLogInfo(", "); + VBClLogInfo("%s", gX11->xAtomToString(m_lstAtomFormats[i]).c_str()); + } + VBClLogInfo("\n"); + VBClLogInfo(" - supports the actions "); + for (size_t i = 0; i < m_lstAtomActions.size(); i++) + { + if (i > 0) + VBClLogInfo(", "); + VBClLogInfo("%s", gX11->xAtomToString(m_lstAtomActions[i]).c_str()); + } + VBClLogInfo("\n"); + + AssertBreakStmt(wndSel == (Window)e.xclient.data.l[XdndEnterWindow], + rc = VERR_INVALID_PARAMETER); /* Source window. */ + + m_wndCur = wndSel; + m_uXdndVer = uXdndVer; + m_enmMode = GH; + m_enmState = Dragging; + + RTCritSectLeave(&m_dataCS); + } + + RTStrFree(pszWndSelName); + } + else if ( e.xclient.message_type == xAtom(XA_XdndPosition) + && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndPositionWindow])) + { + if (m_enmState != Dragging) /* Wrong mode? Bail out. */ + { + reset(); + break; + } +#ifdef LOG_ENABLED + int32_t iPos = e.xclient.data.l[XdndPositionXY]; + Atom atmAction = m_uXdndVer >= 2 /* Actions other than "copy" or only supported since protocol version 2. */ + ? e.xclient.data.l[XdndPositionAction] : xAtom(XA_XdndActionCopy); + LogFlowThisFunc(("XA_XdndPosition: wndProxy=%#x, wndCur=%#x, x=%RI32, y=%RI32, strAction=%s\n", + m_wndProxy.hWnd, m_wndCur, RT_HIWORD(iPos), RT_LOWORD(iPos), + xAtomToString(atmAction).c_str())); +#endif + bool fAcceptDrop = true; + + /* Reply with a XdndStatus message to tell the source whether + * the data can be dropped or not. */ + XClientMessageEvent m; + RT_ZERO(m); + m.type = ClientMessage; + m.display = m_pDisplay; + m.window = e.xclient.data.l[XdndPositionWindow]; + m.message_type = xAtom(XA_XdndStatus); + m.format = 32; + m.data.l[XdndStatusWindow] = m_wndProxy.hWnd; + m.data.l[XdndStatusFlags] = fAcceptDrop ? VBOX_XDND_STATUS_FLAG_ACCEPT : VBOX_XDND_STATUS_FLAG_NONE; /* Whether to accept the drop or not. */ + + /* We don't want any new XA_XdndPosition messages while being + * in our proxy window. */ + m.data.l[XdndStatusNoMsgXY] = RT_MAKE_U32(m_wndProxy.iY, m_wndProxy.iX); + m.data.l[XdndStatusNoMsgWH] = RT_MAKE_U32(m_wndProxy.iHeight, m_wndProxy.iWidth); + + /** @todo Handle default action! */ + m.data.l[XdndStatusAction] = fAcceptDrop ? toAtomAction(VBOX_DND_ACTION_COPY) : None; + + int xRc = XSendEvent(m_pDisplay, e.xclient.data.l[XdndPositionWindow], + False /* Propagate */, NoEventMask, reinterpret_cast<XEvent *>(&m)); + if (xRc == 0) + VBClLogError("Error sending position status event to current window %#x ('%s'): %s\n", + m_wndCur, pszWndCurName, gX11->xErrorToString(xRc).c_str()); + } + else if ( e.xclient.message_type == xAtom(XA_XdndLeave) + && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndLeaveWindow])) + { + LogFlowThisFunc(("XA_XdndLeave\n")); + VBClLogInfo("Guest to host transfer canceled by the guest source window\n"); + + /* Start over. */ + reset(); + } + else if ( e.xclient.message_type == xAtom(XA_XdndDrop) + && m_wndCur == static_cast<Window>(e.xclient.data.l[XdndDropWindow])) + { + LogFlowThisFunc(("XA_XdndDrop\n")); + + if (m_enmState != Dropped) /* Wrong mode? Bail out. */ + { + /* Can occur when dragging from guest->host, but then back in to the guest again. */ + VBClLogInfo("Could not drop on own proxy window\n"); /* Not fatal. */ + + /* Let the source know. */ + rc = m_wndProxy.sendFinished(m_wndCur, VBOX_DND_ACTION_IGNORE); + + /* Start over. */ + reset(); + break; + } + + m_eventQueueList.append(e); + rc = RTSemEventSignal(m_eventQueueEvent); + } + else /* Unhandled event, abort. */ + { + VBClLogInfo("Unhandled event from wnd=%#x, msg=%s\n", e.xclient.window, xAtomToString(e.xclient.message_type).c_str()); + + /* Let the source know. */ + rc = m_wndProxy.sendFinished(m_wndCur, VBOX_DND_ACTION_IGNORE); + + /* Start over. */ + reset(); + } + break; + } + + default: + { + AssertMsgFailed(("Drag and drop mode not implemented: %RU32\n", m_enmMode)); + rc = VERR_NOT_IMPLEMENTED; + break; + } + } + + RTStrFree(pszWndCurName); + + LogFlowThisFunc(("Returning rc=%Rrc\n", rc)); + return rc; +} + +int DragInstance::onX11MotionNotify(const XEvent &e) +{ + RT_NOREF1(e); + LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState)); + + return VINF_SUCCESS; +} + +/** + * Callback handler for being notified if some other window now + * is the owner of the current selection. + * + * @return IPRT status code. + * @param e X11 event to handle. + * + * @remark + */ +int DragInstance::onX11SelectionClear(const XEvent &e) +{ + RT_NOREF1(e); + LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState)); + + return VINF_SUCCESS; +} + +/** + * Callback handler for a XDnD selection notify from a window. This is needed + * to let the us know if a certain window has drag'n drop data to share with us, + * e.g. our proxy window. + * + * @return IPRT status code. + * @param e X11 event to handle. + */ +int DragInstance::onX11SelectionNotify(const XEvent &e) +{ + AssertReturn(e.type == SelectionNotify, VERR_INVALID_PARAMETER); + + LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState)); + + int rc; + + switch (m_enmMode) + { + case GH: + { + if (m_enmState == Dropped) + { + m_eventQueueList.append(e); + rc = RTSemEventSignal(m_eventQueueEvent); + } + else + rc = VERR_WRONG_ORDER; + break; + } + + default: + { + LogFlowThisFunc(("Unhandled: wnd=%#x, msg=%s\n", + e.xclient.data.l[0], xAtomToString(e.xclient.message_type).c_str())); + rc = VERR_INVALID_STATE; + break; + } + } + + LogFlowThisFunc(("Returning rc=%Rrc\n", rc)); + return rc; +} + +/** + * Callback handler for a XDnD selection request from a window. This is needed + * to retrieve the data required to complete the actual drag'n drop operation. + * + * @returns IPRT status code. + * @param evReq X11 event to handle. + */ +int DragInstance::onX11SelectionRequest(const XEvent &evReq) +{ + AssertReturn(evReq.type == SelectionRequest, VERR_INVALID_PARAMETER); + + const XSelectionRequestEvent *pEvReq = &evReq.xselectionrequest; + + char *pszWndSrcName = wndX11GetNameA(pEvReq->owner); + AssertPtrReturn(pszWndSrcName, VERR_INVALID_POINTER); + char *pszWndTgtName = wndX11GetNameA(pEvReq->requestor); + AssertPtrReturn(pszWndTgtName, VERR_INVALID_POINTER); + + LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState)); + LogFlowThisFunc(("Event owner=%#x ('%s'), requestor=%#x ('%s'), selection=%s, target=%s, prop=%s, time=%u\n", + pEvReq->owner, pszWndSrcName, + pEvReq->requestor, pszWndTgtName, + xAtomToString(pEvReq->selection).c_str(), + xAtomToString(pEvReq->target).c_str(), + xAtomToString(pEvReq->property).c_str(), + pEvReq->time)); + + VBClLogInfo("Window '%s' is asking '%s' for '%s' / '%s'\n", + pszWndTgtName, pszWndSrcName, xAtomToString(pEvReq->selection).c_str(), xAtomToString(pEvReq->property).c_str()); + + RTStrFree(pszWndSrcName); + /* Note: pszWndTgtName will be free'd below. */ + + int rc; + + switch (m_enmMode) + { + case HG: + { + rc = VINF_SUCCESS; + + /* + * Start by creating a refusal selection notify message. + * That way we only need to care for the success case. + */ + + XEvent evResp; + RT_ZERO(evResp); + + XSelectionEvent *pEvResp = &evResp.xselection; + + pEvResp->type = SelectionNotify; + pEvResp->display = pEvReq->display; + pEvResp->requestor = pEvReq->requestor; + pEvResp->selection = pEvReq->selection; + pEvResp->target = pEvReq->target; + pEvResp->property = None; /* "None" means refusal. */ + pEvResp->time = pEvReq->time; + + if (g_cVerbosity) + { + VBClLogVerbose(1, "Supported formats by VBoxClient:\n"); + for (size_t i = 0; i < m_lstAtomFormats.size(); i++) + VBClLogVerbose(1, "\t%s\n", xAtomToString(m_lstAtomFormats.at(i)).c_str()); + } + + /* Is the requestor asking for the possible MIME types? */ + if (pEvReq->target == xAtom(XA_TARGETS)) + { + VBClLogInfo("Target window %#x ('%s') asking for target list\n", pEvReq->requestor, pszWndTgtName); + + /* If so, set the window property with the formats on the requestor + * window. */ + rc = wndXDnDSetFormatList(pEvReq->requestor, pEvReq->property, m_lstAtomFormats); + if (RT_SUCCESS(rc)) + pEvResp->property = pEvReq->property; + } + /* Is the requestor asking for a specific MIME type (we support)? */ + else if (m_lstAtomFormats.contains(pEvReq->target)) + { + VBClLogInfo("Target window %#x ('%s') is asking for data as '%s'\n", + pEvReq->requestor, pszWndTgtName, xAtomToString(pEvReq->target).c_str()); + +#ifdef VBOX_WITH_DRAG_AND_DROP_PROMISES +# error "Implement me!" +#else + /* Did we not drop our stuff to the guest yet? Bail out. */ + if (m_enmState != Dropped) + { + VBClLogError("Data not dropped by the host on the guest yet (client state %RU32, mode %RU32), refusing selection request by guest\n", + m_enmState, m_enmMode); + } + /* Did we not store the requestor's initial selection request yet? Then do so now. */ + else + { +#endif /* VBOX_WITH_DRAG_AND_DROP_PROMISES */ + /* Get the data format the requestor wants from us. */ + VBClLogInfo("Target window %#x ('%s') requested data from host as '%s', rc=%Rrc\n", + pEvReq->requestor, pszWndTgtName, xAtomToString(pEvReq->target).c_str(), rc); + + /* Make a copy of the MIME data to be passed back. The X server will be become + * the new owner of that data, so no deletion needed. */ + /** @todo Do we need to do some more conversion here? XConvertSelection? */ + AssertMsgBreakStmt(m_pvSelReqData != NULL, ("Selection request data is NULL\n"), rc = VERR_INVALID_PARAMETER); + AssertMsgBreakStmt(m_cbSelReqData > 0, ("Selection request data size is 0\n"), rc = VERR_INVALID_PARAMETER); + + void const *pvData = RTMemDup(m_pvSelReqData, m_cbSelReqData); + AssertMsgBreakStmt(pvData != NULL, ("Duplicating selection request failed\n"), rc = VERR_NO_MEMORY); + uint32_t const cbData = m_cbSelReqData; + + /* Always return the requested property. */ + evResp.xselection.property = pEvReq->property; + + /* Note: Always seems to return BadRequest. Seems fine. */ + int xRc = XChangeProperty(pEvResp->display, pEvResp->requestor, pEvResp->property, + pEvResp->target, 8, PropModeReplace, + reinterpret_cast<const unsigned char*>(pvData), cbData); + + LogFlowFunc(("Changing property '%s' (of type '%s') of window %#x ('%s'): %s\n", + xAtomToString(pEvReq->property).c_str(), + xAtomToString(pEvReq->target).c_str(), + pEvReq->requestor, pszWndTgtName, + gX11->xErrorToString(xRc).c_str())); + RT_NOREF(xRc); +#ifndef VBOX_WITH_DRAG_AND_DROP_PROMISES + } +#endif + } + /* Anything else. */ + else + { + VBClLogError("Refusing unknown command/format '%s' of wnd=%#x ('%s')\n", + xAtomToString(pEvReq->target).c_str(), pEvReq->requestor, pszWndTgtName); + rc = VERR_NOT_SUPPORTED; + } + + VBClLogVerbose(1, "Offering type '%s', property '%s' to window %#x ('%s') ...\n", + xAtomToString(pEvReq->target).c_str(), + xAtomToString(pEvReq->property).c_str(), pEvReq->requestor, pszWndTgtName); + + int xRc = XSendEvent(pEvReq->display, pEvReq->requestor, True /* Propagate */, 0, &evResp); + if (xRc == 0) + VBClLogError("Error sending SelectionNotify(1) event to window %#x ('%s'): %s\n", + pEvReq->requestor, pszWndTgtName, gX11->xErrorToString(xRc).c_str()); + + XFlush(pEvReq->display); + break; + } + + default: + rc = VERR_INVALID_STATE; + break; + } + + RTStrFree(pszWndTgtName); + pszWndTgtName = NULL; + + LogFlowThisFunc(("Returning rc=%Rrc\n", rc)); + return rc; +} + +/** + * Handles X11 events, called by x11EventThread. + * + * @returns IPRT status code. + * @param e X11 event to handle. + */ +int DragInstance::onX11Event(const XEvent &e) +{ + int rc; + + LogFlowThisFunc(("X11 event, type=%d\n", e.type)); + switch (e.type) + { + /* + * This can happen if a guest->host drag operation + * goes back from the host to the guest. This is not what + * we want and thus resetting everything. + */ + case ButtonPress: + RT_FALL_THROUGH(); + case ButtonRelease: + { + VBClLogInfo("Mouse button %s\n", e.type == ButtonPress ? "pressed" : "released"); + + reset(); + + rc = VINF_SUCCESS; + break; + } + + case ClientMessage: + rc = onX11ClientMessage(e); + break; + + case SelectionClear: + rc = onX11SelectionClear(e); + break; + + case SelectionNotify: + rc = onX11SelectionNotify(e); + break; + + case SelectionRequest: + rc = onX11SelectionRequest(e); + break; + + case MotionNotify: + rc = onX11MotionNotify(e); + break; + + default: + rc = VERR_NOT_IMPLEMENTED; + break; + } + + LogFlowThisFunc(("rc=%Rrc\n", rc)); + return rc; +} + +int DragInstance::waitForStatusChange(uint32_t enmState, RTMSINTERVAL uTimeoutMS /* = 30000 */) +{ + const uint64_t uiStart = RTTimeMilliTS(); + volatile uint32_t enmCurState; + + int rc = VERR_TIMEOUT; + + LogFlowFunc(("enmState=%RU32, uTimeoutMS=%RU32\n", enmState, uTimeoutMS)); + + do + { + enmCurState = ASMAtomicReadU32(&m_enmState); + if (enmCurState == enmState) + { + rc = VINF_SUCCESS; + break; + } + } + while (RTTimeMilliTS() - uiStart < uTimeoutMS); + + LogFlowThisFunc(("Returning %Rrc\n", rc)); + return rc; +} + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH +/** + * Waits for an X11 event of a specific type. + * + * @returns IPRT status code. + * @param evX Reference where to store the event into. + * @param iType Event type to wait for. + * @param uTimeoutMS Timeout (in ms) to wait for the event. + */ +bool DragInstance::waitForX11Msg(XEvent &evX, int iType, RTMSINTERVAL uTimeoutMS /* = 100 */) +{ + LogFlowThisFunc(("iType=%d, uTimeoutMS=%RU32, cEventQueue=%zu\n", iType, uTimeoutMS, m_eventQueueList.size())); + + bool fFound = false; + uint64_t const tsStartMs = RTTimeMilliTS(); + + do + { + /* Check if there is a client message in the queue. */ + for (size_t i = 0; i < m_eventQueueList.size(); i++) + { + int rc2 = RTCritSectEnter(&m_eventQueueCS); + if (RT_SUCCESS(rc2)) + { + XEvent e = m_eventQueueList.at(i).m_Event; + + fFound = e.type == iType; + if (fFound) + { + m_eventQueueList.removeAt(i); + evX = e; + } + + rc2 = RTCritSectLeave(&m_eventQueueCS); + AssertRC(rc2); + + if (fFound) + break; + } + } + + if (fFound) + break; + + int rc2 = RTSemEventWait(m_eventQueueEvent, 25 /* ms */); + if ( RT_FAILURE(rc2) + && rc2 != VERR_TIMEOUT) + { + LogFlowFunc(("Waiting failed with rc=%Rrc\n", rc2)); + break; + } + } + while (RTTimeMilliTS() - tsStartMs < uTimeoutMS); + + LogFlowThisFunc(("Returning fFound=%RTbool, msRuntime=%RU64\n", fFound, RTTimeMilliTS() - tsStartMs)); + return fFound; +} + +/** + * Waits for an X11 client message of a specific type. + * + * @returns IPRT status code. + * @param evMsg Reference where to store the event into. + * @param aType Event type to wait for. + * @param uTimeoutMS Timeout (in ms) to wait for the event. + */ +bool DragInstance::waitForX11ClientMsg(XClientMessageEvent &evMsg, Atom aType, + RTMSINTERVAL uTimeoutMS /* = 100 */) +{ + LogFlowThisFunc(("aType=%s, uTimeoutMS=%RU32, cEventQueue=%zu\n", + xAtomToString(aType).c_str(), uTimeoutMS, m_eventQueueList.size())); + + bool fFound = false; + const uint64_t uiStart = RTTimeMilliTS(); + do + { + /* Check if there is a client message in the queue. */ + for (size_t i = 0; i < m_eventQueueList.size(); i++) + { + int rc2 = RTCritSectEnter(&m_eventQueueCS); + if (RT_SUCCESS(rc2)) + { + XEvent e = m_eventQueueList.at(i).m_Event; + if ( e.type == ClientMessage + && e.xclient.message_type == aType) + { + m_eventQueueList.removeAt(i); + evMsg = e.xclient; + + fFound = true; + } + + if (e.type == ClientMessage) + { + LogFlowThisFunc(("Client message: Type=%ld (%s)\n", + e.xclient.message_type, xAtomToString(e.xclient.message_type).c_str())); + } + else + LogFlowThisFunc(("X message: Type=%d\n", e.type)); + + rc2 = RTCritSectLeave(&m_eventQueueCS); + AssertRC(rc2); + + if (fFound) + break; + } + } + + if (fFound) + break; + + int rc2 = RTSemEventWait(m_eventQueueEvent, 25 /* ms */); + if ( RT_FAILURE(rc2) + && rc2 != VERR_TIMEOUT) + { + LogFlowFunc(("Waiting failed with rc=%Rrc\n", rc2)); + break; + } + } + while (RTTimeMilliTS() - uiStart < uTimeoutMS); + + LogFlowThisFunc(("Returning fFound=%RTbool, msRuntime=%RU64\n", fFound, RTTimeMilliTS() - uiStart)); + return fFound; +} +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + +/* + * Host -> Guest + */ + +/** + * Host -> Guest: Event signalling that the host's (mouse) cursor just entered the VM's (guest's) display + * area. + * + * @returns IPRT status code. + * @param lstFormats List of supported formats from the host. + * @param dndListActionsAllowed (ORed) List of supported actions from the host. + */ +int DragInstance::hgEnter(const RTCList<RTCString> &lstFormats, uint32_t dndListActionsAllowed) +{ + LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState)); + + if (m_enmMode != Unknown) + return VERR_INVALID_STATE; + + reset(); + +#ifdef DEBUG + LogFlowThisFunc(("dndListActionsAllowed=0x%x, lstFormats=%zu: ", dndListActionsAllowed, lstFormats.size())); + for (size_t i = 0; i < lstFormats.size(); ++i) + LogFlow(("'%s' ", lstFormats.at(i).c_str())); + LogFlow(("\n")); +#endif + + int rc; + + do + { + /* Check if the VM session has changed and reconnect to the HGCM service if necessary. */ + rc = checkForSessionChange(); + AssertRCBreak(rc); + + /* Append all actual (MIME) formats we support to the list. + * These must come last, after the default Atoms above. */ + rc = appendFormatsToList(lstFormats, m_lstAtomFormats); + AssertRCBreak(rc); + + rc = wndXDnDSetFormatList(m_wndProxy.hWnd, xAtom(XA_XdndTypeList), m_lstAtomFormats); + AssertRCBreak(rc); + + /* Announce the possible actions. */ + VBoxDnDAtomList lstActions; + rc = toAtomActions(dndListActionsAllowed, lstActions); + AssertRCBreak(rc); + + rc = wndXDnDSetActionList(m_wndProxy.hWnd, lstActions); + AssertRCBreak(rc); + + /* Set the DnD selection owner to our window. */ + /** @todo Don't use CurrentTime -- according to ICCCM section 2.1. */ + XSetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection), m_wndProxy.hWnd, CurrentTime); + + if (g_cVerbosity) + { + RTCString strMsg("Enter: Host -> Guest\n"); + strMsg += RTCStringFmt("Allowed actions: "); + for (size_t i = 0; i < lstActions.size(); i++) + { + if (i > 0) + strMsg += ", "; + strMsg += DnDActionToStr(toHGCMAction(lstActions.at(i))); + } + strMsg += " - Formats: "; + for (size_t i = 0; i < lstFormats.size(); i++) + { + if (i > 0) + strMsg += ", "; + strMsg += lstFormats.at(i); + } + + VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, strMsg.c_str()); + } + + m_enmMode = HG; + m_enmState = Dragging; + + } while (0); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest: Event signalling that the host's (mouse) cursor has left the VM's (guest's) + * display area. + */ +int DragInstance::hgLeave(void) +{ + if (g_cVerbosity) + VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, "Leave: Host -> Guest"); + + if (m_enmMode == HG) /* Only reset if in the right operation mode. */ + reset(); + + return VINF_SUCCESS; +} + +/** + * Host -> Guest: Event signalling that the host's (mouse) cursor has been moved within the VM's + * (guest's) display area. + * + * @returns IPRT status code. + * @param uPosX Relative X position within the guest's display area. + * @param uPosY Relative Y position within the guest's display area. + * @param dndActionDefault Default action the host wants to perform on the guest + * as soon as the operation successfully finishes. + */ +int DragInstance::hgMove(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault) +{ + LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState)); + LogFlowThisFunc(("uPosX=%RU32, uPosY=%RU32, dndActionDefault=0x%x\n", uPosX, uPosY, dndActionDefault)); + + if ( m_enmMode != HG + || m_enmState != Dragging) + { + return VERR_INVALID_STATE; + } + + int rc = VINF_SUCCESS; + int xRc = Success; + + /* Move the mouse cursor within the guest. */ + mouseCursorMove(uPosX, uPosY); + + /* Search for the application window below the cursor. */ + Window wndBelowCursor = gX11->applicationWindowBelowCursor(m_wndRoot); + char *pszWndBelowCursorName = wndX11GetNameA(wndBelowCursor); + AssertPtrReturn(pszWndBelowCursorName, VERR_NO_MEMORY); + + uint8_t uBelowCursorXdndVer = 0; /* 0 means the current window is _not_ XdndAware. */ + + if (wndBelowCursor != None) + { + /* Temp stuff for the XGetWindowProperty call. */ + Atom atmTmp; + int fmt; + unsigned long cItems, cbRemaining; + unsigned char *pcData = NULL; + + /* Query the XdndAware property from the window. We are interested in + * the version and if it is XdndAware at all. */ + xRc = XGetWindowProperty(m_pDisplay, wndBelowCursor, xAtom(XA_XdndAware), + 0, 2, False, AnyPropertyType, + &atmTmp, &fmt, &cItems, &cbRemaining, &pcData); + if (xRc != Success) + { + VBClLogError("Error getting properties of cursor window=%#x: %s\n", wndBelowCursor, gX11->xErrorToString(xRc).c_str()); + } + else + { + if (pcData == NULL || fmt != 32 || cItems != 1) + { + /** @todo Do we need to deal with this? */ + VBClLogError("Wrong window properties for window %#x: pcData=%#x, iFmt=%d, cItems=%ul\n", + wndBelowCursor, pcData, fmt, cItems); + } + else + { + /* Get the current window's Xdnd version. */ + uBelowCursorXdndVer = (uint8_t)reinterpret_cast<long *>(pcData)[0]; + } + + XFree(pcData); + } + } + + char *pszWndCurName = wndX11GetNameA(m_wndCur); + AssertPtrReturn(pszWndCurName, VERR_NO_MEMORY); + + LogFlowThisFunc(("wndCursor=%x ('%s', Xdnd version %u), wndCur=%x ('%s', Xdnd version %u)\n", + wndBelowCursor, pszWndBelowCursorName, uBelowCursorXdndVer, m_wndCur, pszWndCurName, m_uXdndVer)); + + if ( wndBelowCursor != m_wndCur + && m_uXdndVer) + { + VBClLogInfo("Left old window %#x ('%s'), supported Xdnd version %u\n", m_wndCur, pszWndCurName, m_uXdndVer); + + /* We left the current XdndAware window. Announce this to the current indow. */ + XClientMessageEvent m; + RT_ZERO(m); + m.type = ClientMessage; + m.display = m_pDisplay; + m.window = m_wndCur; + m.message_type = xAtom(XA_XdndLeave); + m.format = 32; + m.data.l[XdndLeaveWindow] = m_wndProxy.hWnd; + + xRc = XSendEvent(m_pDisplay, m_wndCur, False, NoEventMask, reinterpret_cast<XEvent*>(&m)); + if (xRc == 0) + VBClLogError("Error sending leave event to old window %#x: %s\n", m_wndCur, gX11->xErrorToString(xRc).c_str()); + + /* Reset our current window. */ + m_wndCur = 0; + m_uXdndVer = 0; + } + + /* + * Do we have a new Xdnd-aware window which now is under the cursor? + */ + if ( wndBelowCursor != m_wndCur + && uBelowCursorXdndVer) + { + VBClLogInfo("Entered new window %#x ('%s'), supports Xdnd version=%u\n", + wndBelowCursor, pszWndBelowCursorName, uBelowCursorXdndVer); + + /* + * We enter a new window. Announce the XdndEnter event to the new + * window. The first three mime types are attached to the event (the + * others could be requested by the XdndTypeList property from the + * window itself). + */ + XClientMessageEvent m; + RT_ZERO(m); + m.type = ClientMessage; + m.display = m_pDisplay; + m.window = wndBelowCursor; + m.message_type = xAtom(XA_XdndEnter); + m.format = 32; + m.data.l[XdndEnterWindow] = m_wndProxy.hWnd; + m.data.l[XdndEnterFlags] = RT_MAKE_U32_FROM_U8( + /* Bit 0 is set if the source supports more than three data types. */ + m_lstAtomFormats.size() > 3 ? RT_BIT(0) : 0, + /* Reserved for future use. */ + 0, 0, + /* Protocol version to use. */ + RT_MIN(VBOX_XDND_VERSION, uBelowCursorXdndVer)); + m.data.l[XdndEnterType1] = m_lstAtomFormats.value(0, None); /* First data type to use. */ + m.data.l[XdndEnterType2] = m_lstAtomFormats.value(1, None); /* Second data type to use. */ + m.data.l[XdndEnterType3] = m_lstAtomFormats.value(2, None); /* Third data type to use. */ + + xRc = XSendEvent(m_pDisplay, wndBelowCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m)); + if (xRc == 0) + VBClLogError("Error sending enter event to window %#x: %s\n", wndBelowCursor, gX11->xErrorToString(xRc).c_str()); + } + + if (uBelowCursorXdndVer) + { + Assert(wndBelowCursor != None); + + Atom atmAction = toAtomAction(dndActionDefault); + LogFlowThisFunc(("strAction=%s\n", xAtomToString(atmAction).c_str())); + + VBClLogInfo("Sent position event (%RU32 x %RU32) to window %#x ('%s') with actions '%s'\n", + uPosX, uPosY, wndBelowCursor, pszWndBelowCursorName, xAtomToString(atmAction).c_str()); + + /* + * Send a XdndPosition event with the proposed action to the guest. + */ + XClientMessageEvent m; + RT_ZERO(m); + m.type = ClientMessage; + m.display = m_pDisplay; + m.window = wndBelowCursor; + m.message_type = xAtom(XA_XdndPosition); + m.format = 32; + m.data.l[XdndPositionWindow] = m_wndProxy.hWnd; /* X window ID of source window. */ + m.data.l[XdndPositionFlags] = 0; /* Reserved, set to 0. */ + m.data.l[XdndPositionXY] = RT_MAKE_U32(uPosY, uPosX); /* Cursor coordinates relative to the root window. */ + m.data.l[XdndPositionTimeStamp] = CurrentTime; /* Timestamp for retrieving data. */ + m.data.l[XdndPositionAction] = atmAction; /* Actions requested by the user. */ + + xRc = XSendEvent(m_pDisplay, wndBelowCursor, False, NoEventMask, reinterpret_cast<XEvent*>(&m)); + if (xRc == 0) + VBClLogError("Error sending position event to current window %#x: %s\n", wndBelowCursor, gX11->xErrorToString(xRc).c_str()); + } + + if (uBelowCursorXdndVer == 0) + { + /* No window to process, so send a ignore ack event to the host. */ + rc = VbglR3DnDHGSendAckOp(&m_dndCtx, VBOX_DND_ACTION_IGNORE); + } + else + { + Assert(wndBelowCursor != None); + + m_wndCur = wndBelowCursor; + m_uXdndVer = uBelowCursorXdndVer; + } + + RTStrFree(pszWndBelowCursorName); + RTStrFree(pszWndCurName); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest: Event signalling that the host has dropped the data over the VM (guest) window. + * + * @returns IPRT status code. + * @param uPosX Relative X position within the guest's display area. + * @param uPosY Relative Y position within the guest's display area. + * @param dndActionDefault Default action the host wants to perform on the guest + * as soon as the operation successfully finishes. + */ +int DragInstance::hgDrop(uint32_t uPosX, uint32_t uPosY, VBOXDNDACTION dndActionDefault) +{ + RT_NOREF3(uPosX, uPosY, dndActionDefault); + LogFlowThisFunc(("wndCur=%RU32, wndProxy=%RU32, mode=%RU32, state=%RU32\n", m_wndCur, m_wndProxy.hWnd, m_enmMode, m_enmState)); + LogFlowThisFunc(("uPosX=%RU32, uPosY=%RU32, dndActionDefault=0x%x\n", uPosX, uPosY, dndActionDefault)); + + if ( m_enmMode != HG + || m_enmState != Dragging) + { + return VERR_INVALID_STATE; + } + + /* Set the state accordingly. */ + m_enmState = Dropped; + + /* + * Ask the host to send the raw data, as we don't (yet) know which format + * the guest exactly expects. As blocking in a SelectionRequest message turned + * out to be very unreliable (e.g. with KDE apps) we request to start transferring + * file/directory data (if any) here. + */ + char szFormat[] = { "text/uri-list" }; + + int rc = VbglR3DnDHGSendReqData(&m_dndCtx, szFormat); + VBClLogInfo("Drop event from host resulted in: %Rrc\n", rc); + + if (g_cVerbosity) + VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, "Drop: Host -> Guest"); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Host -> Guest: Event signalling that the host has finished sending drag'n drop + * data to the guest for further processing. + * + * @returns IPRT status code. + * @param pMeta Pointer to meta data from host. + */ +int DragInstance::hgDataReceive(PVBGLR3GUESTDNDMETADATA pMeta) +{ + LogFlowThisFunc(("enmMode=%RU32, enmState=%RU32\n", m_enmMode, m_enmState)); + LogFlowThisFunc(("enmMetaType=%RU32\n", pMeta->enmType)); + + if ( m_enmMode != HG + || m_enmState != Dropped) + { + return VERR_INVALID_STATE; + } + + void *pvData = NULL; + size_t cbData = 0; + + int rc = VINF_SUCCESS; /* Shut up GCC. */ + + switch (pMeta->enmType) + { + case VBGLR3GUESTDNDMETADATATYPE_RAW: + { + AssertBreakStmt(pMeta->u.Raw.pvMeta != NULL, rc = VERR_INVALID_POINTER); + pvData = pMeta->u.Raw.pvMeta; + AssertBreakStmt(pMeta->u.Raw.cbMeta, rc = VERR_INVALID_PARAMETER); + cbData = pMeta->u.Raw.cbMeta; + + rc = VINF_SUCCESS; + break; + } + + case VBGLR3GUESTDNDMETADATATYPE_URI_LIST: + { + const char *pcszRootPath = DnDTransferListGetRootPathAbs(&pMeta->u.URI.Transfer); + AssertPtrBreakStmt(pcszRootPath, VERR_INVALID_POINTER); + + VBClLogInfo("Transfer list root directory is '%s'\n", pcszRootPath); + + /* Note: Use the URI format here, as X' DnD spec says so. */ + rc = DnDTransferListGetRootsEx(&pMeta->u.URI.Transfer, DNDTRANSFERLISTFMT_URI, pcszRootPath, + DND_PATH_SEPARATOR_STR, (char **)&pvData, &cbData); + break; + } + + default: + AssertFailedStmt(rc = VERR_NOT_IMPLEMENTED); + break; + } + + if (RT_FAILURE(rc)) + return rc; + + /* + * At this point all data needed (including sent files/directories) should + * be on the guest, so proceed working on communicating with the target window. + */ + VBClLogInfo("Received %RU32 bytes of meta data from host\n", cbData); + + /* Destroy any old data. */ + if (m_pvSelReqData) + { + Assert(m_cbSelReqData); + + RTMemFree(m_pvSelReqData); /** @todo RTMemRealloc? */ + m_cbSelReqData = 0; + } + + /** @todo Handle incremental transfers. */ + + /* Make a copy of the data. This data later then will be used to fill into + * the selection request. */ + if (cbData) + { + m_pvSelReqData = RTMemAlloc(cbData); + if (!m_pvSelReqData) + return VERR_NO_MEMORY; + + memcpy(m_pvSelReqData, pvData, cbData); + m_cbSelReqData = cbData; + } + + /* + * Send a drop event to the current window (target). + * This window in turn then will raise a SelectionRequest message to our proxy window, + * which we will handle in our onX11SelectionRequest handler. + * + * The SelectionRequest will tell us in which format the target wants the data from the host. + */ + XClientMessageEvent m; + RT_ZERO(m); + m.type = ClientMessage; + m.display = m_pDisplay; + m.window = m_wndCur; + m.message_type = xAtom(XA_XdndDrop); + m.format = 32; + m.data.l[XdndDropWindow] = m_wndProxy.hWnd; /* Source window. */ + m.data.l[XdndDropFlags] = 0; /* Reserved for future use. */ + m.data.l[XdndDropTimeStamp] = CurrentTime; /* Our DnD data does not rely on any timing, so just use the current time. */ + + int xRc = XSendEvent(m_pDisplay, m_wndCur, False /* Propagate */, NoEventMask, reinterpret_cast<XEvent*>(&m)); + if (xRc == 0) + VBClLogError("Error sending XA_XdndDrop event to window=%#x: %s\n", m_wndCur, gX11->xErrorToString(xRc).c_str()); + XFlush(m_pDisplay); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Checks if the VM session has changed (can happen when restoring the VM from a saved state) + * and do a reconnect to the DnD HGCM service. + * + * @returns IPRT status code. + */ +int DragInstance::checkForSessionChange(void) +{ + uint64_t uSessionID; + int rc = VbglR3GetSessionId(&uSessionID); + if ( RT_SUCCESS(rc) + && uSessionID != m_dndCtx.uSessionID) + { + LogFlowThisFunc(("VM session has changed to %RU64\n", uSessionID)); + + rc = VbglR3DnDDisconnect(&m_dndCtx); + AssertRC(rc); + + rc = VbglR3DnDConnect(&m_dndCtx); + AssertRC(rc); + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH +/** + * Guest -> Host: Event signalling that the host is asking whether there is a pending + * drag event on the guest (to the host). + * + * @returns IPRT status code. + */ +int DragInstance::ghIsDnDPending(void) +{ + LogFlowThisFunc(("mode=%RU32, state=%RU32\n", m_enmMode, m_enmState)); + + int rc; + + RTCString strFormats = DND_PATH_SEPARATOR_STR; /** @todo If empty, IOCTL fails with VERR_ACCESS_DENIED. */ + VBOXDNDACTION dndActionDefault = VBOX_DND_ACTION_IGNORE; + VBOXDNDACTIONLIST dndActionList = VBOX_DND_ACTION_IGNORE; + + /* Currently in wrong mode? Bail out. */ + if (m_enmMode == HG) + { + rc = VERR_INVALID_STATE; + } + /* Message already processed successfully? */ + else if ( m_enmMode == GH + && ( m_enmState == Dragging + || m_enmState == Dropped) + ) + { + /* No need to query for the source window again. */ + rc = VINF_SUCCESS; + } + else + { + /* Check if the VM session has changed and reconnect to the HGCM service if necessary. */ + rc = checkForSessionChange(); + + /* Determine the current window which currently has the XdndSelection set. */ + Window wndSel = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection)); + LogFlowThisFunc(("wndSel=%#x, wndProxy=%#x, wndCur=%#x\n", wndSel, m_wndProxy.hWnd, m_wndCur)); + + /* Is this another window which has a Xdnd selection and not our proxy window? */ + if ( RT_SUCCESS(rc) + && wndSel + && wndSel != m_wndCur) + { + char *pszWndSelName = wndX11GetNameA(wndSel); + AssertPtrReturn(pszWndSelName, VERR_NO_MEMORY); + VBClLogInfo("New guest source window %#x ('%s')\n", wndSel, pszWndSelName); + + /* Start over. */ + reset(); + + /* Map the window on the current cursor position, which should provoke + * an XdndEnter event. */ + rc = proxyWinShow(); + if (RT_SUCCESS(rc)) + { + rc = mouseCursorFakeMove(); + if (RT_SUCCESS(rc)) + { + bool fWaitFailed = false; /* Waiting for status changed failed? */ + + /* Wait until we're in "Dragging" state. */ + rc = waitForStatusChange(Dragging, 100 /* 100ms timeout */); + + /* + * Note: Don't wait too long here, as this mostly will make + * the drag and drop experience on the host being laggy + * and unresponsive. + * + * Instead, let the host query multiple times with 100ms + * timeout each (see above) and only report an error if + * the overall querying time has been exceeded.< + */ + if (RT_SUCCESS(rc)) + { + m_enmMode = GH; + } + else if (rc == VERR_TIMEOUT) + { + /** @todo Make m_cFailedPendingAttempts configurable. For slower window managers? */ + if (m_cFailedPendingAttempts++ > 50) /* Tolerate up to 5s total (100ms for each slot). */ + fWaitFailed = true; + else + rc = VINF_SUCCESS; + } + else if (RT_FAILURE(rc)) + fWaitFailed = true; + + if (fWaitFailed) + { + VBClLogError("Error mapping proxy window to guest source window %#x ('%s'), rc=%Rrc\n", + wndSel, pszWndSelName, rc); + + /* Reset the counter in any case. */ + m_cFailedPendingAttempts = 0; + } + } + } + + RTStrFree(pszWndSelName); + } + else + VBClLogInfo("No guest source window\n"); + } + + /* + * Acknowledge to the host in any case, regardless + * if something failed here or not. Be responsive. + */ + + int rc2 = RTCritSectEnter(&m_dataCS); + if (RT_SUCCESS(rc2)) + { + /* Filter out the default X11-specific formats (required for Xdnd, 'TARGET' / 'MULTIPLE'); + * those will not be supported by VirtualBox. */ + VBoxDnDAtomList const lstAtomFormatsFiltered = gX11->xAtomListFiltered(m_lstAtomFormats, m_lstAtomFormatsX11); + + /* Anything left to report to the host? */ + if (lstAtomFormatsFiltered.size()) + { + strFormats = gX11->xAtomListToString(lstAtomFormatsFiltered); + dndActionDefault = VBOX_DND_ACTION_COPY; /** @todo Handle default action! */ + dndActionList = VBOX_DND_ACTION_COPY; /** @todo Ditto. */ + dndActionList |= toHGCMActions(m_lstAtomActions); + } + + RTCritSectLeave(&m_dataCS); + } + + if (g_cVerbosity) + { + char *pszActions = DnDActionListToStrA(dndActionList); + AssertPtrReturn(pszActions, VERR_NO_MEMORY); + VBClLogVerbose(1, "Reporting formats '%s' (actions '%s' / %#x, default action is '%s' (%#x)\n", + strFormats.c_str(), pszActions, dndActionList, DnDActionToStr(dndActionDefault), dndActionDefault); + RTStrFree(pszActions); + } + + rc2 = VbglR3DnDGHSendAckPending(&m_dndCtx, dndActionDefault, dndActionList, + strFormats.c_str(), strFormats.length() + 1 /* Include termination */); + LogFlowThisFunc(("uClientID=%RU32, dndActionDefault=0x%x, dndActionList=0x%x, strFormats=%s, rc=%Rrc\n", + m_dndCtx.uClientID, dndActionDefault, dndActionList, strFormats.c_str(), rc2)); + if (RT_FAILURE(rc2)) + { + switch (rc2) + { + case VERR_ACCESS_DENIED: + { + rc = VBClShowNotify(VBOX_DND_SHOWNOTIFY_HEADER, + "Drag and drop to the host either is not supported or disabled. " + "Please enable Guest to Host or Bidirectional drag and drop mode " + "or re-install the VirtualBox Guest Additions."); + AssertRC(rc); + break; + } + + default: + break; + } + + VBClLogError("Error reporting pending drag and drop operation status to host: %Rrc\n", rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Guest -> Host: Event signalling that the host has dropped the item(s) on the + * host side. + * + * @returns IPRT status code. + * @param strFormat Requested format to send to the host. + * @param dndActionRequested Requested action to perform on the guest. + */ +int DragInstance::ghDropped(const RTCString &strFormat, VBOXDNDACTION dndActionRequested) +{ + LogFlowThisFunc(("mode=%RU32, state=%RU32, strFormat=%s, dndActionRequested=0x%x\n", + m_enmMode, m_enmState, strFormat.c_str(), dndActionRequested)); + + /* Currently in wrong mode? Bail out. */ + if ( m_enmMode == Unknown + || m_enmMode == HG) + { + return VERR_INVALID_STATE; + } + + if ( m_enmMode == GH + && m_enmState != Dragging) + { + return VERR_INVALID_STATE; + } + + int rc = VINF_SUCCESS; + + m_enmState = Dropped; + +#ifdef DEBUG + XWindowAttributes xwa; + XGetWindowAttributes(m_pDisplay, m_wndCur, &xwa); + LogFlowThisFunc(("wndProxy=%RU32, wndCur=%RU32, x=%d, y=%d, width=%d, height=%d\n", + m_wndProxy.hWnd, m_wndCur, xwa.x, xwa.y, xwa.width, xwa.height)); + + Window wndSelection = XGetSelectionOwner(m_pDisplay, xAtom(XA_XdndSelection)); + LogFlowThisFunc(("wndSelection=%#x\n", wndSelection)); +#endif + + /* We send a fake mouse move event to the current window, cause + * this should have the grab. */ + mouseCursorFakeMove(); + + /** + * The fake button release event above should lead to a XdndDrop event from the + * source window. Because of showing our proxy window, other Xdnd events can + * occur before, e.g. a XdndPosition event. We are not interested + * in those, so just try to get the right one. + */ + + XClientMessageEvent evDnDDrop; + bool fDrop = waitForX11ClientMsg(evDnDDrop, xAtom(XA_XdndDrop), 5 * 1000 /* 5s timeout */); + if (fDrop) + { + LogFlowThisFunc(("XA_XdndDrop\n")); + + /* Request to convert the selection in the specific format and + * place it to our proxy window as property. */ + Assert(evDnDDrop.message_type == xAtom(XA_XdndDrop)); + + Window wndSource = evDnDDrop.data.l[XdndDropWindow]; /* Source window which has sent the message. */ + Assert(wndSource == m_wndCur); + + Atom aFormat = gX11->stringToxAtom(strFormat.c_str()); + + Time tsDrop; + if (m_uXdndVer >= 1) + tsDrop = evDnDDrop.data.l[XdndDropTimeStamp]; + else + tsDrop = CurrentTime; + + XConvertSelection(m_pDisplay, xAtom(XA_XdndSelection), aFormat, xAtom(XA_XdndSelection), + m_wndProxy.hWnd, tsDrop); + + /* Wait for the selection notify event. */ + XEvent evSelNotify; + RT_ZERO(evSelNotify); + if (waitForX11Msg(evSelNotify, SelectionNotify, 5 * 1000 /* 5s timeout */)) + { + bool fCancel = false; + + /* Make some paranoid checks. */ + if ( evSelNotify.xselection.type == SelectionNotify + && evSelNotify.xselection.display == m_pDisplay + && evSelNotify.xselection.selection == xAtom(XA_XdndSelection) + && evSelNotify.xselection.requestor == m_wndProxy.hWnd + && evSelNotify.xselection.target == aFormat) + { + LogFlowThisFunc(("Selection notfiy (from wnd=%#x)\n", m_wndCur)); + + Atom aPropType; + int iPropFormat; + unsigned long cItems, cbRemaining; + unsigned char *pcData = NULL; + int xRc = XGetWindowProperty(m_pDisplay, m_wndProxy.hWnd, + xAtom(XA_XdndSelection) /* Property */, + 0 /* Offset */, + VBOX_MAX_XPROPERTIES /* Length of 32-bit multiples */, + True /* Delete property? */, + AnyPropertyType, /* Property type */ + &aPropType, &iPropFormat, &cItems, &cbRemaining, &pcData); + if (xRc != Success) + VBClLogError("Error getting XA_XdndSelection property of proxy window=%#x: %s\n", + m_wndProxy.hWnd, gX11->xErrorToString(xRc).c_str()); + + LogFlowThisFunc(("strType=%s, iPropFormat=%d, cItems=%RU32, cbRemaining=%RU32\n", + gX11->xAtomToString(aPropType).c_str(), iPropFormat, cItems, cbRemaining)); + + if ( aPropType != None + && pcData != NULL + && iPropFormat >= 8 + && cItems > 0 + && cbRemaining == 0) + { + size_t cbData = cItems * (iPropFormat / 8); + LogFlowThisFunc(("cbData=%zu\n", cbData)); + + /* For whatever reason some of the string MIME types are not + * zero terminated. Check that and correct it when necessary, + * because the guest side wants this in any case. */ + if ( m_lstAllowedFormats.contains(strFormat) + && pcData[cbData - 1] != '\0') + { + unsigned char *pvDataTmp = static_cast<unsigned char*>(RTMemAlloc(cbData + 1)); + if (pvDataTmp) + { + memcpy(pvDataTmp, pcData, cbData); + pvDataTmp[cbData++] = '\0'; + + rc = VbglR3DnDGHSendData(&m_dndCtx, strFormat.c_str(), pvDataTmp, cbData); + RTMemFree(pvDataTmp); + } + else + rc = VERR_NO_MEMORY; + } + else + { + /* Send the raw data to the host. */ + rc = VbglR3DnDGHSendData(&m_dndCtx, strFormat.c_str(), pcData, cbData); + LogFlowThisFunc(("Sent strFormat=%s, rc=%Rrc\n", strFormat.c_str(), rc)); + } + + if (RT_SUCCESS(rc)) + { + rc = m_wndProxy.sendFinished(wndSource, dndActionRequested); + } + else + fCancel = true; + } + else + { + if (aPropType == xAtom(XA_INCR)) + { + /** @todo Support incremental transfers. */ + AssertMsgFailed(("Incremental transfers are not supported yet\n")); + + VBClLogError("Incremental transfers are not supported yet\n"); + rc = VERR_NOT_IMPLEMENTED; + } + else + { + VBClLogError("Not supported data type: %s\n", gX11->xAtomToString(aPropType).c_str()); + rc = VERR_NOT_SUPPORTED; + } + + fCancel = true; + } + + if (fCancel) + { + VBClLogInfo("Cancelling dropping to host\n"); + + /* Cancel the operation -- inform the source window by + * sending a XdndFinished message so that the source can toss the required data. */ + rc = m_wndProxy.sendFinished(wndSource, VBOX_DND_ACTION_IGNORE); + } + + /* Cleanup. */ + if (pcData) + XFree(pcData); + } + else + rc = VERR_INVALID_PARAMETER; + } + else + rc = VERR_TIMEOUT; + } + else + rc = VERR_TIMEOUT; + + /* Inform the host on error. */ + if (RT_FAILURE(rc)) + { + int rc2 = VbglR3DnDSendError(&m_dndCtx, rc); + LogFlowThisFunc(("Sending error %Rrc to host resulted in %Rrc\n", rc, rc2)); RT_NOREF(rc2); + /* This is not fatal for us, just ignore. */ + } + + /* At this point, we have either successfully transfered any data or not. + * So reset our internal state because we are done here for the current (ongoing) + * drag and drop operation. */ + reset(); + + LogFlowFuncLeaveRC(rc); + return rc; +} +#endif /* VBOX_WITH_DRAG_AND_DROP_GH */ + +/* + * Helpers + */ + +/** + * Fakes moving the mouse cursor to provoke various drag and drop + * events such as entering a target window or moving within a + * source window. + * + * Not the most elegant and probably correct function, but does + * the work for now. + * + * @returns IPRT status code. + */ +int DragInstance::mouseCursorFakeMove(void) +{ + int iScreenID = XDefaultScreen(m_pDisplay); + /** @todo What about multiple screens? Test this! */ + + const int iScrX = XDisplayWidth(m_pDisplay, iScreenID); + const int iScrY = XDisplayHeight(m_pDisplay, iScreenID); + + int fx, fy, rx, ry; + Window wndTemp, wndChild; + int wx, wy; unsigned int mask; + XQueryPointer(m_pDisplay, m_wndRoot, &wndTemp, &wndChild, &rx, &ry, &wx, &wy, &mask); + + /* + * Apply some simple clipping and change the position slightly. + */ + + /* FakeX */ + if (rx == 0) fx = 1; + else if (rx == iScrX) fx = iScrX - 1; + else fx = rx + 1; + + /* FakeY */ + if (ry == 0) fy = 1; + else if (ry == iScrY) fy = iScrY - 1; + else fy = ry + 1; + + /* + * Move the cursor to trigger the wanted events. + */ + LogFlowThisFunc(("cursorRootX=%d, cursorRootY=%d\n", fx, fy)); + int rc = mouseCursorMove(fx, fy); + if (RT_SUCCESS(rc)) + { + /* Move the cursor back to its original position. */ + rc = mouseCursorMove(rx, ry); + } + + return rc; +} + +/** + * Moves the mouse pointer to a specific position. + * + * @returns IPRT status code. + * @param iPosX Absolute X coordinate. + * @param iPosY Absolute Y coordinate. + */ +int DragInstance::mouseCursorMove(int iPosX, int iPosY) +{ + int const iScreenID = XDefaultScreen(m_pDisplay); + /** @todo What about multiple screens? Test this! */ + + int const iScreenWidth = XDisplayWidth (m_pDisplay, iScreenID); + int const iScreenHeight = XDisplayHeight(m_pDisplay, iScreenID); + + iPosX = RT_CLAMP(iPosX, 0, iScreenWidth); + iPosY = RT_CLAMP(iPosY, 0, iScreenHeight); + + /* Same mouse position as before? No need to do anything. */ + if ( m_lastMouseX == iPosX + && m_lastMouseY == iPosY) + { + return VINF_SUCCESS; + } + + LogFlowThisFunc(("iPosX=%d, iPosY=%d, m_wndRoot=%#x\n", iPosX, iPosY, m_wndRoot)); + + /* Move the guest pointer to the DnD position, so we can find the window + * below that position. */ + int xRc = XWarpPointer(m_pDisplay, None, m_wndRoot, 0, 0, 0, 0, iPosX, iPosY); + if (xRc == Success) + { + XFlush(m_pDisplay); + + m_lastMouseX = iPosX; + m_lastMouseY = iPosY; + } + else + VBClLogError("Moving mouse cursor failed: %s", gX11->xErrorToString(xRc).c_str()); + + return VINF_SUCCESS; +} + +/** + * Sends a mouse button event to a specific window. + * + * @param wndDest Window to send the mouse button event to. + * @param rx X coordinate relative to the root window's origin. + * @param ry Y coordinate relative to the root window's origin. + * @param iButton Mouse button to press/release. + * @param fPress Whether to press or release the mouse button. + */ +void DragInstance::mouseButtonSet(Window wndDest, int rx, int ry, int iButton, bool fPress) +{ + LogFlowThisFunc(("wndDest=%#x, rx=%d, ry=%d, iBtn=%d, fPress=%RTbool\n", + wndDest, rx, ry, iButton, fPress)); + +#ifdef VBOX_DND_WITH_XTEST + /** @todo Make this check run only once. */ + int ev, er, ma, mi; + if (XTestQueryExtension(m_pDisplay, &ev, &er, &ma, &mi)) + { + LogFlowThisFunc(("XText extension available\n")); + + int xRc = XTestFakeButtonEvent(m_pDisplay, 1, fPress ? True : False, CurrentTime); + if (Rc == 0) + VBClLogError("Error sending XTestFakeButtonEvent event: %s\n", gX11->xErrorToString(xRc).c_str()); + XFlush(m_pDisplay); + } + else + { +#endif + LogFlowThisFunc(("Note: XText extension not available or disabled\n")); + + unsigned int mask = 0; + + if ( rx == -1 + && ry == -1) + { + Window wndRoot, wndChild; + int wx, wy; + XQueryPointer(m_pDisplay, m_wndRoot, &wndRoot, &wndChild, &rx, &ry, &wx, &wy, &mask); + LogFlowThisFunc(("Mouse pointer is at root x=%d, y=%d\n", rx, ry)); + } + + XButtonEvent eBtn; + RT_ZERO(eBtn); + + eBtn.display = m_pDisplay; + eBtn.root = m_wndRoot; + eBtn.window = wndDest; + eBtn.subwindow = None; + eBtn.same_screen = True; + eBtn.time = CurrentTime; + eBtn.button = iButton; + eBtn.state = mask | (iButton == 1 ? Button1MotionMask : + iButton == 2 ? Button2MotionMask : + iButton == 3 ? Button3MotionMask : + iButton == 4 ? Button4MotionMask : + iButton == 5 ? Button5MotionMask : 0); + eBtn.type = fPress ? ButtonPress : ButtonRelease; + eBtn.send_event = False; + eBtn.x_root = rx; + eBtn.y_root = ry; + + XTranslateCoordinates(m_pDisplay, eBtn.root, eBtn.window, eBtn.x_root, eBtn.y_root, &eBtn.x, &eBtn.y, &eBtn.subwindow); + LogFlowThisFunc(("state=0x%x, x=%d, y=%d\n", eBtn.state, eBtn.x, eBtn.y)); + + int xRc = XSendEvent(m_pDisplay, wndDest, True /* fPropagate */, + ButtonPressMask, + reinterpret_cast<XEvent*>(&eBtn)); + if (xRc == 0) + VBClLogError("Error sending XButtonEvent event to window=%#x: %s\n", wndDest, gX11->xErrorToString(xRc).c_str()); + + XFlush(m_pDisplay); + +#ifdef VBOX_DND_WITH_XTEST + } +#endif +} + +/** + * Shows the (invisible) proxy window. The proxy window is needed for intercepting + * drags from the host to the guest or from the guest to the host. It acts as a proxy + * between the host and the actual (UI) element on the guest OS. + * + * To not make it miss any actions this window gets spawned across the entire guest + * screen (think of an umbrella) to (hopefully) capture everything. A proxy window + * which follows the cursor would be far too slow here. + * + * @returns IPRT status code. + * @param piRootX X coordinate relative to the root window's origin. Optional. + * @param piRootY Y coordinate relative to the root window's origin. Optional. + */ +int DragInstance::proxyWinShow(int *piRootX /* = NULL */, int *piRootY /* = NULL */) const +{ + /* piRootX is optional. */ + /* piRootY is optional. */ + + LogFlowThisFuncEnter(); + + int rc = VINF_SUCCESS; + +#if 0 +# ifdef VBOX_DND_WITH_XTEST + XTestGrabControl(m_pDisplay, False); +# endif +#endif + + /* Get the mouse pointer position and determine if we're on the same screen as the root window + * and return the current child window beneath our mouse pointer, if any. */ + int iRootX, iRootY; + int iChildX, iChildY; + unsigned int iMask; + Window wndRoot, wndChild; + Bool fInRootWnd = XQueryPointer(m_pDisplay, m_wndRoot, &wndRoot, &wndChild, + &iRootX, &iRootY, &iChildX, &iChildY, &iMask); + + LogFlowThisFunc(("fInRootWnd=%RTbool, wndRoot=%RU32, wndChild=%RU32, iRootX=%d, iRootY=%d\n", + RT_BOOL(fInRootWnd), wndRoot, wndChild, iRootX, iRootY)); RT_NOREF(fInRootWnd); + + if (piRootX) + *piRootX = iRootX; + if (piRootY) + *piRootY = iRootY; + + XSynchronize(m_pDisplay, True /* Enable sync */); + + /* Bring our proxy window into foreground. */ + XMapWindow(m_pDisplay, m_wndProxy.hWnd); + XRaiseWindow(m_pDisplay, m_wndProxy.hWnd); + + /* Spawn our proxy window over the entire screen, making it an easy drop target for the host's cursor. */ + LogFlowThisFunc(("Proxy window x=%d, y=%d, width=%d, height=%d\n", + m_wndProxy.iX, m_wndProxy.iY, m_wndProxy.iWidth, m_wndProxy.iHeight)); + XMoveResizeWindow(m_pDisplay, m_wndProxy.hWnd, m_wndProxy.iX, m_wndProxy.iY, m_wndProxy.iWidth, m_wndProxy.iHeight); + + XFlush(m_pDisplay); + + XSynchronize(m_pDisplay, False /* Disable sync */); + +#if 0 +# ifdef VBOX_DND_WITH_XTEST + XTestGrabControl(m_pDisplay, True); +# endif +#endif + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Hides the (invisible) proxy window. + */ +int DragInstance::proxyWinHide(void) +{ + LogFlowFuncEnter(); + + XUnmapWindow(m_pDisplay, m_wndProxy.hWnd); + XFlush(m_pDisplay); + + return VINF_SUCCESS; /** @todo Add error checking. */ +} + +/** + * Allocates the name (title) of an X window. + * The returned pointer must be freed using RTStrFree(). + * + * @returns Pointer to the allocated window name. + * @retval NULL on allocation failure. + * @retval "<No name>" if window name was not found / invalid window handle. + * @param wndThis Window to retrieve name for. + */ +char *DragInstance::wndX11GetNameA(Window wndThis) const +{ + char *pszName = NULL; + + XTextProperty propName; + if ( wndThis != None + && XGetWMName(m_pDisplay, wndThis, &propName)) + { + if (propName.value) + pszName = RTStrDup((char *)propName.value); /** @todo UTF8? */ + XFree(propName.value); + } + + if (!pszName) /* No window name found? */ + pszName = RTStrDup("<No name>"); + + return pszName; +} + +/** + * Clear a window's supported/accepted actions list. + * + * @param wndThis Window to clear the list for. + */ +void DragInstance::wndXDnDClearActionList(Window wndThis) const +{ + XDeleteProperty(m_pDisplay, wndThis, xAtom(XA_XdndActionList)); +} + +/** + * Clear a window's supported/accepted formats list. + * + * @param wndThis Window to clear the list for. + */ +void DragInstance::wndXDnDClearFormatList(Window wndThis) const +{ + XDeleteProperty(m_pDisplay, wndThis, xAtom(XA_XdndTypeList)); +} + +/** + * Retrieves a window's supported/accepted XDnD actions. + * + * @returns IPRT status code. + * @param wndThis Window to retrieve the XDnD actions for. + * @param lstActions Reference to VBoxDnDAtomList to store the action into. + */ +int DragInstance::wndXDnDGetActionList(Window wndThis, VBoxDnDAtomList &lstActions) const +{ + Atom iActType = None; + int iActFmt; + unsigned long cItems, cbData; + unsigned char *pcbData = NULL; + + /* Fetch the possible list of actions, if this property is set. */ + int xRc = XGetWindowProperty(m_pDisplay, wndThis, + xAtom(XA_XdndActionList), + 0, VBOX_MAX_XPROPERTIES, + False, XA_ATOM, &iActType, &iActFmt, &cItems, &cbData, &pcbData); + if (xRc != Success) + { + LogFlowThisFunc(("Error getting XA_XdndActionList atoms from window=%#x: %s\n", + wndThis, gX11->xErrorToString(xRc).c_str())); + return VERR_NOT_FOUND; + } + + LogFlowThisFunc(("wndThis=%#x, cItems=%RU32, pcbData=%p\n", wndThis, cItems, pcbData)); + + if (cItems > 0) + { + AssertPtr(pcbData); + Atom *paData = reinterpret_cast<Atom *>(pcbData); + + for (unsigned i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, cItems); i++) + { + LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(paData[i]).c_str())); + lstActions.append(paData[i]); + } + + XFree(pcbData); + } + + return VINF_SUCCESS; +} + +/** + * Retrieves a window's supported/accepted XDnD formats. + * + * @returns IPRT status code. + * @param wndThis Window to retrieve the XDnD formats for. + * @param lstTypes Reference to VBoxDnDAtomList to store the formats into. + */ +int DragInstance::wndXDnDGetFormatList(Window wndThis, VBoxDnDAtomList &lstTypes) const +{ + Atom iActType = None; + int iActFmt; + unsigned long cItems, cbData; + unsigned char *pcbData = NULL; + + int xRc = XGetWindowProperty(m_pDisplay, wndThis, + xAtom(XA_XdndTypeList), + 0, VBOX_MAX_XPROPERTIES, + False, XA_ATOM, &iActType, &iActFmt, &cItems, &cbData, &pcbData); + if (xRc != Success) + { + LogFlowThisFunc(("Error getting XA_XdndTypeList atoms from window=%#x: %s\n", + wndThis, gX11->xErrorToString(xRc).c_str())); + return VERR_NOT_FOUND; + } + + LogFlowThisFunc(("wndThis=%#x, cItems=%RU32, pcbData=%p\n", wndThis, cItems, pcbData)); + + if (cItems > 0) + { + AssertPtr(pcbData); + Atom *paData = reinterpret_cast<Atom *>(pcbData); + + for (unsigned i = 0; i < RT_MIN(VBOX_MAX_XPROPERTIES, cItems); i++) + { + LogFlowThisFunc(("\t%s\n", gX11->xAtomToString(paData[i]).c_str())); + lstTypes.append(paData[i]); + } + + XFree(pcbData); + } + + return VINF_SUCCESS; +} + +/** + * Sets (replaces) a window's XDnD accepted/allowed actions. + * + * @returns IPRT status code. + * @param wndThis Window to set the format list for. + * @param lstActions Reference to list of XDnD actions to set. + */ +int DragInstance::wndXDnDSetActionList(Window wndThis, const VBoxDnDAtomList &lstActions) const +{ + if (lstActions.isEmpty()) + return VINF_SUCCESS; + + XChangeProperty(m_pDisplay, wndThis, + xAtom(XA_XdndActionList), + XA_ATOM, 32, PropModeReplace, + reinterpret_cast<const unsigned char*>(lstActions.raw()), + lstActions.size()); + + return VINF_SUCCESS; +} + +/** + * Sets (replaces) a window's XDnD accepted format list. + * + * @returns IPRT status code. + * @param wndThis Window to set the format list for. + * @param atmProp Property to set. + * @param lstFormats Reference to list of XDnD formats to set. + */ +int DragInstance::wndXDnDSetFormatList(Window wndThis, Atom atmProp, const VBoxDnDAtomList &lstFormats) const +{ + if (lstFormats.isEmpty()) + return VERR_INVALID_PARAMETER; + + /* Add the property with the property data to the window. */ + XChangeProperty(m_pDisplay, wndThis, atmProp, + XA_ATOM, 32, PropModeReplace, + reinterpret_cast<const unsigned char*>(lstFormats.raw()), + lstFormats.size()); + + return VINF_SUCCESS; +} + +/** + * Appends a RTCString list to VBoxDnDAtomList list. + * + * @returns IPRT status code. + * @param lstFormats Reference to RTCString list to convert. + * @param lstAtoms Reference to VBoxDnDAtomList list to store results in. + */ +int DragInstance::appendFormatsToList(const RTCList<RTCString> &lstFormats, VBoxDnDAtomList &lstAtoms) const +{ + for (size_t i = 0; i < lstFormats.size(); ++i) + lstAtoms.append(XInternAtom(m_pDisplay, lstFormats.at(i).c_str(), False)); + + return VINF_SUCCESS; +} + +/** + * Appends a raw-data string list to VBoxDnDAtomList list. + * + * @returns IPRT status code. + * @param pvData Pointer to string data to convert. + * @param cbData Size (in bytes) to convert. + * @param lstAtoms Reference to VBoxDnDAtomList list to store results in. + */ +int DragInstance::appendDataToList(const void *pvData, uint32_t cbData, VBoxDnDAtomList &lstAtoms) const +{ + RT_NOREF1(lstAtoms); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(cbData, VERR_INVALID_PARAMETER); + + const char *pszStr = (char *)pvData; + uint32_t cbStr = cbData; + + int rc = VINF_SUCCESS; + + VBoxDnDAtomList lstAtom; + while (cbStr) + { + size_t cbSize = RTStrNLen(pszStr, cbStr); + + /* Create a copy with max N chars, so that we are on the save side, + * even if the data isn't zero terminated. */ + char *pszTmp = RTStrDupN(pszStr, cbSize); + if (!pszTmp) + { + rc = VERR_NO_MEMORY; + break; + } + + lstAtom.append(XInternAtom(m_pDisplay, pszTmp, False)); + RTStrFree(pszTmp); + + pszStr += cbSize + 1; + cbStr -= cbSize + 1; + } + + return rc; +} + +/** + * Converts a HGCM-based drag'n drop action to a Atom-based drag'n drop action. + * + * @returns Converted Atom-based drag'n drop action. + * @param dndAction HGCM drag'n drop actions to convert. + */ +/* static */ +Atom DragInstance::toAtomAction(VBOXDNDACTION dndAction) +{ + /* Ignore is None. */ + return (isDnDCopyAction(dndAction) ? xAtom(XA_XdndActionCopy) : + isDnDMoveAction(dndAction) ? xAtom(XA_XdndActionMove) : + isDnDLinkAction(dndAction) ? xAtom(XA_XdndActionLink) : + None); +} + +/** + * Converts HGCM-based drag'n drop actions to a VBoxDnDAtomList list. + * + * @returns IPRT status code. + * @param dndActionList HGCM drag'n drop actions to convert. + * @param lstAtoms Reference to VBoxDnDAtomList to store actions in. + */ +/* static */ +int DragInstance::toAtomActions(VBOXDNDACTIONLIST dndActionList, VBoxDnDAtomList &lstAtoms) +{ + if (hasDnDCopyAction(dndActionList)) + lstAtoms.append(xAtom(XA_XdndActionCopy)); + if (hasDnDMoveAction(dndActionList)) + lstAtoms.append(xAtom(XA_XdndActionMove)); + if (hasDnDLinkAction(dndActionList)) + lstAtoms.append(xAtom(XA_XdndActionLink)); + + return VINF_SUCCESS; +} + +/** + * Converts an Atom-based drag'n drop action to a HGCM drag'n drop action. + * + * @returns HGCM drag'n drop action. + * @param atom Atom-based drag'n drop action to convert. + */ +/* static */ +uint32_t DragInstance::toHGCMAction(Atom atom) +{ + uint32_t uAction = VBOX_DND_ACTION_IGNORE; + + if (atom == xAtom(XA_XdndActionCopy)) + uAction = VBOX_DND_ACTION_COPY; + else if (atom == xAtom(XA_XdndActionMove)) + uAction = VBOX_DND_ACTION_MOVE; + else if (atom == xAtom(XA_XdndActionLink)) + uAction = VBOX_DND_ACTION_LINK; + + return uAction; +} + +/** + * Converts an VBoxDnDAtomList list to an HGCM action list. + * + * @returns ORed HGCM action list. + * @param lstActions List of Atom-based actions to convert. + */ +/* static */ +uint32_t DragInstance::toHGCMActions(const VBoxDnDAtomList &lstActions) +{ + uint32_t uActions = VBOX_DND_ACTION_IGNORE; + + for (size_t i = 0; i < lstActions.size(); i++) + uActions |= toHGCMAction(lstActions.at(i)); + + return uActions; +} + +/********************************************************************************************************************************* + * VBoxDnDProxyWnd implementation. * + ********************************************************************************************************************************/ + +VBoxDnDProxyWnd::VBoxDnDProxyWnd(void) + : pDisp(NULL) + , hWnd(0) + , iX(0) + , iY(0) + , iWidth(0) + , iHeight(0) +{ + +} + +VBoxDnDProxyWnd::~VBoxDnDProxyWnd(void) +{ + destroy(); +} + +int VBoxDnDProxyWnd::init(Display *pDisplay) +{ + /** @todo What about multiple screens? Test this! */ + int iScreenID = XDefaultScreen(pDisplay); + + iWidth = XDisplayWidth(pDisplay, iScreenID); + iHeight = XDisplayHeight(pDisplay, iScreenID); + pDisp = pDisplay; + + return VINF_SUCCESS; +} + +void VBoxDnDProxyWnd::destroy(void) +{ + +} + +int VBoxDnDProxyWnd::sendFinished(Window hWndSource, VBOXDNDACTION dndAction) +{ + /* Was the drop accepted by the host? That is, anything than ignoring. */ + bool fDropAccepted = dndAction > VBOX_DND_ACTION_IGNORE; + + LogFlowFunc(("dndAction=0x%x\n", dndAction)); + + /* Confirm the result of the transfer to the target window. */ + XClientMessageEvent m; + RT_ZERO(m); + m.type = ClientMessage; + m.display = pDisp; + m.window = hWnd; + m.message_type = xAtom(XA_XdndFinished); + m.format = 32; + m.data.l[XdndFinishedWindow] = hWnd; /* Target window. */ + m.data.l[XdndFinishedFlags] = fDropAccepted ? RT_BIT(0) : 0; /* Was the drop accepted? */ + m.data.l[XdndFinishedAction] = fDropAccepted ? DragInstance::toAtomAction(dndAction) : None; /* Action used on accept. */ + + int xRc = XSendEvent(pDisp, hWndSource, True, NoEventMask, reinterpret_cast<XEvent*>(&m)); + if (xRc == 0) + { + VBClLogError("Error sending finished event to source window=%#x: %s\n", + hWndSource, gX11->xErrorToString(xRc).c_str()); + + return VERR_GENERAL_FAILURE; /** @todo Fudge. */ + } + + return VINF_SUCCESS; +} + +/********************************************************************************************************************************* + * DragAndDropService implementation. * + ********************************************************************************************************************************/ + +/** @copydoc VBCLSERVICE::pfnInit */ +int DragAndDropService::init(void) +{ + LogFlowFuncEnter(); + + /* Connect to the x11 server. */ + m_pDisplay = XOpenDisplay(NULL); + if (!m_pDisplay) + { + VBClLogFatalError("Unable to connect to X server -- running in a terminal session?\n"); + return VERR_NOT_FOUND; + } + + xHelpers *pHelpers = xHelpers::getInstance(m_pDisplay); + if (!pHelpers) + return VERR_NO_MEMORY; + + int rc; + + do + { + rc = RTSemEventCreate(&m_hEventSem); + AssertRCBreak(rc); + + rc = RTCritSectInit(&m_eventQueueCS); + AssertRCBreak(rc); + + rc = VbglR3DnDConnect(&m_dndCtx); + AssertRCBreak(rc); + + /* Event thread for events coming from the HGCM device. */ + rc = RTThreadCreate(&m_hHGCMThread, hgcmEventThread, this, + 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "dndHGCM"); + AssertRCBreak(rc); + + rc = RTThreadUserWait(m_hHGCMThread, RT_MS_30SEC); + AssertRCBreak(rc); + + if (ASMAtomicReadBool(&m_fStop)) + break; + + /* Event thread for events coming from the x11 system. */ + rc = RTThreadCreate(&m_hX11Thread, x11EventThread, this, + 0, RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, "dndX11"); + AssertRCBreak(rc); + + rc = RTThreadUserWait(m_hX11Thread, RT_MS_30SEC); + AssertRCBreak(rc); + + if (ASMAtomicReadBool(&m_fStop)) + break; + + } while (0); + + if (m_fStop) + rc = VERR_GENERAL_FAILURE; /** @todo Fudge! */ + + if (RT_FAILURE(rc)) + VBClLogError("Failed to initialize, rc=%Rrc\n", rc); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** @copydoc VBCLSERVICE::pfnWorker */ +int DragAndDropService::worker(bool volatile *pfShutdown) +{ + int rc; + do + { + m_pCurDnD = new DragInstance(m_pDisplay, this); + if (!m_pCurDnD) + { + rc = VERR_NO_MEMORY; + break; + } + + /* Note: For multiple screen support in VBox it is not necessary to use + * another screen number than zero. Maybe in the future it will become + * necessary if VBox supports multiple X11 screens. */ + rc = m_pCurDnD->init(0 /* uScreenID */); + /* Note: Can return VINF_PERMISSION_DENIED if HGCM host service is not available. */ + if (rc != VINF_SUCCESS) + { + if (RT_FAILURE(rc)) + VBClLogError("Unable to connect to drag and drop service, rc=%Rrc\n", rc); + else if (rc == VINF_PERMISSION_DENIED) /* No error, DnD might be just disabled. */ + VBClLogInfo("Not available on host, terminating\n"); + break; + } + + /* Let the main thread know that it can continue spawning services. */ + RTThreadUserSignal(RTThreadSelf()); + + /* Enter the main event processing loop. */ + do + { + DNDEVENT e; + RT_ZERO(e); + + LogFlowFunc(("Waiting for new events ...\n")); + rc = RTSemEventWait(m_hEventSem, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + + size_t cEvents = 0; + + int rc2 = RTCritSectEnter(&m_eventQueueCS); + if (RT_SUCCESS(rc2)) + { + cEvents = m_eventQueue.size(); + + rc2 = RTCritSectLeave(&m_eventQueueCS); + AssertRC(rc2); + } + + while (cEvents) + { + rc2 = RTCritSectEnter(&m_eventQueueCS); + if (RT_SUCCESS(rc2)) + { + if (m_eventQueue.isEmpty()) + { + rc2 = RTCritSectLeave(&m_eventQueueCS); + AssertRC(rc2); + break; + } + + e = m_eventQueue.first(); + m_eventQueue.removeFirst(); + + rc2 = RTCritSectLeave(&m_eventQueueCS); + AssertRC(rc2); + } + + if (e.enmType == DNDEVENT::DnDEventType_HGCM) + { + PVBGLR3DNDEVENT pVbglR3Event = e.hgcm; + AssertPtrBreak(pVbglR3Event); + + LogFlowThisFunc(("HGCM event enmType=%RU32\n", pVbglR3Event->enmType)); + switch (pVbglR3Event->enmType) + { + case VBGLR3DNDEVENTTYPE_HG_ENTER: + { + if (pVbglR3Event->u.HG_Enter.cbFormats) + { + RTCList<RTCString> lstFormats = + RTCString(pVbglR3Event->u.HG_Enter.pszFormats, pVbglR3Event->u.HG_Enter.cbFormats - 1).split(DND_PATH_SEPARATOR_STR); + rc = m_pCurDnD->hgEnter(lstFormats, pVbglR3Event->u.HG_Enter.dndLstActionsAllowed); + if (RT_FAILURE(rc)) + break; + /* Enter is always followed by a move event. */ + } + else + { + AssertMsgFailed(("cbFormats is 0\n")); + rc = VERR_INVALID_PARAMETER; + break; + } + + /* Note: After HOST_DND_FN_HG_EVT_ENTER there immediately is a move + * event, so fall through is intentional here. */ + RT_FALL_THROUGH(); + } + + case VBGLR3DNDEVENTTYPE_HG_MOVE: + { + rc = m_pCurDnD->hgMove(pVbglR3Event->u.HG_Move.uXpos, pVbglR3Event->u.HG_Move.uYpos, + pVbglR3Event->u.HG_Move.dndActionDefault); + break; + } + + case VBGLR3DNDEVENTTYPE_HG_LEAVE: + { + rc = m_pCurDnD->hgLeave(); + break; + } + + case VBGLR3DNDEVENTTYPE_HG_DROP: + { + rc = m_pCurDnD->hgDrop(pVbglR3Event->u.HG_Drop.uXpos, pVbglR3Event->u.HG_Drop.uYpos, + pVbglR3Event->u.HG_Drop.dndActionDefault); + break; + } + + /* Note: VbglR3DnDRecvNextMsg() will return HOST_DND_FN_HG_SND_DATA_HDR when + * the host has finished copying over all the data to the guest. + * + * The actual data transfer (and message processing for it) will be done + * internally by VbglR3DnDRecvNextMsg() to not duplicate any code for different + * platforms. + * + * The data header now will contain all the (meta) data the guest needs in + * order to complete the DnD operation. */ + case VBGLR3DNDEVENTTYPE_HG_RECEIVE: + { + rc = m_pCurDnD->hgDataReceive(&pVbglR3Event->u.HG_Received.Meta); + break; + } + + case VBGLR3DNDEVENTTYPE_CANCEL: + { + m_pCurDnD->reset(); + break; + } + +#ifdef VBOX_WITH_DRAG_AND_DROP_GH + case VBGLR3DNDEVENTTYPE_GH_ERROR: + { + m_pCurDnD->reset(); + break; + } + + case VBGLR3DNDEVENTTYPE_GH_REQ_PENDING: + { + rc = m_pCurDnD->ghIsDnDPending(); + break; + } + + case VBGLR3DNDEVENTTYPE_GH_DROP: + { + rc = m_pCurDnD->ghDropped(pVbglR3Event->u.GH_Drop.pszFormat, pVbglR3Event->u.GH_Drop.dndActionRequested); + break; + } +#endif + case VBGLR3DNDEVENTTYPE_QUIT: + { + rc = VINF_SUCCESS; + break; + } + + default: + { + VBClLogError("Received unsupported message type %RU32\n", pVbglR3Event->enmType); + rc = VERR_NOT_SUPPORTED; + break; + } + } + + LogFlowFunc(("Message %RU32 processed with %Rrc\n", pVbglR3Event->enmType, rc)); + if (RT_FAILURE(rc)) + { + /* Tell the user. */ + VBClLogError("Processing message %RU32 failed with %Rrc\n", pVbglR3Event->enmType, rc); + + /* If anything went wrong, do a reset and start over. */ + reset(); + } + + const bool fQuit = pVbglR3Event->enmType == VBGLR3DNDEVENTTYPE_QUIT; + + VbglR3DnDEventFree(e.hgcm); + e.hgcm = NULL; + + if (fQuit) + break; + } + else if (e.enmType == DNDEVENT::DnDEventType_X11) + { + LogFlowThisFunc(("X11 event (type %#x)\n", e.x11.type)); + m_pCurDnD->onX11Event(e.x11); + } + else + AssertMsgFailed(("Unknown event queue type %RU32\n", e.enmType)); + + --cEvents; + + } /* for */ + + /* + * Make sure that any X11 requests have actually been sent to the + * server, since we are waiting for responses using poll() on + * another thread which will not automatically trigger flushing. + */ + XFlush(m_pDisplay); + + if (m_fStop) + break; + + } while (!ASMAtomicReadBool(pfShutdown)); + + } while (0); + + if (m_pCurDnD) + { + delete m_pCurDnD; + m_pCurDnD = NULL; + } + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Resets the DnD service' data. + */ +void DragAndDropService::reset(void) +{ + LogFlowFuncEnter(); + + if (m_pCurDnD) + m_pCurDnD->reset(); + + /* + * Clear the event queue. + */ + int rc2 = RTCritSectEnter(&m_eventQueueCS); + if (RT_SUCCESS(rc2)) + { + for (size_t i = 0; i < m_eventQueue.size(); i++) + { + switch (m_eventQueue[i].enmType) + { + case DNDEVENT::DnDEventType_HGCM: + { + VbglR3DnDEventFree(m_eventQueue[i].hgcm); + break; + } + + default: + break; + } + + } + + m_eventQueue.clear(); + + rc2 = RTCritSectLeave(&m_eventQueueCS); + AssertRC(rc2); + } + + LogFlowFuncLeave(); +} + +/** @copydoc VBCLSERVICE::pfnStop */ +void DragAndDropService::stop(void) +{ + LogFlowFuncEnter(); + + /* Set stop flag first. */ + ASMAtomicXchgBool(&m_fStop, true); + + /* First, disconnect any instances. */ + if (m_pCurDnD) + m_pCurDnD->stop(); + + /* Second, disconnect the service's DnD connection. */ + VbglR3DnDDisconnect(&m_dndCtx); + + LogFlowFuncLeave(); +} + +/** @copydoc VBCLSERVICE::pfnTerm */ +int DragAndDropService::term(void) +{ + int rc = VINF_SUCCESS; + + /* + * Wait for threads to terminate. + */ + int rcThread; + + if (m_hX11Thread != NIL_RTTHREAD) + { + VBClLogVerbose(2, "Terminating X11 thread ...\n"); + + int rc2 = RTThreadWait(m_hX11Thread, RT_MS_30SEC, &rcThread); + if (RT_SUCCESS(rc2)) + rc2 = rcThread; + + if (RT_FAILURE(rc2)) + VBClLogError("Error waiting for X11 thread to terminate: %Rrc\n", rc2); + + if (RT_SUCCESS(rc)) + rc = rc2; + + m_hX11Thread = NIL_RTTHREAD; + + VBClLogVerbose(2, "X11 thread terminated\n"); + } + + if (m_hHGCMThread != NIL_RTTHREAD) + { + VBClLogVerbose(2, "Terminating HGCM thread ...\n"); + + int rc2 = RTThreadWait(m_hHGCMThread, RT_MS_30SEC, &rcThread); + if (RT_SUCCESS(rc2)) + rc2 = rcThread; + + if (RT_FAILURE(rc2)) + VBClLogError("Error waiting for HGCM thread to terminate: %Rrc\n", rc2); + + if (RT_SUCCESS(rc)) + rc = rc2; + + m_hHGCMThread = NIL_RTTHREAD; + + VBClLogVerbose(2, "HGCM thread terminated\n"); + } + + reset(); + + if (m_pCurDnD) + { + delete m_pCurDnD; + m_pCurDnD = NULL; + } + + xHelpers::destroyInstance(); + + return rc; +} + +/** + * Static callback function for HGCM message processing thread. An internal + * message queue will be filled which then will be processed by the according + * drag'n drop instance. + * + * @returns IPRT status code. + * @param hThread Thread handle to use. + * @param pvUser Pointer to DragAndDropService instance to use. + */ +/* static */ +DECLCALLBACK(int) DragAndDropService::hgcmEventThread(RTTHREAD hThread, void *pvUser) +{ + AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER); + DragAndDropService *pThis = static_cast<DragAndDropService*>(pvUser); + + /* Let the service instance know in any case. */ + int rc = RTThreadUserSignal(hThread); + AssertRCReturn(rc, rc); + + VBClLogVerbose(2, "HGCM thread started\n"); + + /* Number of invalid messages skipped in a row. */ + int cMsgSkippedInvalid = 0; + DNDEVENT e; + + do + { + RT_ZERO(e); + e.enmType = DNDEVENT::DnDEventType_HGCM; + + /* Wait for new events. */ + rc = VbglR3DnDEventGetNext(&pThis->m_dndCtx, &e.hgcm); + if (RT_SUCCESS(rc)) + { + cMsgSkippedInvalid = 0; /* Reset skipped messages count. */ + + int rc2 = RTCritSectEnter(&pThis->m_eventQueueCS); + if (RT_SUCCESS(rc2)) + { + VBClLogVerbose(2, "Received new HGCM message (type %#x)\n", e.hgcm->enmType); + + pThis->m_eventQueue.append(e); + + rc2 = RTCritSectLeave(&pThis->m_eventQueueCS); + AssertRC(rc2); + } + + rc = RTSemEventSignal(pThis->m_hEventSem); + if (RT_FAILURE(rc)) + break; + } + else + { + VBClLogError("Processing next message failed with rc=%Rrc\n", rc); + + /* Old(er) hosts either are broken regarding DnD support or otherwise + * don't support the stuff we do on the guest side, so make sure we + * don't process invalid messages forever. */ + + if (cMsgSkippedInvalid++ > 32) + { + VBClLogError("Too many invalid/skipped messages from host, exiting ...\n"); + break; + } + } + + } while (!ASMAtomicReadBool(&pThis->m_fStop)); + + VBClLogVerbose(2, "HGCM thread ended\n"); + + LogFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Static callback function for X11 message processing thread. All X11 messages + * will be directly routed to the according drag'n drop instance. + * + * @returns IPRT status code. + * @param hThread Thread handle to use. + * @param pvUser Pointer to DragAndDropService instance to use. + */ +/* static */ +DECLCALLBACK(int) DragAndDropService::x11EventThread(RTTHREAD hThread, void *pvUser) +{ + AssertPtrReturn(pvUser, VERR_INVALID_PARAMETER); + DragAndDropService *pThis = static_cast<DragAndDropService*>(pvUser); + AssertPtr(pThis); + + int rc = VINF_SUCCESS; + + /* Note: Nothing to initialize here (yet). */ + + /* Let the service instance know in any case. */ + int rc2 = RTThreadUserSignal(hThread); + AssertRC(rc2); + + VBClLogVerbose(2, "X11 thread started\n"); + + DNDEVENT e; + RT_ZERO(e); + e.enmType = DNDEVENT::DnDEventType_X11; + + do + { + /* + * Wait for new events. We can't use XIfEvent here, cause this locks + * the window connection with a mutex and if no X11 events occurs this + * blocks any other calls we made to X11. So instead check for new + * events and if there are not any new one, sleep for a certain amount + * of time. + */ + unsigned cNewEvents = 0; + unsigned cQueued = XEventsQueued(pThis->m_pDisplay, QueuedAfterFlush); + while (cQueued) + { + /* XNextEvent will block until a new X event becomes available. */ + XNextEvent(pThis->m_pDisplay, &e.x11); + { + rc2 = RTCritSectEnter(&pThis->m_eventQueueCS); + if (RT_SUCCESS(rc2)) + { + LogFlowFunc(("Added new X11 event, type=%d\n", e.x11.type)); + + pThis->m_eventQueue.append(e); + cNewEvents++; + + rc2 = RTCritSectLeave(&pThis->m_eventQueueCS); + AssertRC(rc2); + } + } + + cQueued--; + } + + if (cNewEvents) + { + rc = RTSemEventSignal(pThis->m_hEventSem); + if (RT_FAILURE(rc)) + break; + + continue; + } + + /* No new events; wait a bit. */ + RTThreadSleep(25 /* ms */); + + } while (!ASMAtomicReadBool(&pThis->m_fStop)); + + VBClLogVerbose(2, "X11 thread ended\n"); + + LogFlowFuncLeaveRC(rc); + return rc; +} +/** + * @interface_method_impl{VBCLSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vbclDnDInit(void) +{ + return g_Svc.init(); +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vbclDnDWorker(bool volatile *pfShutdown) +{ + return g_Svc.worker(pfShutdown); +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vbclDnDStop(void) +{ + g_Svc.stop(); +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnTerm} + */ +static DECLCALLBACK(int) vbclDnDTerm(void) +{ + return g_Svc.term(); +} + +VBCLSERVICE g_SvcDragAndDrop = +{ + "dnd", /* szName */ + "Drag'n'Drop", /* pszDescription */ + ".vboxclient-draganddrop", /* pszPidFilePathTemplate */ + NULL, /* pszUsage */ + NULL, /* pszOptions */ + NULL, /* pfnOption */ + vbclDnDInit, /* pfnInit */ + vbclDnDWorker, /* pfnWorker */ + vbclDnDStop, /* pfnStop*/ + vbclDnDTerm /* pfnTerm */ +}; + diff --git a/src/VBox/Additions/x11/VBoxClient/hostversion.cpp b/src/VBox/Additions/x11/VBoxClient/hostversion.cpp new file mode 100644 index 00000000..d2da9094 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/hostversion.cpp @@ -0,0 +1,130 @@ +/* $Id: hostversion.cpp $ */ +/** @file + * X11 guest client - Host version check. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ +#include <stdio.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/ldr.h> +#include <iprt/string.h> +#include <iprt/thread.h> + +#include <VBox/log.h> +#include <VBox/VBoxGuestLib.h> +#ifdef VBOX_OSE +# include <VBox/version.h> +#endif + +#include "VBoxClient.h" + + +/** + * @interface_method_impl{VBCLSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vbclHostVerWorker(bool volatile *pfShutdown) +{ + /** @todo Move this part in VbglR3 and just provide a callback for the platform-specific + notification stuff, since this is very similar to the VBoxTray code. */ + + RT_NOREF(pfShutdown); + + LogFlowFuncEnter(); + + int rc; +#ifdef VBOX_WITH_GUEST_PROPS + uint32_t uGuestPropSvcClientID; + rc = VbglR3GuestPropConnect(&uGuestPropSvcClientID); + if (RT_FAILURE(rc)) + { + VBClLogError("Cannot connect to guest property service while chcking for host version, rc = %Rrc\n", rc); + return rc; + } + + /* Let the main thread know that it can continue spawning services. */ + RTThreadUserSignal(RTThreadSelf()); + + /* Because we need desktop notifications to be displayed, wait + * some time to make the desktop environment load (as a work around). */ + if (g_fDaemonized) + RTThreadSleep(RT_MS_30SEC); + + char *pszHostVersion; + char *pszGuestVersion; + bool fUpdate; + + rc = VbglR3HostVersionCheckForUpdate(uGuestPropSvcClientID, &fUpdate, &pszHostVersion, &pszGuestVersion); + if (RT_SUCCESS(rc)) + { + if (fUpdate) + { + char szMsg[1024]; + char szTitle[64]; + + /** @todo add some translation macros here */ + RTStrPrintf(szTitle, sizeof(szTitle), "VirtualBox Guest Additions update available!"); +# ifndef VBOX_OSE + RTStrPrintf(szMsg, sizeof(szMsg), "Your guest is currently running the Guest Additions version %s. " + "We recommend updating to the latest version (%s) by choosing the " + "install option from the Devices menu.", pszGuestVersion, pszHostVersion); +# else +/* This is the message which appears for non-Oracle builds of the +* Guest Additions. Distributors are encouraged to customise this. */ + RTStrPrintf(szMsg, sizeof(szMsg), "Your virtual machine is currently running the Guest Additions version %s. Since you are running a version of the Guest Additions provided by the operating system you installed in the virtual machine we recommend that you update it to at least version %s using that system's update features, or alternatively that you remove this version and then install the " VBOX_VENDOR_SHORT " Guest Additions package using the install option from the Devices menu. Please consult the documentation for the operating system you are running to find out how to update or remove the current Guest Additions package.", pszGuestVersion, pszHostVersion); +# endif /* VBOX_OSE */ + rc = VBClShowNotify(szTitle, szMsg); + } + + /* Store host version to not notify again */ + int rc2 = VbglR3HostVersionLastCheckedStore(uGuestPropSvcClientID, pszHostVersion); + if (RT_SUCCESS(rc)) + rc = rc2; + + VbglR3GuestPropReadValueFree(pszHostVersion); + VbglR3GuestPropReadValueFree(pszGuestVersion); + } + + VbglR3GuestPropDisconnect(uGuestPropSvcClientID); +#else /* !VBOX_WITH_GUEST_PROPS */ + rc = VERR_NOT_SUPPORTED; +#endif /* VBOX_WITH_GUEST_PROPS */ + + return rc; +} + +VBCLSERVICE g_SvcHostVersion = +{ + "hostversion", /* szName */ + "VirtualBox host version check", /* pszDescription */ + ".vboxclient-hostversion", /* pszPidFilePathTemplate */ + NULL, /* pszUsage */ + NULL, /* pszOptions */ + NULL, /* pfnOption */ + NULL, /* pfnInit */ + vbclHostVerWorker, /* pfnWorker */ + NULL, /* pfnStop*/ + NULL /* pfnTerm */ +}; + diff --git a/src/VBox/Additions/x11/VBoxClient/logging.cpp b/src/VBox/Additions/x11/VBoxClient/logging.cpp new file mode 100644 index 00000000..6591e2fc --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/logging.cpp @@ -0,0 +1,458 @@ +/* $Id: logging.cpp $ */ +/** @file + * VirtualBox Guest Additions - X11 Client. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +#include <sys/wait.h> +#include <stdlib.h> + +#include <iprt/buildconfig.h> +#include <iprt/file.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/system.h> + +#ifdef VBOX_WITH_DBUS +# include <VBox/dbus.h> +#endif +#include <VBox/VBoxGuestLib.h> + +#include <package-generated.h> +#include "VBoxClient.h" + +/** Logging parameters. */ +/** @todo Make this configurable later. */ +static PRTLOGGER g_pLoggerRelease = NULL; +static uint32_t g_cHistory = 10; /* Enable log rotation, 10 files. */ +static uint32_t g_uHistoryFileTime = RT_SEC_1DAY; /* Max 1 day per file. */ +static uint64_t g_uHistoryFileSize = 100 * _1M; /* Max 100MB per file. */ + +/** Custom log prefix (to be set externally). */ +static char *g_pszCustomLogPrefix; + +extern unsigned g_cRespawn; + + +/** + * Fallback notification helper using 'notify-send'. + * + * @returns VBox status code. + * @returns VERR_NOT_SUPPORTED if 'notify-send' is not available, or there was an error while running 'notify-send'. + * @param pszMessage Message to notify desktop environment with. + */ +int vbclNotifyFallbackNotifySend(const char *pszMessage) +{ + AssertPtrReturn(pszMessage, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + if (g_cRespawn == 0) + { + char *pszCommand = RTStrAPrintf2("notify-send \"VBoxClient: %s\"", pszMessage); + if (pszCommand) + { + int status = system(pszCommand); + + RTStrFree(pszCommand); + + if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */ + { + pszCommand = RTStrAPrintf2("xmessage -buttons OK:0 -center \"VBoxClient: %s\"", + pszMessage); + if (pszCommand) + { + status = system(pszCommand); + if (WEXITSTATUS(status) != 0) /* Utility or extension not available. */ + rc = VERR_NOT_SUPPORTED; + + RTStrFree(pszCommand); + } + else + rc = VERR_NO_MEMORY; + } + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + +/** + * Shows a notification on the desktop. + * + * @returns VBox status code. + * @returns VERR_NOT_SUPPORTED if the current desktop environment is not supported. + * @param pszHeader Header text to show. + * @param pszBody Body text to show. + * + * @note How this notification will look like depends on the actual desktop environment implementing + * the actual notification service. Currently only D-BUS-compatible environments are supported. + * + * Most notification implementations have length limits on their header / body texts, so keep + * the text(s) short. + */ +int VBClShowNotify(const char *pszHeader, const char *pszBody) +{ + AssertPtrReturn(pszHeader, VERR_INVALID_POINTER); + AssertPtrReturn(pszBody, VERR_INVALID_POINTER); + + int rc; +# ifdef VBOX_WITH_DBUS + rc = RTDBusLoadLib(); /** @todo Does this init / load the lib only once? Need to check this. */ + if (RT_FAILURE(rc)) + { + VBClLogError("D-Bus seems not to be installed; no desktop notifications available\n"); + return rc; + } + + DBusConnection *conn; + DBusMessage* msg = NULL; + conn = dbus_bus_get(DBUS_BUS_SESSION, NULL); + if (conn == NULL) + { + VBClLogError("Could not retrieve D-BUS session bus\n"); + rc = VERR_INVALID_HANDLE; + } + else + { + msg = dbus_message_new_method_call("org.freedesktop.Notifications", + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "Notify"); + if (msg == NULL) + { + VBClLogError("Could not create D-BUS message!\n"); + rc = VERR_INVALID_HANDLE; + } + else + rc = VINF_SUCCESS; + } + if (RT_SUCCESS(rc)) + { + uint32_t msg_replace_id = 0; + const char *msg_app = "VBoxClient"; + const char *msg_icon = ""; + const char *msg_summary = pszHeader; + const char *msg_body = pszBody; + int32_t msg_timeout = -1; /* Let the notification server decide */ + + DBusMessageIter iter; + DBusMessageIter array; + /*DBusMessageIter dict; - unused */ + /*DBusMessageIter value; - unused */ + /*DBusMessageIter variant; - unused */ + /*DBusMessageIter data; - unused */ + + /* Format: UINT32 org.freedesktop.Notifications.Notify + * (STRING app_name, UINT32 replaces_id, STRING app_icon, STRING summary, STRING body, + * ARRAY actions, DICT hints, INT32 expire_timeout) + */ + dbus_message_iter_init_append(msg,&iter); + dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_app); + dbus_message_iter_append_basic(&iter,DBUS_TYPE_UINT32,&msg_replace_id); + dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_icon); + dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_summary); + dbus_message_iter_append_basic(&iter,DBUS_TYPE_STRING,&msg_body); + dbus_message_iter_open_container(&iter,DBUS_TYPE_ARRAY,DBUS_TYPE_STRING_AS_STRING,&array); + dbus_message_iter_close_container(&iter,&array); + dbus_message_iter_open_container(&iter,DBUS_TYPE_ARRAY,"{sv}",&array); + dbus_message_iter_close_container(&iter,&array); + dbus_message_iter_append_basic(&iter,DBUS_TYPE_INT32,&msg_timeout); + + DBusError err; + dbus_error_init(&err); + + DBusMessage *reply; + reply = dbus_connection_send_with_reply_and_block(conn, msg, 30 * 1000 /* 30 seconds timeout */, &err); + if (dbus_error_is_set(&err)) + VBClLogError("D-BUS returned an error while sending the notification: %s", err.message); + else if (reply) + { + dbus_connection_flush(conn); + dbus_message_unref(reply); + } + if (dbus_error_is_set(&err)) + dbus_error_free(&err); + } + if (msg != NULL) + dbus_message_unref(msg); +# else + /** @todo Implement me */ + RT_NOREF(pszHeader, pszBody); + rc = VERR_NOT_SUPPORTED; +# endif /* VBOX_WITH_DBUS */ + + /* Try to use a fallback if the stuff above fails or is not available. */ + if (RT_FAILURE(rc)) + rc = vbclNotifyFallbackNotifySend(pszBody); + + /* If everything fails, still print out our notification to stdout, in the hope + * someone still gets aware of it. */ + if (RT_FAILURE(rc)) + VBClLogInfo("*** Notification: %s - %s ***\n", pszHeader, pszBody); + + return rc; +} + + + +/** + * Logs a verbose message. + * + * @param pszFormat The message text. + * @param va Format arguments. + */ +static void vbClLogV(const char *pszFormat, va_list va) +{ + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, va); + AssertPtrReturnVoid(psz); + LogRel(("%s", psz)); + RTStrFree(psz); +} + +/** + * Logs a fatal error, notifies the desktop environment via a message and + * exits the application immediately. + * + * @param pszFormat Format string to log. + * @param ... Variable arguments for format string. Optional. + */ +void VBClLogFatalError(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, args); + va_end(args); + + AssertPtrReturnVoid(psz); + LogFunc(("Fatal Error: %s", psz)); + LogRel(("Fatal Error: %s", psz)); + + VBClShowNotify("VBoxClient - Fatal Error", psz); + + RTStrFree(psz); +} + +/** + * Logs an error message to the (release) logging instance. + * + * @param pszFormat Format string to log. + */ +void VBClLogError(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, args); + va_end(args); + + AssertPtrReturnVoid(psz); + LogFunc(("Error: %s", psz)); + LogRel(("Error: %s", psz)); + + RTStrFree(psz); +} + +/** + * Logs an info message to the (release) logging instance. + * + * @param pszFormat Format string to log. + */ +void VBClLogInfo(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + vbClLogV(pszFormat, args); + va_end(args); +} + +/** + * Displays a verbose message based on the currently + * set global verbosity level. + * + * @param iLevel Minimum log level required to display this message. + * @param pszFormat The message text. + * @param ... Format arguments. + */ +void VBClLogVerbose(unsigned iLevel, const char *pszFormat, ...) +{ + if (iLevel <= g_cVerbosity) + { + va_list va; + va_start(va, pszFormat); + vbClLogV(pszFormat, va); + va_end(va); + } +} + +/** + * @callback_method_impl{FNRTLOGPHASE, Release logger callback} + */ +static DECLCALLBACK(void) vbClLogHeaderFooter(PRTLOGGER pLoggerRelease, RTLOGPHASE enmPhase, PFNRTLOGPHASEMSG pfnLog) +{ + /* Some introductory information. */ + static RTTIMESPEC s_TimeSpec; + char szTmp[256]; + if (enmPhase == RTLOGPHASE_BEGIN) + RTTimeNow(&s_TimeSpec); + RTTimeSpecToString(&s_TimeSpec, szTmp, sizeof(szTmp)); + + switch (enmPhase) + { + case RTLOGPHASE_BEGIN: + { + pfnLog(pLoggerRelease, + "VBoxClient %s r%s (verbosity: %u) %s (%s %s) release log\n" + "Log opened %s\n", + RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, VBOX_BUILD_TARGET, + __DATE__, __TIME__, szTmp); + + int vrc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Product: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Release: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Version: %s\n", szTmp); + vrc = RTSystemQueryOSInfo(RTSYSOSINFO_SERVICE_PACK, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(vrc) || vrc == VERR_BUFFER_OVERFLOW) + pfnLog(pLoggerRelease, "OS Service Pack: %s\n", szTmp); + + /* the package type is interesting for Linux distributions */ + char szExecName[RTPATH_MAX]; + char *pszExecName = RTProcGetExecutablePath(szExecName, sizeof(szExecName)); + pfnLog(pLoggerRelease, + "Executable: %s\n" + "Process ID: %u\n" + "Package type: %s" +#ifdef VBOX_OSE + " (OSE)" +#endif + "\n", + pszExecName ? pszExecName : "unknown", + RTProcSelf(), + VBOX_PACKAGE_STRING); + break; + } + + case RTLOGPHASE_PREROTATE: + pfnLog(pLoggerRelease, "Log rotated - Log started %s\n", szTmp); + break; + + case RTLOGPHASE_POSTROTATE: + pfnLog(pLoggerRelease, "Log continuation - Log started %s\n", szTmp); + break; + + case RTLOGPHASE_END: + pfnLog(pLoggerRelease, "End of log file - Log started %s\n", szTmp); + break; + + default: + /* nothing */ + break; + } +} + +static DECLCALLBACK(size_t) vbClLogPrefixCb(PRTLOGGER pLogger, char *pchBuf, size_t cchBuf, void *pvUser) +{ + size_t cbPrefix = 0; + + RT_NOREF(pLogger); + RT_NOREF(pvUser); + + if (g_pszCustomLogPrefix) + { + cbPrefix = RT_MIN(strlen(g_pszCustomLogPrefix), cchBuf); + memcpy(pchBuf, g_pszCustomLogPrefix, cbPrefix); + } + + return cbPrefix; +} + +/** + * Creates the default release logger outputting to the specified file. + * + * Pass NULL to disabled logging. + * + * @return IPRT status code. + * @param pszLogFile Filename for log output. NULL disables custom handling. + */ +int VBClLogCreate(const char *pszLogFile) +{ + if (!pszLogFile) + return VINF_SUCCESS; + + /* Create release logger (stdout + file). */ + static const char * const s_apszGroups[] = VBOX_LOGGROUP_NAMES; + RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME | RTLOGFLAGS_PREFIX_CUSTOM; +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + fFlags |= RTLOGFLAGS_USECRLF; +#endif + int rc = RTLogCreateEx(&g_pLoggerRelease, "VBOXCLIENT_RELEASE_LOG", fFlags, "all", + RT_ELEMENTS(s_apszGroups), s_apszGroups, UINT32_MAX /*cMaxEntriesPerGroup*/, + 0 /*cBufDescs*/, NULL /*paBufDescs*/, RTLOGDEST_STDOUT | RTLOGDEST_USER, + vbClLogHeaderFooter, g_cHistory, g_uHistoryFileSize, g_uHistoryFileTime, + NULL /*pOutputIf*/, NULL /*pvOutputIfUser*/, + NULL /*pErrInfo*/, "%s", pszLogFile ? pszLogFile : ""); + if (RT_SUCCESS(rc)) + { + /* register this logger as the release logger */ + RTLogRelSetDefaultInstance(g_pLoggerRelease); + + rc = RTLogSetCustomPrefixCallback(g_pLoggerRelease, vbClLogPrefixCb, NULL); + if (RT_FAILURE(rc)) + VBClLogError("unable to register custom log prefix callback\n"); + + /* Explicitly flush the log in case of VBOXSERVICE_RELEASE_LOG=buffered. */ + RTLogFlush(g_pLoggerRelease); + } + + return rc; +} + +/** + * Set custom log prefix. + * + * @param pszPrefix Custom log prefix string. + */ +void VBClLogSetLogPrefix(const char *pszPrefix) +{ + g_pszCustomLogPrefix = (char *)pszPrefix; +} + +/** + * Destroys the currently active logging instance. + */ +void VBClLogDestroy(void) +{ + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); +} + diff --git a/src/VBox/Additions/x11/VBoxClient/main.cpp b/src/VBox/Additions/x11/VBoxClient/main.cpp new file mode 100644 index 00000000..f276223a --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/main.cpp @@ -0,0 +1,885 @@ +/* $Id: main.cpp $ */ +/** @file + * VirtualBox Guest Additions - X11 Client. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <sys/wait.h> +#include <stdlib.h> /* For exit */ +#include <signal.h> +#include <X11/Xlib.h> +#include "product-generated.h" +#include <iprt/asm.h> +#include <iprt/buildconfig.h> +#include <iprt/critsect.h> +#include <iprt/errno.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/path.h> +#include <iprt/stream.h> +#include <iprt/env.h> +#include <iprt/process.h> +#include <iprt/linux/sysfs.h> +#include <VBox/VBoxGuestLib.h> +#include <VBox/err.h> +#include <VBox/version.h> +#include "VBoxClient.h" + + +/********************************************************************************************************************************* +* Defines * +*********************************************************************************************************************************/ +#define VBOXCLIENT_OPT_SERVICES 980 +#define VBOXCLIENT_OPT_CHECKHOSTVERSION VBOXCLIENT_OPT_SERVICES +#define VBOXCLIENT_OPT_CLIPBOARD VBOXCLIENT_OPT_SERVICES + 1 +#define VBOXCLIENT_OPT_DRAGANDDROP VBOXCLIENT_OPT_SERVICES + 2 +#define VBOXCLIENT_OPT_SEAMLESS VBOXCLIENT_OPT_SERVICES + 3 +#define VBOXCLIENT_OPT_VMSVGA VBOXCLIENT_OPT_SERVICES + 4 +#define VBOXCLIENT_OPT_VMSVGA_SESSION VBOXCLIENT_OPT_SERVICES + 5 +#define VBOXCLIENT_OPT_DISPLAY VBOXCLIENT_OPT_SERVICES + 6 + + +/********************************************************************************************************************************* +* Local structures * +*********************************************************************************************************************************/ +/** + * The global service state. + */ +typedef struct VBCLSERVICESTATE +{ + /** Pointer to the service descriptor. */ + PVBCLSERVICE pDesc; + /** The worker thread. NIL_RTTHREAD if it's the main thread. */ + RTTHREAD Thread; + /** Whether Pre-init was called. */ + bool fPreInited; + /** Shutdown indicator. */ + bool volatile fShutdown; + /** Indicator set by the service thread exiting. */ + bool volatile fStopped; + /** Whether the service was started or not. */ + bool fStarted; +} VBCLSERVICESTATE; +/** Pointer to a service state. */ +typedef VBCLSERVICESTATE *PVBCLSERVICESTATE; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** The global service state. */ +VBCLSERVICESTATE g_Service = { 0 }; + +/** Set by the signal handler when being called. */ +static volatile bool g_fSignalHandlerCalled = false; +/** Critical section for the signal handler. */ +static RTCRITSECT g_csSignalHandler; +/** Flag indicating Whether the service starts in daemonized mode or not. */ +bool g_fDaemonized = false; +/** The name of our pidfile. It is global for the benefit of the cleanup + * routine. */ +static char g_szPidFile[RTPATH_MAX] = ""; +/** The file handle of our pidfile. It is global for the benefit of the + * cleanup routine. */ +static RTFILE g_hPidFile; +/** The name of pidfile for parent (control) process. */ +static char g_szControlPidFile[RTPATH_MAX] = ""; +/** The file handle of parent process pidfile. */ +static RTFILE g_hControlPidFile; + +/** Global critical section held during the clean-up routine (to prevent it + * being called on multiple threads at once) or things which may not happen + * during clean-up (e.g. pausing and resuming the service). + */ +static RTCRITSECT g_critSect; +/** Counter of how often our daemon has been respawned. */ +unsigned g_cRespawn = 0; +/** Logging verbosity level. */ +unsigned g_cVerbosity = 0; +/** Absolute path to log file, if any. */ +static char g_szLogFile[RTPATH_MAX + 128] = ""; +/** Set by the signal handler when SIGUSR1 received. */ +static volatile bool g_fProcessReloadRequested = false; + +/** + * Tries to determine if the session parenting this process is of Xwayland. + * NB: XDG_SESSION_TYPE is a systemd(1) environment variable and is unlikely + * set in non-systemd environments or remote logins. + * Therefore we check the Wayland specific display environment variable first. + */ +bool VBClHasWayland(void) +{ + const char *const pDisplayType = RTEnvGet(VBCL_ENV_WAYLAND_DISPLAY); + const char *pSessionType; + + if (pDisplayType != NULL) + return true; + + pSessionType = RTEnvGet(VBCL_ENV_XDG_SESSION_TYPE); + if ((pSessionType != NULL) && (RTStrIStartsWith(pSessionType, "wayland"))) + return true; + + return false; +} + +/** + * Shut down if we get a signal or something. + * + * This is extern so that we can call it from other compilation units. + */ +void VBClShutdown(bool fExit /*=true*/) +{ + /* We never release this, as we end up with a call to exit(3) which is not + * async-safe. Unless we fix this application properly, we should be sure + * never to exit from anywhere except from this method. */ + int rc = RTCritSectEnter(&g_critSect); + if (RT_FAILURE(rc)) + VBClLogFatalError("Failure while acquiring the global critical section, rc=%Rrc\n", rc); + + /* Ask service to stop. */ + if (g_Service.pDesc && + g_Service.pDesc->pfnStop) + { + ASMAtomicWriteBool(&g_Service.fShutdown, true); + g_Service.pDesc->pfnStop(); + + } + + if (g_szPidFile[0] && g_hPidFile) + VbglR3ClosePidFile(g_szPidFile, g_hPidFile); + + VBClLogDestroy(); + + if (fExit) + exit(RTEXITCODE_SUCCESS); +} + +/** + * Xlib error handler for certain errors that we can't avoid. + */ +static int vboxClientXLibErrorHandler(Display *pDisplay, XErrorEvent *pError) +{ + char errorText[1024]; + + XGetErrorText(pDisplay, pError->error_code, errorText, sizeof(errorText)); + VBClLogError("An X Window protocol error occurred: %s (error code %d). Request code: %d, minor code: %d, serial number: %d\n", errorText, pError->error_code, pError->request_code, pError->minor_code, pError->serial); + return 0; +} + +/** + * Xlib error handler for fatal errors. This often means that the programme is still running + * when X exits. + */ +static int vboxClientXLibIOErrorHandler(Display *pDisplay) +{ + RT_NOREF1(pDisplay); + VBClLogError("A fatal guest X Window error occurred. This may just mean that the Window system was shut down while the client was still running\n"); + VBClShutdown(); + return 0; /* We should never reach this. */ +} + +/** + * A standard signal handler which cleans up and exits. + */ +static void vboxClientSignalHandler(int iSignal) +{ + int rc = RTCritSectEnter(&g_csSignalHandler); + if (RT_SUCCESS(rc)) + { + if (g_fSignalHandlerCalled) + { + RTCritSectLeave(&g_csSignalHandler); + return; + } + + VBClLogVerbose(2, "Received signal %d\n", iSignal); + g_fSignalHandlerCalled = true; + + /* In our internal convention, when VBoxClient process receives SIGUSR1, + * this is a trigger for restarting a process with exec() call. Usually + * happens as a result of Guest Additions update in order to seamlessly + * restart newly installed binaries. */ + if (iSignal == SIGUSR1) + g_fProcessReloadRequested = true; + + /* Leave critical section before stopping the service. */ + RTCritSectLeave(&g_csSignalHandler); + + if ( g_Service.pDesc + && g_Service.pDesc->pfnStop) + { + VBClLogVerbose(2, "Notifying service to stop ...\n"); + + /* Signal the service to stop. */ + ASMAtomicWriteBool(&g_Service.fShutdown, true); + + g_Service.pDesc->pfnStop(); + + VBClLogVerbose(2, "Service notified to stop, waiting on worker thread to stop ...\n"); + } + } +} + +/** + * Reset all standard termination signals to call our signal handler. + */ +static int vboxClientSignalHandlerInstall(void) +{ + struct sigaction sigAction; + sigAction.sa_handler = vboxClientSignalHandler; + sigemptyset(&sigAction.sa_mask); + sigAction.sa_flags = 0; + sigaction(SIGHUP, &sigAction, NULL); + sigaction(SIGINT, &sigAction, NULL); + sigaction(SIGQUIT, &sigAction, NULL); + sigaction(SIGPIPE, &sigAction, NULL); + sigaction(SIGALRM, &sigAction, NULL); + sigaction(SIGTERM, &sigAction, NULL); + sigaction(SIGUSR1, &sigAction, NULL); + sigaction(SIGUSR2, &sigAction, NULL); + + return RTCritSectInit(&g_csSignalHandler); +} + +/** + * Uninstalls a previously installed signal handler. + */ +static int vboxClientSignalHandlerUninstall(void) +{ + signal(SIGTERM, SIG_DFL); +#ifdef SIGBREAK + signal(SIGBREAK, SIG_DFL); +#endif + + return RTCritSectDelete(&g_csSignalHandler); +} + +/** + * Print out a usage message and exit with success. + */ +static void vboxClientUsage(const char *pcszFileName) +{ + RTPrintf(VBOX_PRODUCT " VBoxClient " + VBOX_VERSION_STRING "\n" + "Copyright (C) 2005-" VBOX_C_YEAR " " VBOX_VENDOR "\n\n"); + + RTPrintf("Usage: %s " +#ifdef VBOX_WITH_SHARED_CLIPBOARD + "--clipboard|" +#endif +#ifdef VBOX_WITH_DRAG_AND_DROP + "--draganddrop|" +#endif +#ifdef VBOX_WITH_GUEST_PROPS + "--checkhostversion|" +#endif +#ifdef VBOX_WITH_SEAMLESS + "--seamless|" +#endif +#ifdef VBOX_WITH_VMSVGA + "--vmsvga|" + "--vmsvga-session" +#endif + "\n[-d|--nodaemon]\n", pcszFileName); + RTPrintf("\n"); + RTPrintf("Options:\n"); +#ifdef VBOX_WITH_SHARED_CLIPBOARD + RTPrintf(" --clipboard starts the shared clipboard service\n"); +#endif +#ifdef VBOX_WITH_DRAG_AND_DROP + RTPrintf(" --draganddrop starts the drag and drop service\n"); +#endif +#ifdef VBOX_WITH_GUEST_PROPS + RTPrintf(" --checkhostversion starts the host version notifier service\n"); +#endif +#ifdef VBOX_WITH_SEAMLESS + RTPrintf(" --seamless starts the seamless windows service\n"); +#endif +#ifdef VBOX_WITH_VMSVGA + RTPrintf(" --vmsvga starts VMSVGA dynamic resizing for X11/Wayland guests\n"); +#ifdef RT_OS_LINUX + RTPrintf(" --vmsvga-session starts Desktop Environment specific screen assistant for X11/Wayland guests\n" + " (VMSVGA graphics adapter only)\n"); +#else + RTPrintf(" --vmsvga-session an alias for --vmsvga\n"); +#endif + RTPrintf(" --display starts VMSVGA dynamic resizing for legacy guests\n"); +#endif + RTPrintf(" -f, --foreground run in the foreground (no daemonizing)\n"); + RTPrintf(" -d, --nodaemon continues running as a system service\n"); + RTPrintf(" -h, --help shows this help text\n"); + RTPrintf(" -l, --logfile <path> enables logging to a file\n"); + RTPrintf(" -v, --verbose increases logging verbosity level\n"); + RTPrintf(" -V, --version shows version information\n"); + RTPrintf("\n"); +} + +/** + * Complains about seeing more than one service specification. + * + * @returns RTEXITCODE_SYNTAX. + */ +static int vbclSyntaxOnlyOneService(void) +{ + RTMsgError("More than one service specified! Only one, please."); + return RTEXITCODE_SYNTAX; +} + +/** + * The service thread. + * + * @returns Whatever the worker function returns. + * @param ThreadSelf My thread handle. + * @param pvUser The service index. + */ +static DECLCALLBACK(int) vbclThread(RTTHREAD ThreadSelf, void *pvUser) +{ + PVBCLSERVICESTATE pState = (PVBCLSERVICESTATE)pvUser; + AssertPtrReturn(pState, VERR_INVALID_POINTER); + +#ifndef RT_OS_WINDOWS + /* + * Block all signals for this thread. Only the main thread will handle signals. + */ + sigset_t signalMask; + sigfillset(&signalMask); + pthread_sigmask(SIG_BLOCK, &signalMask, NULL); +#endif + + AssertPtrReturn(pState->pDesc->pfnWorker, VERR_INVALID_POINTER); + int rc = pState->pDesc->pfnWorker(&pState->fShutdown); + + VBClLogVerbose(2, "Worker loop ended with %Rrc\n", rc); + + ASMAtomicXchgBool(&pState->fShutdown, true); + RTThreadUserSignal(ThreadSelf); + return rc; +} + +/** + * Wait for SIGUSR1 and re-exec. + */ +static void vbclHandleUpdateStarted(char *const argv[]) +{ + /* Context of parent process */ + sigset_t signalMask; + int iSignal; + int rc; + + /* Release reference to guest driver. */ + VbglR3Term(); + + sigemptyset(&signalMask); + sigaddset(&signalMask, SIGUSR1); + rc = pthread_sigmask(SIG_BLOCK, &signalMask, NULL); + + if (rc == 0) + { + LogRel(("%s: waiting for Guest Additions installation to be completed\n", + g_Service.pDesc->pszDesc)); + + /* Wait for SIGUSR1. */ + rc = sigwait(&signalMask, &iSignal); + if (rc == 0) + { + LogRel(("%s: Guest Additions installation to be completed, reloading service\n", + g_Service.pDesc->pszDesc)); + + /* Release pidfile, otherwise new VBoxClient instance won't be able to quire it. */ + VBClShutdown(false); + + rc = RTProcCreate(argv[0], argv, RTENV_DEFAULT, + RTPROC_FLAGS_DETACHED | RTPROC_FLAGS_SEARCH_PATH, NULL); + if (RT_SUCCESS(rc)) + LogRel(("%s: service restarted\n", g_Service.pDesc->pszDesc)); + else + LogRel(("%s: cannot replace running image with %s (%s), automatic service reloading has failed\n", + g_Service.pDesc->pszDesc, argv[0], strerror(errno))); + } + else + LogRel(("%s: cannot wait for signal (%s), automatic service reloading has failed\n", + g_Service.pDesc->pszDesc, strerror(errno))); + } + else + LogRel(("%s: failed to setup signal handler, automatic service reloading has failed\n", + g_Service.pDesc->pszDesc)); + + exit(RT_BOOL(rc != 0)); +} + +/** + * Compose pidfile name. + * + * @returns IPRT status code. + * @param szBuf Buffer to store pidfile name into. + * @param cbBuf Size of buffer. + * @param szTemplate Null-terminated string which contains pidfile name. + * @param fParentProcess Whether pidfile path should be composed for + * parent (control) process or for a child (actual + * service) process. + */ +static int vbclGetPidfileName(char *szBuf, size_t cbBuf, const char *szTemplate, + bool fParentProcess) +{ + int rc; + char pszActiveTTY[128]; + size_t cchRead; + + RT_ZERO(pszActiveTTY); + + AssertPtrReturn(szBuf, VERR_INVALID_PARAMETER); + AssertReturn(cbBuf > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(szTemplate, VERR_INVALID_PARAMETER); + + rc = RTPathUserHome(szBuf, cbBuf); + if (RT_FAILURE(rc)) + VBClLogFatalError("%s: getting home directory failed: %Rrc\n", + g_Service.pDesc->pszDesc, rc); + + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szBuf, cbBuf, szTemplate); + +#ifdef RT_OS_LINUX + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadStrFile(pszActiveTTY, sizeof(pszActiveTTY) - 1 /* reserve last byte for string termination */, + &cchRead, "class/tty/tty0/active"); + if (RT_SUCCESS(rc)) + { + RTStrCat(szBuf, cbBuf, "-"); + RTStrCat(szBuf, cbBuf, pszActiveTTY); + } + else + VBClLogInfo("%s: cannot detect currently active tty device, " + "multiple service instances for a single user will not be allowed, rc=%Rrc", + g_Service.pDesc->pszDesc, rc); +#endif /* RT_OS_LINUX */ + + if (RT_SUCCESS(rc)) + RTStrCat(szBuf, cbBuf, fParentProcess ? "-control.pid" : "-service.pid"); + + if (RT_FAILURE(rc)) + VBClLogFatalError("%s: reating PID file path failed: %Rrc\n", + g_Service.pDesc->pszDesc, rc); + + return rc; +} + +/** + * The main loop for the VBoxClient daemon. + */ +int main(int argc, char *argv[]) +{ + /* Note: No VBClLogXXX calls before actually creating the log. */ + + /* Initialize our runtime before all else. */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* A flag which is returned to the parent process when Guest Additions update started. */ + bool fUpdateStarted = false; + + /* This should never be called twice in one process - in fact one Display + * object should probably never be used from multiple threads anyway. */ + if (!XInitThreads()) + return RTMsgErrorExitFailure("Failed to initialize X11 threads\n"); + + /* Get our file name for usage info and hints. */ + const char *pcszFileName = RTPathFilename(argv[0]); + if (!pcszFileName) + pcszFileName = "VBoxClient"; + + /* Parse our option(s). */ + static const RTGETOPTDEF s_aOptions[] = + { + { "--nodaemon", 'd', RTGETOPT_REQ_NOTHING }, + { "--foreground", 'f', RTGETOPT_REQ_NOTHING }, + { "--help", 'h', RTGETOPT_REQ_NOTHING }, + { "--logfile", 'l', RTGETOPT_REQ_STRING }, + { "--version", 'V', RTGETOPT_REQ_NOTHING }, + { "--verbose", 'v', RTGETOPT_REQ_NOTHING }, + + /* Services */ +#ifdef VBOX_WITH_GUEST_PROPS + { "--checkhostversion", VBOXCLIENT_OPT_CHECKHOSTVERSION, RTGETOPT_REQ_NOTHING }, +#endif +#ifdef VBOX_WITH_SHARED_CLIPBOARD + { "--clipboard", VBOXCLIENT_OPT_CLIPBOARD, RTGETOPT_REQ_NOTHING }, +#endif +#ifdef VBOX_WITH_DRAG_AND_DROP + { "--draganddrop", VBOXCLIENT_OPT_DRAGANDDROP, RTGETOPT_REQ_NOTHING }, +#endif +#ifdef VBOX_WITH_SEAMLESS + { "--seamless", VBOXCLIENT_OPT_SEAMLESS, RTGETOPT_REQ_NOTHING }, +#endif +#ifdef VBOX_WITH_VMSVGA + { "--vmsvga", VBOXCLIENT_OPT_VMSVGA, RTGETOPT_REQ_NOTHING }, + { "--vmsvga-session", VBOXCLIENT_OPT_VMSVGA_SESSION, RTGETOPT_REQ_NOTHING }, + { "--display", VBOXCLIENT_OPT_DISPLAY, RTGETOPT_REQ_NOTHING }, +#endif + }; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + rc = RTGetOptInit(&GetState, argc, argv, s_aOptions, RT_ELEMENTS(s_aOptions), 0, 0 /* fFlags */); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to parse command line options, rc=%Rrc\n", rc); + + AssertRC(rc); + + bool fDaemonise = true; + bool fRespawn = true; + + while ((ch = RTGetOpt(&GetState, &ValueUnion)) != 0) + { + /* For options that require an argument, ValueUnion has received the value. */ + switch (ch) + { + case 'd': + { + fDaemonise = false; + break; + } + + case 'h': + { + vboxClientUsage(pcszFileName); + return RTEXITCODE_SUCCESS; + } + + case 'f': + { + fDaemonise = false; + fRespawn = false; + break; + } + + case 'l': + { + rc = RTStrCopy(g_szLogFile, sizeof(g_szLogFile), ValueUnion.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Unable to set log file path, rc=%Rrc\n", rc); + break; + } + + case 'n': + { + fRespawn = false; + break; + } + + case 'v': + { + g_cVerbosity++; + break; + } + + case 'V': + { + RTPrintf("%sr%s\n", RTBldCfgVersion(), RTBldCfgRevisionStr()); + return RTEXITCODE_SUCCESS; + } + + /* Services */ +#ifdef VBOX_WITH_GUEST_PROPS + case VBOXCLIENT_OPT_CHECKHOSTVERSION: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcHostVersion; + break; + } +#endif +#ifdef VBOX_WITH_SHARED_CLIPBOARD + case VBOXCLIENT_OPT_CLIPBOARD: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcClipboard; + break; + } +#endif +#ifdef VBOX_WITH_DRAG_AND_DROP + case VBOXCLIENT_OPT_DRAGANDDROP: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcDragAndDrop; + break; + } +#endif +#ifdef VBOX_WITH_SEAMLESS + case VBOXCLIENT_OPT_SEAMLESS: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcSeamless; + break; + } +#endif +#ifdef VBOX_WITH_VMSVGA + case VBOXCLIENT_OPT_VMSVGA: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcDisplaySVGA; + break; + } + + case VBOXCLIENT_OPT_VMSVGA_SESSION: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); +# ifdef RT_OS_LINUX + g_Service.pDesc = &g_SvcDisplaySVGASession; +# else + g_Service.pDesc = &g_SvcDisplaySVGA; +# endif + break; + } + + case VBOXCLIENT_OPT_DISPLAY: + { + if (g_Service.pDesc) + return vbclSyntaxOnlyOneService(); + g_Service.pDesc = &g_SvcDisplayLegacy; + break; + } +#endif + case VINF_GETOPT_NOT_OPTION: + break; + + case VERR_GETOPT_UNKNOWN_OPTION: + RT_FALL_THROUGH(); + default: + { + if ( g_Service.pDesc + && g_Service.pDesc->pfnOption) + { + rc = g_Service.pDesc->pfnOption(NULL, argc, argv, &GetState.iNext); + } + else /* No service specified yet. */ + rc = VERR_NOT_FOUND; + + if (RT_FAILURE(rc)) + { + RTMsgError("unrecognized option '%s'", ValueUnion.psz); + RTMsgInfo("Try '%s --help' for more information", pcszFileName); + return RTEXITCODE_SYNTAX; + } + break; + } + + } /* switch */ + } /* while RTGetOpt */ + + if (!g_Service.pDesc) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "No service specified. Quitting because nothing to do!"); + + /* Initialize VbglR3 before we do anything else with the logger. */ + rc = VbglR3InitUser(); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("VbglR3InitUser failed: %Rrc", rc); + + rc = VBClLogCreate(g_szLogFile[0] ? g_szLogFile : ""); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to create release log '%s', rc=%Rrc\n", + g_szLogFile[0] ? g_szLogFile : "<None>", rc); + + if (!fDaemonise) + { + /* If the user is running in "no daemon" mode, send critical logging to stdout as well. */ + PRTLOGGER pReleaseLog = RTLogRelGetDefaultInstance(); + if (pReleaseLog) + { + rc = RTLogDestinations(pReleaseLog, "stdout"); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("Failed to redivert error output, rc=%Rrc", rc); + } + } + + VBClLogInfo("VBoxClient %s r%s started. Verbose level = %d. Wayland environment detected: %s\n", + RTBldCfgVersion(), RTBldCfgRevisionStr(), g_cVerbosity, VBClHasWayland() ? "yes" : "no"); + VBClLogInfo("Service: %s\n", g_Service.pDesc->pszDesc); + + rc = RTCritSectInit(&g_critSect); + if (RT_FAILURE(rc)) + VBClLogFatalError("Initializing critical section failed: %Rrc\n", rc); + if (g_Service.pDesc->pszPidFilePathTemplate) + { + /* Get pidfile name for parent (control) process. */ + rc = vbclGetPidfileName(g_szControlPidFile, sizeof(g_szControlPidFile), g_Service.pDesc->pszPidFilePathTemplate, true); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + /* Get pidfile name for service process. */ + rc = vbclGetPidfileName(g_szPidFile, sizeof(g_szPidFile), g_Service.pDesc->pszPidFilePathTemplate, false); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + } + + if (fDaemonise) + { + rc = VbglR3DaemonizeEx(false /* fNoChDir */, false /* fNoClose */, fRespawn, &g_cRespawn, + true /* fReturnOnUpdate */, &fUpdateStarted, g_szControlPidFile, &g_hControlPidFile); + /* This combination only works in context of parent process. */ + if (RT_SUCCESS(rc) && fUpdateStarted) + vbclHandleUpdateStarted(argv); + } + + if (RT_FAILURE(rc)) + VBClLogFatalError("Daemonizing service failed: %Rrc\n", rc); + + if (g_szPidFile[0]) + { + rc = VbglR3PidFile(g_szPidFile, &g_hPidFile); + if (rc == VERR_FILE_LOCK_VIOLATION) /* Already running. */ + { + VBClLogInfo("%s: service already running, exitting\n", + g_Service.pDesc->pszDesc); + return RTEXITCODE_SUCCESS; + } + if (RT_FAILURE(rc)) + { + VBClLogFatalError("Creating PID file %s failed: %Rrc\n", g_szPidFile, rc); + return RTEXITCODE_FAILURE; + } + } + +#ifndef VBOXCLIENT_WITHOUT_X11 + /* Set an X11 error handler, so that we don't die when we get unavoidable + * errors. */ + XSetErrorHandler(vboxClientXLibErrorHandler); + /* Set an X11 I/O error handler, so that we can shutdown properly on + * fatal errors. */ + XSetIOErrorHandler(vboxClientXLibIOErrorHandler); +#endif + + bool fSignalHandlerInstalled = false; + if (RT_SUCCESS(rc)) + { + rc = vboxClientSignalHandlerInstall(); + if (RT_SUCCESS(rc)) + fSignalHandlerInstalled = true; + } + + if ( RT_SUCCESS(rc) + && g_Service.pDesc->pfnInit) + { + VBClLogInfo("Initializing service ...\n"); + rc = g_Service.pDesc->pfnInit(); + } + + if (RT_SUCCESS(rc)) + { + VBClLogInfo("Creating worker thread ...\n"); + rc = RTThreadCreate(&g_Service.Thread, vbclThread, (void *)&g_Service, 0, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, g_Service.pDesc->pszName); + if (RT_FAILURE(rc)) + { + VBClLogError("Creating worker thread failed, rc=%Rrc\n", rc); + } + else + { + g_Service.fStarted = true; + + /* Wait for the thread to initialize. */ + /** @todo There is a race between waiting and checking + * the fShutdown flag of a thread here and processing + * the thread's actual worker loop. If the thread decides + * to exit the loop before we skipped the fShutdown check + * below the service will fail to start! */ + /** @todo This presumably means either a one-shot service or that + * something has gone wrong. In the second case treating it as failure + * to start is probably right, so we need a way to signal the first + * rather than leaving the idle thread hanging around. A flag in the + * service description? */ + RTThreadUserWait(g_Service.Thread, RT_MS_1MIN); + if (g_Service.fShutdown) + { + VBClLogError("Service failed to start!\n"); + rc = VERR_GENERAL_FAILURE; + } + else + { + VBClLogInfo("Service started\n"); + + int rcThread; + rc = RTThreadWait(g_Service.Thread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rc)) + rc = rcThread; + + if (RT_FAILURE(rc)) + VBClLogError("Waiting on worker thread to stop failed, rc=%Rrc\n", rc); + + if (g_Service.pDesc->pfnTerm) + { + VBClLogInfo("Terminating service\n"); + + int rc2 = g_Service.pDesc->pfnTerm(); + if (RT_SUCCESS(rc)) + rc = rc2; + + if (RT_SUCCESS(rc)) + { + VBClLogInfo("Service terminated\n"); + } + else + VBClLogError("Service failed to terminate, rc=%Rrc\n", rc); + } + } + } + } + + if (RT_FAILURE(rc)) + { + if (rc == VERR_NOT_AVAILABLE) + VBClLogInfo("Service is not availabe, skipping\n"); + else if (rc == VERR_NOT_SUPPORTED) + VBClLogInfo("Service is not supported on this platform, skipping\n"); + else + VBClLogError("Service ended with error %Rrc\n", rc); + } + else + VBClLogVerbose(2, "Service ended\n"); + + if (fSignalHandlerInstalled) + { + int rc2 = vboxClientSignalHandlerUninstall(); + AssertRC(rc2); + } + + VBClShutdown(false /*fExit*/); + + /** @todo r=andy Should we return an appropriate exit code if the service failed to init? + * Must be tested carefully with our init scripts first. */ + return g_fProcessReloadRequested ? VBGLR3EXITCODERELOAD : RTEXITCODE_SUCCESS; +} + diff --git a/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp b/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp new file mode 100644 index 00000000..e4a88e7d --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/seamless-x11.cpp @@ -0,0 +1,568 @@ +/* $Id: seamless-x11.cpp $ */ +/** @file + * X11 Seamless mode. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header files * +*********************************************************************************************************************************/ + +#include <iprt/errcore.h> +#include <iprt/assert.h> +#include <iprt/vector.h> +#include <iprt/thread.h> +#include <VBox/log.h> + +#include "seamless-x11.h" +#include "VBoxClient.h" + +#include <X11/Xatom.h> +#include <X11/Xmu/WinUtil.h> + +#include <limits.h> + +#ifdef TESTCASE +#undef DefaultRootWindow +#define DefaultRootWindow XDefaultRootWindow +#endif + +/***************************************************************************** +* Static functions * +*****************************************************************************/ + +static unsigned char *XXGetProperty(Display *aDpy, Window aWnd, Atom aPropType, + const char *aPropName, unsigned long *nItems) +{ + LogRelFlowFuncEnter(); + Atom propNameAtom = XInternAtom (aDpy, aPropName, + True /* only_if_exists */); + if (propNameAtom == None) + { + return NULL; + } + + Atom actTypeAtom = None; + int actFmt = 0; + unsigned long nBytesAfter = 0; + unsigned char *propVal = 0; + int rc = XGetWindowProperty (aDpy, aWnd, propNameAtom, + 0, LONG_MAX, False /* delete */, + aPropType, &actTypeAtom, &actFmt, + nItems, &nBytesAfter, &propVal); + if (rc != Success) + return NULL; + + LogRelFlowFuncLeave(); + return propVal; +} + +int SeamlessX11::init(PFNSENDREGIONUPDATE pHostCallback) +{ + int rc = VINF_SUCCESS; + + LogRelFlowFuncEnter(); + if (mHostCallback != NULL) /* Assertion */ + { + VBClLogError("Attempting to initialise seamless guest object twice!\n"); + return VERR_INTERNAL_ERROR; + } + if (!(mDisplay = XOpenDisplay(NULL))) + { + VBClLogError("Seamless guest object failed to acquire a connection to the display\n"); + return VERR_ACCESS_DENIED; + } + mHostCallback = pHostCallback; + mEnabled = false; + unmonitorClientList(); + LogRelFlowFuncLeaveRC(rc); + return rc; +} + +/** + * Shutdown seamless event monitoring. + */ +void SeamlessX11::uninit(void) +{ + if (mHostCallback) + stop(); + mHostCallback = NULL; + + /* Before closing a Display, make sure X11 is still running. The indicator + * that is when XOpenDisplay() returns non NULL. If it is not a + * case, XCloseDisplay() will hang on internal X11 mutex forever. */ + Display *pDisplay = XOpenDisplay(NULL); + if (pDisplay) + { + XCloseDisplay(pDisplay); + if (mDisplay) + { + XCloseDisplay(mDisplay); + mDisplay = NULL; + } + } + + if (mpRects) + { + RTMemFree(mpRects); + mpRects = NULL; + } +} + +/** + * Read information about currently visible windows in the guest and subscribe to X11 + * events about changes to this information. + * + * @note This class does not contain its own event thread, so an external thread must + * call nextConfigurationEvent() for as long as events are wished. + * @todo This function should switch the guest to fullscreen mode. + */ +int SeamlessX11::start(void) +{ + int rc = VINF_SUCCESS; + /** Dummy values for XShapeQueryExtension */ + int error, event; + + LogRelFlowFuncEnter(); + if (mEnabled) + return VINF_SUCCESS; + mSupportsShape = XShapeQueryExtension(mDisplay, &event, &error); + mEnabled = true; + monitorClientList(); + rebuildWindowTree(); + LogRelFlowFuncLeaveRC(rc); + return rc; +} + +/** Stop reporting seamless events to the host. Free information about guest windows + and stop requesting updates. */ +void SeamlessX11::stop(void) +{ + LogRelFlowFuncEnter(); + if (!mEnabled) + return; + mEnabled = false; + unmonitorClientList(); + freeWindowTree(); + LogRelFlowFuncLeave(); +} + +void SeamlessX11::monitorClientList(void) +{ + LogRelFlowFuncEnter(); + XSelectInput(mDisplay, DefaultRootWindow(mDisplay), PropertyChangeMask | SubstructureNotifyMask); +} + +void SeamlessX11::unmonitorClientList(void) +{ + LogRelFlowFuncEnter(); + XSelectInput(mDisplay, DefaultRootWindow(mDisplay), PropertyChangeMask); +} + +/** + * Recreate the table of toplevel windows of clients on the default root window of the + * X server. + */ +void SeamlessX11::rebuildWindowTree(void) +{ + LogRelFlowFuncEnter(); + freeWindowTree(); + addClients(DefaultRootWindow(mDisplay)); + mChanged = true; +} + + +/** + * Look at the list of children of a virtual root window and add them to the list of clients + * if they belong to a client which is not a virtual root. + * + * @param hRoot the virtual root window to be examined + */ +void SeamlessX11::addClients(const Window hRoot) +{ + /** Unused out parameters of XQueryTree */ + Window hRealRoot, hParent; + /** The list of children of the root supplied, raw pointer */ + Window *phChildrenRaw = NULL; + /** The list of children of the root supplied, auto-pointer */ + Window *phChildren; + /** The number of children of the root supplied */ + unsigned cChildren; + + LogRelFlowFuncEnter(); + if (!XQueryTree(mDisplay, hRoot, &hRealRoot, &hParent, &phChildrenRaw, &cChildren)) + return; + phChildren = phChildrenRaw; + for (unsigned i = 0; i < cChildren; ++i) + addClientWindow(phChildren[i]); + XFree(phChildrenRaw); + LogRelFlowFuncLeave(); +} + + +void SeamlessX11::addClientWindow(const Window hWin) +{ + LogRelFlowFuncEnter(); + XWindowAttributes winAttrib; + bool fAddWin = true; + Window hClient = XmuClientWindow(mDisplay, hWin); + + if (isVirtualRoot(hClient)) + fAddWin = false; + if (fAddWin && !XGetWindowAttributes(mDisplay, hWin, &winAttrib)) + { + VBClLogError("Failed to get the window attributes for window %d\n", hWin); + fAddWin = false; + } + if (fAddWin && (winAttrib.map_state == IsUnmapped)) + fAddWin = false; + XSizeHints dummyHints; + long dummyLong; + /* Apparently (?) some old kwin versions had unwanted client windows + * without normal hints. */ + if (fAddWin && (!XGetWMNormalHints(mDisplay, hClient, &dummyHints, + &dummyLong))) + { + LogRelFlowFunc(("window %lu, client window %lu has no size hints\n", hWin, hClient)); + fAddWin = false; + } + if (fAddWin) + { + XRectangle *pRects = NULL; + int cRects = 0, iOrdering; + bool hasShape = false; + + LogRelFlowFunc(("adding window %lu, client window %lu\n", hWin, + hClient)); + if (mSupportsShape) + { + XShapeSelectInput(mDisplay, hWin, ShapeNotifyMask); + pRects = XShapeGetRectangles(mDisplay, hWin, ShapeBounding, &cRects, &iOrdering); + if (!pRects) + cRects = 0; + else + { + if ( (cRects > 1) + || (pRects[0].x != 0) + || (pRects[0].y != 0) + || (pRects[0].width != winAttrib.width) + || (pRects[0].height != winAttrib.height) + ) + hasShape = true; + } + } + mGuestWindows.addWindow(hWin, hasShape, winAttrib.x, winAttrib.y, + winAttrib.width, winAttrib.height, cRects, + pRects); + } + LogRelFlowFuncLeave(); +} + + +/** + * Checks whether a window is a virtual root. + * @returns true if it is, false otherwise + * @param hWin the window to be examined + */ +bool SeamlessX11::isVirtualRoot(Window hWin) +{ + unsigned char *windowTypeRaw = NULL; + Atom *windowType; + unsigned long ulCount; + bool rc = false; + + LogRelFlowFuncEnter(); + windowTypeRaw = XXGetProperty(mDisplay, hWin, XA_ATOM, WM_TYPE_PROP, &ulCount); + if (windowTypeRaw != NULL) + { + windowType = (Atom *)(windowTypeRaw); + if ( (ulCount != 0) + && (*windowType == XInternAtom(mDisplay, WM_TYPE_DESKTOP_PROP, True))) + rc = true; + } + if (windowTypeRaw) + XFree(windowTypeRaw); + LogRelFlowFunc(("returning %RTbool\n", rc)); + return rc; +} + +DECLCALLBACK(int) VBoxGuestWinFree(VBoxGuestWinInfo *pInfo, void *pvParam) +{ + Display *pDisplay = (Display *)pvParam; + + XShapeSelectInput(pDisplay, pInfo->Core.Key, 0); + delete pInfo; + return VINF_SUCCESS; +} + +/** + * Free all information in the tree of visible windows + */ +void SeamlessX11::freeWindowTree(void) +{ + /* We use post-increment in the operation to prevent the iterator from being invalidated. */ + LogRelFlowFuncEnter(); + mGuestWindows.detachAll(VBoxGuestWinFree, mDisplay); + LogRelFlowFuncLeave(); +} + + +/** + * Waits for a position or shape-related event from guest windows + * + * @note Called from the guest event thread. + */ +void SeamlessX11::nextConfigurationEvent(void) +{ + XEvent event; + + LogRelFlowFuncEnter(); + /* Start by sending information about the current window setup to the host. We do this + here because we want to send all such information from a single thread. */ + if (mChanged && mEnabled) + { + updateRects(); + mHostCallback(mpRects, mcRects); + } + mChanged = false; + + if (XPending(mDisplay) > 0) + { + /* We execute this even when seamless is disabled, as it also waits for + * enable and disable notification. */ + XNextEvent(mDisplay, &event); + } else + { + /* This function is called in a loop by upper layer. In order to + * prevent CPU spinning, sleep a bit before returning. */ + RTThreadSleep(300 /* ms */); + return; + } + + if (!mEnabled) + return; + switch (event.type) + { + case ConfigureNotify: + { + XConfigureEvent *pConf = &event.xconfigure; + LogRelFlowFunc(("configure event, window=%lu, x=%i, y=%i, w=%i, h=%i, send_event=%RTbool\n", + (unsigned long) pConf->window, (int) pConf->x, + (int) pConf->y, (int) pConf->width, + (int) pConf->height, pConf->send_event)); + } + doConfigureEvent(event.xconfigure.window); + break; + case MapNotify: + LogRelFlowFunc(("map event, window=%lu, send_event=%RTbool\n", + (unsigned long) event.xmap.window, + event.xmap.send_event)); + rebuildWindowTree(); + break; + case PropertyNotify: + if ( event.xproperty.atom != XInternAtom(mDisplay, "_NET_CLIENT_LIST", True /* only_if_exists */) + || event.xproperty.window != DefaultRootWindow(mDisplay)) + break; + LogRelFlowFunc(("_NET_CLIENT_LIST property event on root window\n")); + rebuildWindowTree(); + break; + case VBoxShapeNotify: /* This is defined wrong in my X11 header files! */ + LogRelFlowFunc(("shape event, window=%lu, send_event=%RTbool\n", + (unsigned long) event.xany.window, + event.xany.send_event)); + /* the window member in xany is in the same place as in the shape event */ + doShapeEvent(event.xany.window); + break; + case UnmapNotify: + LogRelFlowFunc(("unmap event, window=%lu, send_event=%RTbool\n", + (unsigned long) event.xunmap.window, + event.xunmap.send_event)); + rebuildWindowTree(); + break; + default: + break; + } + LogRelFlowFunc(("processed event\n")); +} + +/** + * Handle a configuration event in the seamless event thread by setting the new position. + * + * @param hWin the window to be examined + */ +void SeamlessX11::doConfigureEvent(Window hWin) +{ + VBoxGuestWinInfo *pInfo = mGuestWindows.find(hWin); + if (pInfo) + { + XWindowAttributes winAttrib; + + if (!XGetWindowAttributes(mDisplay, hWin, &winAttrib)) + return; + pInfo->mX = winAttrib.x; + pInfo->mY = winAttrib.y; + pInfo->mWidth = winAttrib.width; + pInfo->mHeight = winAttrib.height; + mChanged = true; + } +} + +/** + * Handle a window shape change event in the seamless event thread. + * + * @param hWin the window to be examined + */ +void SeamlessX11::doShapeEvent(Window hWin) +{ + LogRelFlowFuncEnter(); + VBoxGuestWinInfo *pInfo = mGuestWindows.find(hWin); + if (pInfo) + { + XRectangle *pRects; + int cRects = 0, iOrdering; + + pRects = XShapeGetRectangles(mDisplay, hWin, ShapeBounding, &cRects, + &iOrdering); + if (!pRects) + cRects = 0; + pInfo->mhasShape = true; + if (pInfo->mpRects) + XFree(pInfo->mpRects); + pInfo->mcRects = cRects; + pInfo->mpRects = pRects; + mChanged = true; + } + LogRelFlowFuncLeave(); +} + +/** + * Gets the list of visible rectangles + */ +RTRECT *SeamlessX11::getRects(void) +{ + return mpRects; +} + +/** + * Gets the number of rectangles in the visible rectangle list + */ +size_t SeamlessX11::getRectCount(void) +{ + return mcRects; +} + +RTVEC_DECL(RectList, RTRECT) + +static DECLCALLBACK(int) getRectsCallback(VBoxGuestWinInfo *pInfo, struct RectList *pRects) +{ + if (pInfo->mhasShape) + { + for (int i = 0; i < pInfo->mcRects; ++i) + { + RTRECT *pRect; + + pRect = RectListPushBack(pRects); + if (!pRect) + return VERR_NO_MEMORY; + pRect->xLeft = pInfo->mX + + pInfo->mpRects[i].x; + pRect->yBottom = pInfo->mY + + pInfo->mpRects[i].y + + pInfo->mpRects[i].height; + pRect->xRight = pInfo->mX + + pInfo->mpRects[i].x + + pInfo->mpRects[i].width; + pRect->yTop = pInfo->mY + + pInfo->mpRects[i].y; + } + } + else + { + RTRECT *pRect; + + pRect = RectListPushBack(pRects); + if (!pRect) + return VERR_NO_MEMORY; + pRect->xLeft = pInfo->mX; + pRect->yBottom = pInfo->mY + + pInfo->mHeight; + pRect->xRight = pInfo->mX + + pInfo->mWidth; + pRect->yTop = pInfo->mY; + } + return VINF_SUCCESS; +} + +/** + * Updates the list of seamless rectangles + */ +int SeamlessX11::updateRects(void) +{ + LogRelFlowFuncEnter(); + struct RectList rects = RTVEC_INITIALIZER; + + if (mcRects != 0) + { + int rc = RectListReserve(&rects, mcRects * 2); + if (RT_FAILURE(rc)) + return rc; + } + mGuestWindows.doWithAll((PFNVBOXGUESTWINCALLBACK)getRectsCallback, &rects); + if (mpRects) + RTMemFree(mpRects); + mcRects = RectListSize(&rects); + mpRects = RectListDetach(&rects); + LogRelFlowFuncLeave(); + return VINF_SUCCESS; +} + +/** + * Send a client event to wake up the X11 seamless event loop prior to stopping it. + * + * @note This function should only be called from the host event thread. + */ +bool SeamlessX11::interruptEventWait(void) +{ + bool rc = false; + Display *pDisplay = XOpenDisplay(NULL); + + LogRelFlowFuncEnter(); + if (pDisplay == NULL) + { + VBClLogError("Failed to open X11 display\n"); + return false; + } + + /* Message contents set to zero. */ + XClientMessageEvent clientMessage = + { ClientMessage, 0, 0, 0, 0, XInternAtom(pDisplay, "VBOX_CLIENT_SEAMLESS_HEARTBEAT", false), 8 }; + + if (XSendEvent(pDisplay, DefaultRootWindow(mDisplay), false, + PropertyChangeMask, (XEvent *)&clientMessage)) + rc = true; + XCloseDisplay(pDisplay); + LogRelFlowFunc(("returning %RTbool\n", rc)); + return rc; +} diff --git a/src/VBox/Additions/x11/VBoxClient/seamless-x11.h b/src/VBox/Additions/x11/VBoxClient/seamless-x11.h new file mode 100644 index 00000000..f8ab185d --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/seamless-x11.h @@ -0,0 +1,275 @@ +/* $Id: seamless-x11.h $ */ +/** @file + * Seamless mode - X11 guests. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_x11_VBoxClient_seamless_x11_h +#define GA_INCLUDED_SRC_x11_VBoxClient_seamless_x11_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/log.h> +#include <iprt/avl.h> +#ifdef RT_NEED_NEW_AND_DELETE +# include <iprt/mem.h> +# include <new> +#endif + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/extensions/shape.h> + +#define WM_TYPE_PROP "_NET_WM_WINDOW_TYPE" +#define WM_TYPE_DESKTOP_PROP "_NET_WM_WINDOW_TYPE_DESKTOP" + +/* This is defined wrong in my X11 header files! */ +#define VBoxShapeNotify 64 + +/** + * Callback which provides the interface for notifying the host of changes to + * the X11 window configuration, mainly split out from @a VBoxGuestSeamlessHost + * to simplify the unit test. + */ +typedef void FNSENDREGIONUPDATE(RTRECT *pRects, size_t cRects); +typedef FNSENDREGIONUPDATE *PFNSENDREGIONUPDATE; + +/** Structure containing information about a guest window's position and visible area. + Used inside of VBoxGuestWindowList. */ +struct VBoxGuestWinInfo +{ +public: + /** Header structure for insertion into an AVL tree */ + AVLU32NODECORE Core; + /** Is the window currently mapped? */ + bool mhasShape; + /** Co-ordinates in the guest screen. */ + int mX, mY; + /** Window dimensions. */ + int mWidth, mHeight; + /** Number of rectangles used to represent the visible area. */ + int mcRects; + /** Rectangles representing the visible area. These must be allocated + * by XMalloc and will be freed automatically if non-null when the class + * is destroyed. */ + XRectangle *mpRects; + /** Constructor. */ + VBoxGuestWinInfo(bool hasShape, int x, int y, int w, int h, int cRects, XRectangle *pRects) + : mhasShape(hasShape), mX(x), mY(y), mWidth(w), mHeight(h) + , mcRects(cRects), mpRects(pRects) + {} + + /** Destructor */ + ~VBoxGuestWinInfo() + { + if (mpRects) + XFree(mpRects); + } +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#endif + +private: + // We don't want a copy constructor or assignment operator + VBoxGuestWinInfo(const VBoxGuestWinInfo &); + VBoxGuestWinInfo &operator=(const VBoxGuestWinInfo &); +}; + +/** Callback type used for "DoWithAll" calls */ +typedef DECLCALLBACKTYPE(int, FNVBOXGUESTWINCALLBACK,(VBoxGuestWinInfo *, void *)); +/** Pointer to VBOXGUESTWINCALLBACK */ +typedef FNVBOXGUESTWINCALLBACK *PFNVBOXGUESTWINCALLBACK; + +static inline DECLCALLBACK(int) VBoxGuestWinCleanup(VBoxGuestWinInfo *pInfo, void *) +{ + delete pInfo; + return VINF_SUCCESS; +} + +/** + * This class is just a wrapper around a map of structures containing + * information about the windows on the guest system. It has a function for + * adding a structure (see addWindow) and one for removing it by window + * handle (see removeWindow). + */ +class VBoxGuestWindowList +{ +private: + // We don't want a copy constructor or an assignment operator + VBoxGuestWindowList(const VBoxGuestWindowList&); + VBoxGuestWindowList& operator=(const VBoxGuestWindowList&); + + // Private class members + AVLU32TREE mWindows; + +public: + // Constructor + VBoxGuestWindowList(void) : mWindows(NULL) {} + // Destructor + ~VBoxGuestWindowList() + { + /** @todo having this inside the container class hard codes that the + * elements have to be allocated with the "new" operator, and + * I don't see a need to require this. */ + doWithAll(VBoxGuestWinCleanup, NULL); + } + +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#endif + + // Standard operations + VBoxGuestWinInfo *find(Window hWin) + { + return (VBoxGuestWinInfo *)RTAvlU32Get(&mWindows, hWin); + } + + void detachAll(PFNVBOXGUESTWINCALLBACK pfnCallback, void *pvParam) + { + RTAvlU32Destroy(&mWindows, (PAVLU32CALLBACK)pfnCallback, pvParam); + } + + int doWithAll(PFNVBOXGUESTWINCALLBACK pfnCallback, void *pvParam) + { + return RTAvlU32DoWithAll(&mWindows, 1, (PAVLU32CALLBACK)pfnCallback, pvParam); + } + + bool addWindow(Window hWin, bool isMapped, int x, int y, int w, int h, int cRects, + XRectangle *pRects) + { + LogRelFlowFunc(("hWin=%lu, isMapped=%RTbool, x=%d, y=%d, w=%d, h=%d, cRects=%d\n", + (unsigned long) hWin, isMapped, x, y, w, h, cRects)); + VBoxGuestWinInfo *pInfo = new VBoxGuestWinInfo(isMapped, x, y, w, h, cRects, pRects); + pInfo->Core.Key = hWin; + LogRelFlowFuncLeave(); + return RTAvlU32Insert(&mWindows, &pInfo->Core); + } + + VBoxGuestWinInfo *removeWindow(Window hWin) + { + LogRelFlowFuncEnter(); + return (VBoxGuestWinInfo *)RTAvlU32Remove(&mWindows, hWin); + } +}; + +class SeamlessX11 +{ +private: + // We don't want a copy constructor or assignment operator + SeamlessX11(const SeamlessX11&); + SeamlessX11& operator=(const SeamlessX11&); + + // Private member variables + /** Pointer to the host callback. */ + PFNSENDREGIONUPDATE mHostCallback; + /** Our connection to the X11 display we are running on. */ + Display *mDisplay; + /** Class to keep track of visible guest windows. */ + VBoxGuestWindowList mGuestWindows; + /** The current set of seamless rectangles. */ + RTRECT *mpRects; + /** The current number of seamless rectangles. */ + int mcRects; + /** Do we support the X shaped window extension? */ + bool mSupportsShape; + /** Is seamless mode currently enabled? */ + bool mEnabled; + /** Have there been changes since the last time we sent a notification? */ + bool mChanged; + + // Private methods + + // Methods to manage guest window information + /** + * Store information about a desktop window and register for structure events on it. + * If it is mapped, go through the list of it's children and add information about + * mapped children to the tree of visible windows, making sure that those windows are + * not already in our list of desktop windows. + * + * @param hWin the window concerned - should be a "desktop" window + */ + void monitorClientList(void); + void unmonitorClientList(void); + void rebuildWindowTree(void); + void addClients(const Window hRoot); + bool isVirtualRoot(Window hWin); + void addClientWindow(Window hWin); + void freeWindowTree(void); + void updateHostSeamlessInfo(void); + int updateRects(void); + +public: + /** + * Initialise the guest and ensure that it is capable of handling seamless mode + * @param pHostCallback Host interface callback to notify of window configuration + * changes. + * + * @returns iprt status code + */ + int init(PFNSENDREGIONUPDATE pHostCallback); + + /** + * Shutdown seamless event monitoring. + */ + void uninit(void); + + /** + * Initialise seamless event reporting in the guest. + * + * @returns IPRT status code + */ + int start(void); + /** Stop reporting seamless events. */ + void stop(void); + /** Get the current list of visible rectangles. */ + RTRECT *getRects(void); + /** Get the number of visible rectangles in the current list */ + size_t getRectCount(void); + + /** Process next event in the guest event queue - called by the event thread. */ + void nextConfigurationEvent(void); + /** Wake up the event thread if it is waiting for an event so that it can exit. */ + bool interruptEventWait(void); + + /* Methods to handle X11 events. These are public so that the unit test + * can call them. */ + void doConfigureEvent(Window hWin); + void doShapeEvent(Window hWin); + + SeamlessX11(void) + : mHostCallback(NULL), mDisplay(NULL), mpRects(NULL), mcRects(0), + mSupportsShape(false), mEnabled(false), mChanged(false) {} + + ~SeamlessX11() + { + uninit(); + } + +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#endif +}; + +#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_seamless_x11_h */ diff --git a/src/VBox/Additions/x11/VBoxClient/seamless.cpp b/src/VBox/Additions/x11/VBoxClient/seamless.cpp new file mode 100644 index 00000000..365b31f6 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/seamless.cpp @@ -0,0 +1,360 @@ +/* $Id: seamless.cpp $ */ +/** @file + * X11 Guest client - seamless mode: main logic, communication with the host and + * wrapper interface for the main code of the VBoxClient deamon. The + * X11-specific parts are split out into their own file for ease of testing. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header files * +*********************************************************************************************************************************/ +#include <new> + +#include <X11/Xlib.h> + +#include <iprt/asm.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> + +#include <VBox/log.h> +#include <VBox/VBoxGuestLib.h> + +#include "VBoxClient.h" +#include "seamless.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** + * Struct for keeping a service instance. + */ +struct SEAMLESSSERVICE +{ + /** Seamless service object. */ + SeamlessMain mSeamless; +}; + +/** Service instance data. */ +static SEAMLESSSERVICE g_Svc; + + +SeamlessMain::SeamlessMain(void) +{ + mX11MonitorThread = NIL_RTTHREAD; + mX11MonitorThreadStopping = false; + + mMode = VMMDev_Seamless_Disabled; + mfPaused = true; +} + +SeamlessMain::~SeamlessMain() +{ + /* Stopping will be done via main.cpp. */ +} + +/** + * Update the set of visible rectangles in the host. + */ +static void sendRegionUpdate(RTRECT *pRects, size_t cRects) +{ + if ( cRects + && !pRects) /* Assertion */ + { + VBClLogError(("Region update called with NULL pointer\n")); + return; + } + VbglR3SeamlessSendRects(cRects, pRects); +} + +/** @copydoc VBCLSERVICE::pfnInit */ +int SeamlessMain::init(void) +{ + int rc; + const char *pcszStage; + + do + { + pcszStage = "Connecting to the X server"; + rc = mX11Monitor.init(sendRegionUpdate); + if (RT_FAILURE(rc)) + break; + pcszStage = "Setting guest IRQ filter mask"; + rc = VbglR3CtlFilterMask(VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST, 0); + if (RT_FAILURE(rc)) + break; + pcszStage = "Reporting support for seamless capability"; + rc = VbglR3SeamlessSetCap(true); + if (RT_FAILURE(rc)) + break; + rc = startX11MonitorThread(); + if (RT_FAILURE(rc)) + break; + + } while(0); + + if (RT_FAILURE(rc)) + VBClLogError("Failed to start in stage '%s' -- error %Rrc\n", pcszStage, rc); + + return rc; +} + +/** @copydoc VBCLSERVICE::pfnWorker */ +int SeamlessMain::worker(bool volatile *pfShutdown) +{ + int rc = VINF_SUCCESS; + + /* Let the main thread know that it can continue spawning services. */ + RTThreadUserSignal(RTThreadSelf()); + + /* This will only exit if something goes wrong. */ + for (;;) + { + if (ASMAtomicReadBool(pfShutdown)) + break; + + rc = nextStateChangeEvent(); + + if (rc == VERR_TRY_AGAIN) + rc = VINF_SUCCESS; + + if (RT_FAILURE(rc)) + break; + + if (ASMAtomicReadBool(pfShutdown)) + break; + + /* If we are not stopping, sleep for a bit to avoid using up too + much CPU while retrying. */ + RTThreadYield(); + } + + return rc; +} + +/** @copydoc VBCLSERVICE::pfnStop */ +void SeamlessMain::stop(void) +{ + VbglR3SeamlessSetCap(false); + VbglR3CtlFilterMask(0, VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST); + stopX11MonitorThread(); +} + +/** @copydoc VBCLSERVICE::pfnTerm */ +int SeamlessMain::term(void) +{ + mX11Monitor.uninit(); + return VINF_SUCCESS; +} + +/** + * Waits for a seamless state change events from the host and dispatch it. + * + * @returns VBox return code, or + * VERR_TRY_AGAIN if no new status is available and we have to try it again + * at some later point in time. + */ +int SeamlessMain::nextStateChangeEvent(void) +{ + VMMDevSeamlessMode newMode = VMMDev_Seamless_Disabled; + + int rc = VbglR3SeamlessWaitEvent(&newMode); + if (RT_SUCCESS(rc)) + { + mMode = newMode; + switch (newMode) + { + case VMMDev_Seamless_Visible_Region: + /* A simplified seamless mode, obtained by making the host VM window + * borderless and making the guest desktop transparent. */ + VBClLogVerbose(2, "\"Visible region\" mode requested\n"); + break; + case VMMDev_Seamless_Disabled: + VBClLogVerbose(2, "\"Disabled\" mode requested\n"); + break; + case VMMDev_Seamless_Host_Window: + /* One host window represents one guest window. Not yet implemented. */ + VBClLogVerbose(2, "Unsupported \"host window\" mode requested\n"); + return VERR_NOT_SUPPORTED; + default: + VBClLogError("Unsupported mode %d requested\n", newMode); + return VERR_NOT_SUPPORTED; + } + } + if ( RT_SUCCESS(rc) + || rc == VERR_TRY_AGAIN) + { + if (mMode == VMMDev_Seamless_Visible_Region) + mfPaused = false; + else + mfPaused = true; + mX11Monitor.interruptEventWait(); + } + else + VBClLogError("VbglR3SeamlessWaitEvent returned %Rrc\n", rc); + + return rc; +} + +/** + * The actual X11 window configuration change monitor thread function. + */ +int SeamlessMain::x11MonitorThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + + SeamlessMain *pThis = (SeamlessMain *)pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + RTThreadUserSignal(hThreadSelf); + + VBClLogVerbose(2, "X11 monitor thread started\n"); + + while (!pThis->mX11MonitorThreadStopping) + { + if (!pThis->mfPaused) + { + rc = pThis->mX11Monitor.start(); + if (RT_FAILURE(rc)) + VBClLogFatalError("Failed to change the X11 seamless service state, mfPaused=%RTbool, rc=%Rrc\n", + pThis->mfPaused, rc); + } + + pThis->mX11Monitor.nextConfigurationEvent(); + + if ( pThis->mfPaused + || pThis->mX11MonitorThreadStopping) + { + pThis->mX11Monitor.stop(); + } + } + + VBClLogVerbose(2, "X11 monitor thread ended\n"); + + return rc; +} + +/** + * Start the X11 window configuration change monitor thread. + */ +int SeamlessMain::startX11MonitorThread(void) +{ + mX11MonitorThreadStopping = false; + + if (isX11MonitorThreadRunning()) + return VINF_SUCCESS; + + int rc = RTThreadCreate(&mX11MonitorThread, x11MonitorThread, this, 0, + RTTHREADTYPE_MSG_PUMP, RTTHREADFLAGS_WAITABLE, + "seamless x11"); + if (RT_SUCCESS(rc)) + rc = RTThreadUserWait(mX11MonitorThread, RT_MS_30SEC); + + if (RT_FAILURE(rc)) + VBClLogError("Failed to start X11 monitor thread, rc=%Rrc\n", rc); + + return rc; +} + +/** + * Stops the monitor thread. + */ +int SeamlessMain::stopX11MonitorThread(void) +{ + if (!isX11MonitorThreadRunning()) + return VINF_SUCCESS; + + mX11MonitorThreadStopping = true; + if (!mX11Monitor.interruptEventWait()) + { + VBClLogError("Unable to notify X11 monitor thread\n"); + return VERR_INVALID_STATE; + } + + int rcThread; + int rc = RTThreadWait(mX11MonitorThread, RT_MS_30SEC, &rcThread); + if (RT_SUCCESS(rc)) + rc = rcThread; + + if (RT_SUCCESS(rc)) + { + mX11MonitorThread = NIL_RTTHREAD; + } + else + VBClLogError("Waiting for X11 monitor thread to stop failed, rc=%Rrc\n", rc); + + return rc; +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnInit} + */ +static DECLCALLBACK(int) vbclSeamlessInit(void) +{ + return g_Svc.mSeamless.init(); +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnWorker} + */ +static DECLCALLBACK(int) vbclSeamlessWorker(bool volatile *pfShutdown) +{ + return g_Svc.mSeamless.worker(pfShutdown); +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnStop} + */ +static DECLCALLBACK(void) vbclSeamlessStop(void) +{ + return g_Svc.mSeamless.stop(); +} + +/** + * @interface_method_impl{VBCLSERVICE,pfnTerm} + */ +static DECLCALLBACK(int) vbclSeamlessTerm(void) +{ + return g_Svc.mSeamless.term(); +} + +VBCLSERVICE g_SvcSeamless = +{ + "seamless", /* szName */ + "Seamless Mode Support", /* pszDescription */ + ".vboxclient-seamless", /* pszPidFilePathTemplate */ + NULL, /* pszUsage */ + NULL, /* pszOptions */ + NULL, /* pfnOption */ + vbclSeamlessInit, /* pfnInit */ + vbclSeamlessWorker, /* pfnWorker */ + vbclSeamlessStop, /* pfnStop*/ + vbclSeamlessTerm /* pfnTerm */ +}; + diff --git a/src/VBox/Additions/x11/VBoxClient/seamless.h b/src/VBox/Additions/x11/VBoxClient/seamless.h new file mode 100644 index 00000000..3e9041a8 --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/seamless.h @@ -0,0 +1,109 @@ +/* $Id: seamless.h $ */ +/** @file + * X11 Guest client - seamless mode, missing proper description while using the + * potentially confusing word 'host'. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef GA_INCLUDED_SRC_x11_VBoxClient_seamless_h +#define GA_INCLUDED_SRC_x11_VBoxClient_seamless_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/thread.h> + +#include <VBox/log.h> +#include <VBox/VBoxGuestLib.h> /* for the R3 guest library functions */ + +#include "seamless-x11.h" + +/** + * Interface to the host + */ +class SeamlessMain +{ +private: + // We don't want a copy constructor or assignment operator + SeamlessMain(const SeamlessMain&); + SeamlessMain& operator=(const SeamlessMain&); + + /** X11 event monitor object */ + SeamlessX11 mX11Monitor; + + /** Thread to start and stop when we enter and leave seamless mode which + * monitors X11 windows in the guest. */ + RTTHREAD mX11MonitorThread; + /** Should the X11 monitor thread be stopping? */ + volatile bool mX11MonitorThreadStopping; + + /** The current seamless mode we are in. */ + VMMDevSeamlessMode mMode; + /** Is the service currently paused? */ + volatile bool mfPaused; + + /** + * Waits for a seamless state change events from the host and dispatch it. This is + * meant to be called by the host event monitor thread exclusively. + * + * @returns IRPT return code. + */ + int nextStateChangeEvent(void); + + /** Thread function to monitor X11 window configuration changes. */ + static DECLCALLBACK(int) x11MonitorThread(RTTHREAD self, void *pvUser); + + /** Helper to start the X11 monitor thread. */ + int startX11MonitorThread(void); + + /** Helper to stop the X11 monitor thread again. */ + int stopX11MonitorThread(void); + + /** Is the service currently actively monitoring X11 windows? */ + bool isX11MonitorThreadRunning() + { + return mX11MonitorThread != NIL_RTTHREAD; + } + +public: + SeamlessMain(void); + virtual ~SeamlessMain(); +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#endif + + /** @copydoc VBCLSERVICE::pfnInit */ + int init(void); + + /** @copydoc VBCLSERVICE::pfnWorker */ + int worker(bool volatile *pfShutdown); + + /** @copydoc VBCLSERVICE::pfnStop */ + void stop(void); + + /** @copydoc VBCLSERVICE::pfnTerm */ + int term(void); +}; + +#endif /* !GA_INCLUDED_SRC_x11_VBoxClient_seamless_h */ diff --git a/src/VBox/Additions/x11/VBoxClient/testcase/Makefile.kup b/src/VBox/Additions/x11/VBoxClient/testcase/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/testcase/Makefile.kup diff --git a/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp new file mode 100644 index 00000000..0a90684d --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11-auto.cpp @@ -0,0 +1,791 @@ +/* $Id: tstSeamlessX11-auto.cpp $ */ +/** @file + * Automated test of the X11 seamless Additions code. + * @todo Better separate test data from implementation details! + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <stdlib.h> /* exit() */ + +#include <X11/Xatom.h> +#include <X11/Xmu/WinUtil.h> + +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/semaphore.h> +#include <iprt/stream.h> +#include <iprt/string.h> + +#include "../seamless.h" + +#undef DefaultRootWindow + +/****************************************************** +* Mock X11 functions needed by the seamless X11 class * +******************************************************/ + +int XFree(void *data) +{ + RTMemFree(data); + return 0; +} + +#define TEST_DISPLAY ((Display *)0xffff) +#define TEST_ROOT ((Window)1) + +void VBClLogError(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, args); + va_end(args); + + AssertPtr(psz); + RTPrintf("Error: %s", psz); + + RTStrFree(psz); +} + +/** Exit with a fatal error. */ +void VBClLogFatalError(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, args); + va_end(args); + + AssertPtr(psz); + RTPrintf("Fatal error: %s", psz); + + RTStrFree(psz); + + exit(1); +} + +extern "C" Display *XOpenDisplay(const char *display_name); +Display *XOpenDisplay(const char *display_name) +{ + RT_NOREF1(display_name); + return TEST_DISPLAY; +} + +extern "C" int XCloseDisplay(Display *display); +int XCloseDisplay(Display *display) +{ + RT_NOREF1(display); + Assert(display == TEST_DISPLAY); + return 0; +} + +enum +{ + ATOM_PROP = 1, + ATOM_DESKTOP_PROP +}; + +extern "C" Atom XInternAtom(Display *display, const char *atom_name, Bool only_if_exists); +Atom XInternAtom(Display *display, const char *atom_name, Bool only_if_exists) +{ + RT_NOREF2(only_if_exists, display); + Assert(display == TEST_DISPLAY); + if (!RTStrCmp(atom_name, WM_TYPE_PROP)) + return (Atom) ATOM_PROP; + if (!RTStrCmp(atom_name, WM_TYPE_DESKTOP_PROP)) + return (Atom) ATOM_DESKTOP_PROP; + AssertFailed(); + return (Atom)0; +} + +/** The window (if any) on which the WM_TYPE_PROP property is set to the + * WM_TYPE_DESKTOP_PROP atom. */ +static Window g_hSmlsDesktopWindow = 0; + +extern "C" int XGetWindowProperty(Display *display, Window w, Atom property, + long long_offset, long long_length, + Bool delProp, Atom req_type, + Atom *actual_type_return, + int *actual_format_return, + unsigned long *nitems_return, + unsigned long *bytes_after_return, + unsigned char **prop_return); +int XGetWindowProperty(Display *display, Window w, Atom property, + long long_offset, long long_length, Bool delProp, + Atom req_type, Atom *actual_type_return, + int *actual_format_return, + unsigned long *nitems_return, + unsigned long *bytes_after_return, + unsigned char **prop_return) +{ + RT_NOREF2(display, long_length); + Assert(display == TEST_DISPLAY); + Atom atomType = XInternAtom (display, WM_TYPE_PROP, true); + Atom atomTypeDesktop = XInternAtom (display, WM_TYPE_DESKTOP_PROP, true); + /* We only handle things we expect. */ + AssertReturn((req_type == XA_ATOM) || (req_type == AnyPropertyType), + 0xffff); + AssertReturn(property == atomType, 0xffff); + *actual_type_return = XA_ATOM; + *actual_format_return = sizeof(Atom) * 8; + *nitems_return = 0; + *bytes_after_return = sizeof(Atom); + *prop_return = NULL; + if ((w != g_hSmlsDesktopWindow) || (g_hSmlsDesktopWindow == 0)) + return Success; + AssertReturn(long_offset == 0, 0); + AssertReturn(delProp == false, 0); + unsigned char *pProp; + pProp = (unsigned char *)RTMemDup(&atomTypeDesktop, + sizeof(atomTypeDesktop)); + AssertReturn(pProp, 0xffff); + *nitems_return = 1; + *prop_return = pProp; + *bytes_after_return = 0; + return 0; +} + +#if 0 /* unused */ +/** Sets the current set of properties for all mock X11 windows */ +static void smlsSetDesktopWindow(Window hWin) +{ + g_hSmlsDesktopWindow = hWin; +} +#endif + +extern "C" Bool XShapeQueryExtension(Display *dpy, int *event_basep, int *error_basep); +Bool XShapeQueryExtension(Display *dpy, int *event_basep, int *error_basep) +{ + RT_NOREF3(dpy, event_basep, error_basep); + Assert(dpy == TEST_DISPLAY); + return true; +} + +/* We silently ignore this for now. */ +extern "C" int XSelectInput(Display *display, Window w, long event_mask); +int XSelectInput(Display *display, Window w, long event_mask) +{ + RT_NOREF3(display, w, event_mask); + Assert(display == TEST_DISPLAY); + return 0; +} + +/* We silently ignore this for now. */ +extern "C" void XShapeSelectInput(Display *display, Window w, unsigned long event_mask); +void XShapeSelectInput(Display *display, Window w, unsigned long event_mask) +{ + RT_NOREF3(display, w, event_mask); + Assert(display == TEST_DISPLAY); +} + +extern "C" Window XDefaultRootWindow(Display *display); +Window XDefaultRootWindow(Display *display) +{ + RT_NOREF1(display); + Assert(display == TEST_DISPLAY); + return TEST_ROOT; +} + +static unsigned g_cSmlsWindows = 0; +static Window *g_paSmlsWindows = NULL; +static XWindowAttributes *g_paSmlsWinAttribs = NULL; +static const char **g_papszSmlsWinNames = NULL; + +extern "C" Status XQueryTree(Display *display, Window w, Window *root_return, + Window *parent_return, Window **children_return, + unsigned int *nchildren_return); +Status XQueryTree(Display *display, Window w, Window *root_return, + Window *parent_return, Window **children_return, + unsigned int *nchildren_return) +{ + RT_NOREF1(display); + Assert(display == TEST_DISPLAY); + AssertReturn(w == TEST_ROOT, False); /* We support nothing else */ + AssertPtrReturn(children_return, False); + AssertReturn(g_paSmlsWindows, False); + if (root_return) + *root_return = TEST_ROOT; + if (parent_return) + *parent_return = TEST_ROOT; + *children_return = (Window *)RTMemDup(g_paSmlsWindows, + g_cSmlsWindows * sizeof(Window)); + if (nchildren_return) + *nchildren_return = g_cSmlsWindows; + return (g_cSmlsWindows != 0); +} + +extern "C" Window XmuClientWindow(Display *dpy, Window win); +Window XmuClientWindow(Display *dpy, Window win) +{ + RT_NOREF1(dpy); + Assert(dpy == TEST_DISPLAY); + return win; +} + +extern "C" Status XGetWindowAttributes(Display *display, Window w, + XWindowAttributes *window_attributes_return); +Status XGetWindowAttributes(Display *display, Window w, + XWindowAttributes *window_attributes_return) +{ + RT_NOREF1(display); + Assert(display == TEST_DISPLAY); + AssertPtrReturn(window_attributes_return, 1); + for (unsigned i = 0; i < g_cSmlsWindows; ++i) + if (g_paSmlsWindows[i] == w) + { + *window_attributes_return = g_paSmlsWinAttribs[i]; + return 1; + } + return 0; +} + +extern "C" Status XGetWMNormalHints(Display *display, Window w, + XSizeHints *hints_return, + long *supplied_return); + +Status XGetWMNormalHints(Display *display, Window w, + XSizeHints *hints_return, long *supplied_return) +{ + RT_NOREF4(display, w, hints_return, supplied_return); + Assert(display == TEST_DISPLAY); + return 1; +} + +static void smlsSetWindowAttributes(XWindowAttributes *pAttribs, + Window *pWindows, unsigned cAttribs, + const char **paNames) +{ + g_paSmlsWinAttribs = pAttribs; + g_paSmlsWindows = pWindows; + g_cSmlsWindows = cAttribs; + g_papszSmlsWinNames = paNames; +} + +static Window g_SmlsShapedWindow = 0; +static int g_cSmlsShapeRectangles = 0; +static XRectangle *g_pSmlsShapeRectangles = NULL; + +extern "C" XRectangle *XShapeGetRectangles (Display *dpy, Window window, + int kind, int *count, + int *ordering); +XRectangle *XShapeGetRectangles (Display *dpy, Window window, int kind, + int *count, int *ordering) +{ + RT_NOREF2(dpy, kind); + Assert(dpy == TEST_DISPLAY); + if ((window != g_SmlsShapedWindow) || (window == 0)) + return NULL; /* Probably not correct, but works for us. */ + *count = g_cSmlsShapeRectangles; + *ordering = 0; + return (XRectangle *)RTMemDup(g_pSmlsShapeRectangles, + sizeof(XRectangle) + * g_cSmlsShapeRectangles); +} + +static void smlsSetShapeRectangles(Window window, int cRects, + XRectangle *pRects) +{ + g_SmlsShapedWindow = window; + g_cSmlsShapeRectangles = cRects; + g_pSmlsShapeRectangles = pRects; +} + +static int g_SmlsEventType = 0; +static Window g_SmlsEventWindow = 0; + +/* This should not be needed in the bits of the code we test. */ +extern "C" int XNextEvent(Display *display, XEvent *event_return); +int XNextEvent(Display *display, XEvent *event_return) +{ + RT_NOREF1(display); + Assert(display == TEST_DISPLAY); + event_return->xany.type = g_SmlsEventType; + event_return->xany.window = g_SmlsEventWindow; + event_return->xmap.window = g_SmlsEventWindow; + return True; +} + +/* Mock XPending(): this also should not be needed. Just in case, always + * return that at least one event is pending to be processed. */ +extern "C" int XPending(Display *display); +int XPending(Display *display) +{ + RT_NOREF1(display); + return 1; +} + +static void smlsSetNextEvent(int type, Window window) +{ + g_SmlsEventType = type; + g_SmlsEventWindow = window; +} + +/* This should not be needed in the bits of the code we test. */ +extern "C" Status XSendEvent(Display *display, Window w, Bool propagate, + long event_mask, XEvent *event_send); +Status XSendEvent(Display *display, Window w, Bool propagate, + long event_mask, XEvent *event_send) +{ + RT_NOREF5(display, w, propagate, event_mask, event_send); + Assert(display == TEST_DISPLAY); + AssertFailedReturn(0); +} + +/* This should not be needed in the bits of the code we test. */ +extern "C" int XFlush(Display *display); +int XFlush(Display *display) +{ + RT_NOREF1(display); + Assert(display == TEST_DISPLAY); + AssertFailedReturn(0); +} + +/** Global "received a notification" flag. */ +static bool g_fNotified = false; + +/** Dummy host call-back. */ +static void sendRegionUpdate(RTRECT *pRects, size_t cRects) +{ + RT_NOREF2(pRects, cRects); + g_fNotified = true; +} + +static bool gotNotification(void) +{ + if (!g_fNotified) + return false; + g_fNotified = false; + return true; +} + +/***************************** +* The actual tests to be run * +*****************************/ + +/** The name of the unit test */ +static const char *g_pszTestName = NULL; + +/*** Test fixture data and data structures ***/ + +/** A structure describing a test fixture to be run through. Each fixture + * describes the state of the windows visible (and unmapped) on the X server + * before and after a particular event is delivered, and the expected + * on-screen positions of all interesting visible windows at the end of the + * fixture as reported by the code (currently in the order it is likely to + * report them in, @todo sort this). We expect that the set of visible + * windows will be the same whether we start the code before the event and + * handle it or start the code after the event. + */ +struct SMLSFIXTURE +{ + /** The number of windows visible before the event */ + unsigned cWindowsBefore; + /** An array of Window IDs for the visible and unmapped windows before + * the event */ + Window *pahWindowsBefore; + /** The window attributes matching the windows in @a paWindowsBefore */ + XWindowAttributes *paAttribsBefore; + /** The window names matching the windows in @a paWindowsBefore */ + const char **papszNamesBefore; + /** The shaped window before the event - we allow at most one of these. + * Zero for none. */ + Window hShapeWindowBefore; + /** The number of rectangles in the shaped window before the event. */ + int cShapeRectsBefore; + /** The rectangles in the shaped window before the event */ + XRectangle *paShapeRectsBefore; + /** The number of windows visible after the event */ + unsigned cWindowsAfter; + /** An array of Window IDs for the visible and unmapped windows after + * the event */ + Window *pahWindowsAfter; + /** The window attributes matching the windows in @a paWindowsAfter */ + XWindowAttributes *paAttribsAfter; + /** The window names matching the windows in @a paWindowsAfter */ + const char **papszNamesAfter; + /** The shaped window after the event - we allow at most one of these. + * Zero for none. */ + Window hShapeWindowAfter; + /** The number of rectangles in the shaped window after the event. */ + int cShapeRectsAfter; + /** The rectangles in the shaped window after the event */ + XRectangle *paShapeRectsAfter; + /** The event to delivered */ + int x11EventType; + /** The window for which the event in @enmEvent is delivered */ + Window hEventWindow; + /** The number of windows expected to be reported at the end of the + * fixture */ + unsigned cReportedRects; + /** The onscreen positions of those windows. */ + RTRECT *paReportedRects; + /** Do we expect notification after the event? */ + bool fExpectNotification; +}; + +/*** Test fixture to test the code against X11 configure (move) events ***/ + +static Window g_ahWin1[] = { 20 }; +static XWindowAttributes g_aAttrib1Before[] = +{ { 100, 200, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable } +}; +static XRectangle g_aRectangle1[] = +{ + { 0, 0, 50, 50 }, + { 50, 50, 150, 250 } +}; +static XWindowAttributes g_aAttrib1After[] = +{ { 200, 300, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable } +}; +static const char *g_apszNames1[] = { "Test Window" }; + +AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib1Before)); +AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib1After)); +AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_apszNames1)); + +static RTRECT g_aRects1[] = +{ + { 200, 300, 250, 350 }, + { 250, 350, 400, 600 } +}; + +static SMLSFIXTURE g_testMove = +{ + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib1Before, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle1), + g_aRectangle1, + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib1After, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle1), + g_aRectangle1, + ConfigureNotify, + 20, + RT_ELEMENTS(g_aRects1), + g_aRects1, + true +}; + +/*** Test fixture to test the code against X11 configure (resize) events ***/ + +static XWindowAttributes g_aAttrib2Before[] = +{ { 100, 200, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsViewable } +}; +static XRectangle g_aRectangle2Before[] = +{ + { 0, 0, 50, 50 }, + { 50, 50, 100, 100 } +}; + +AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib2Before)); + +static SMLSFIXTURE g_testResize = +{ + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib2Before, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle2Before), + g_aRectangle2Before, + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib1After, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle1), + g_aRectangle1, + ConfigureNotify, + 20, + RT_ELEMENTS(g_aRects1), + g_aRects1, + true +}; + +/*** Test fixture to test the code against X11 map events ***/ + +static XWindowAttributes g_aAttrib3Before[] = +{ { 200, 300, 200, 300, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsUnmapped } +}; + +AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib3Before)); + +static SMLSFIXTURE g_testMap = +{ + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib3Before, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle1), + g_aRectangle1, + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib1After, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle1), + g_aRectangle1, + MapNotify, + 20, + RT_ELEMENTS(g_aRects1), + g_aRects1, + true +}; + +/*** Test fixtures to test the code against X11 unmap events ***/ + +static XWindowAttributes g_aAttrib4After[] = +{ { 100, 200, 300, 400, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, IsUnmapped } +}; + +AssertCompile(RT_ELEMENTS(g_ahWin1) == RT_ELEMENTS(g_aAttrib4After)); + +static SMLSFIXTURE g_testUnmap = +{ + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib1Before, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle1), + g_aRectangle1, + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib4After, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle1), + g_aRectangle1, + UnmapNotify, + 20, + 0, + NULL, + true +}; + +/*** A window we are not monitoring has been unmapped. Nothing should + *** happen, especially nothing bad. ***/ + +static RTRECT g_aRects2[] = +{ + { 100, 200, 150, 250 }, + { 150, 250, 300, 500 } +}; + +static SMLSFIXTURE g_testUnmapOther = +{ + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib1Before, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle1), + g_aRectangle1, + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib1Before, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle1), + g_aRectangle1, + UnmapNotify, + 21, + RT_ELEMENTS(g_aRects2), + g_aRects2, + false +}; + +/*** Test fixture to test the code against X11 shape events ***/ + +static XRectangle g_aRectangle5Before[] = +{ + { 0, 0, 200, 200 } +}; + +static SMLSFIXTURE g_testShape = +{ + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib1After, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle5Before), + g_aRectangle5Before, + RT_ELEMENTS(g_ahWin1), + g_ahWin1, + g_aAttrib1After, + g_apszNames1, + 20, + RT_ELEMENTS(g_aRectangle1), + g_aRectangle1, + VBoxShapeNotify, + 20, + RT_ELEMENTS(g_aRects1), + g_aRects1, + true +}; + +/*** And the test code proper ***/ + +/** Compare two RTRECT structures */ +static bool smlsCompRect(RTRECT *pFirst, RTRECT *pSecond) +{ + return ( (pFirst->xLeft == pSecond->xLeft) + && (pFirst->yTop == pSecond->yTop) + && (pFirst->xRight == pSecond->xRight) + && (pFirst->yBottom == pSecond->yBottom)); +} + +static void smlsPrintDiffRects(RTRECT *pExp, RTRECT *pGot) +{ + RTPrintf(" Expected: %d, %d, %d, %d. Got: %d, %d, %d, %d\n", + pExp->xLeft, pExp->yTop, pExp->xRight, pExp->yBottom, + pGot->xLeft, pGot->yTop, pGot->xRight, pGot->yBottom); +} + +/** Run through a test fixture */ +static unsigned smlsDoFixture(SMLSFIXTURE *pFixture, const char *pszDesc) +{ + SeamlessX11 subject; + unsigned cErrs = 0; + + subject.init(sendRegionUpdate); + smlsSetWindowAttributes(pFixture->paAttribsBefore, + pFixture->pahWindowsBefore, + pFixture->cWindowsBefore, + pFixture->papszNamesBefore); + smlsSetShapeRectangles(pFixture->hShapeWindowBefore, + pFixture->cShapeRectsBefore, + pFixture->paShapeRectsBefore); + subject.start(); + smlsSetWindowAttributes(pFixture->paAttribsAfter, + pFixture->pahWindowsAfter, + pFixture->cWindowsAfter, + pFixture->papszNamesAfter); + smlsSetShapeRectangles(pFixture->hShapeWindowAfter, + pFixture->cShapeRectsAfter, + pFixture->paShapeRectsAfter); + smlsSetNextEvent(pFixture->x11EventType, pFixture->hEventWindow); + if (gotNotification()) /* Initial window tree rebuild */ + { + RTPrintf("%s: fixture: %s. Notification was set before the first event!!!\n", + g_pszTestName, pszDesc); + ++cErrs; + } + subject.nextConfigurationEvent(); + if (!gotNotification()) + { + RTPrintf("%s: fixture: %s. No notification was sent for the initial window tree rebuild.\n", + g_pszTestName, pszDesc); + ++cErrs; + } + smlsSetNextEvent(0, 0); + subject.nextConfigurationEvent(); + if (pFixture->fExpectNotification && !gotNotification()) + { + RTPrintf("%s: fixture: %s. No notification was sent after the event.\n", + g_pszTestName, pszDesc); + ++cErrs; + } + RTRECT *pRects = subject.getRects(); + size_t cRects = subject.getRectCount(); + if (cRects != pFixture->cReportedRects) + { + RTPrintf("%s: fixture: %s. Wrong number of rectangles reported after processing event (expected %u, got %u).\n", + g_pszTestName, pszDesc, pFixture->cReportedRects, + cRects); + ++cErrs; + } + else + for (unsigned i = 0; i < cRects; ++i) + if (!smlsCompRect(&pRects[i], &pFixture->paReportedRects[i])) + { + RTPrintf("%s: fixture: %s. Rectangle %u wrong after processing event.\n", + g_pszTestName, pszDesc, i); + smlsPrintDiffRects(&pFixture->paReportedRects[i], + &pRects[i]); + ++cErrs; + break; + } + subject.stop(); + subject.start(); + if (cRects != pFixture->cReportedRects) + { + RTPrintf("%s: fixture: %s. Wrong number of rectangles reported without processing event (expected %u, got %u).\n", + g_pszTestName, pszDesc, pFixture->cReportedRects, + cRects); + ++cErrs; + } + else + for (unsigned i = 0; i < cRects; ++i) + if (!smlsCompRect(&pRects[i], &pFixture->paReportedRects[i])) + { + RTPrintf("%s: fixture: %s. Rectangle %u wrong without processing event.\n", + g_pszTestName, pszDesc, i); + smlsPrintDiffRects(&pFixture->paReportedRects[i], + &pRects[i]); + ++cErrs; + break; + } + return cErrs; +} + +int main(int argc, char **argv) +{ + RTR3InitExe(argc, &argv, 0); + unsigned cErrs = 0; + g_pszTestName = RTPathFilename(argv[0]); + + RTPrintf("%s: TESTING\n", g_pszTestName); + +/** @todo r=bird: This testcase is broken and we didn't notice because we + * don't run it on the testboxes! @bugref{9842} */ +if (argc == 1) +{ + RTPrintf("%s: Note! This testcase is broken, skipping!\n", g_pszTestName); + return RTEXITCODE_SUCCESS; +} + + cErrs += smlsDoFixture(&g_testMove, + "ConfigureNotify event (window moved)"); + // Currently not working + cErrs += smlsDoFixture(&g_testResize, + "ConfigureNotify event (window resized)"); + cErrs += smlsDoFixture(&g_testMap, "MapNotify event"); + cErrs += smlsDoFixture(&g_testUnmap, "UnmapNotify event"); + cErrs += smlsDoFixture(&g_testUnmapOther, + "UnmapNotify event for unmonitored window"); + cErrs += smlsDoFixture(&g_testShape, "ShapeNotify event"); + if (cErrs > 0) + RTPrintf("%u errors\n", cErrs); + return cErrs == 0 ? 0 : 1; +} diff --git a/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp new file mode 100644 index 00000000..06bd5c8e --- /dev/null +++ b/src/VBox/Additions/x11/VBoxClient/testcase/tstSeamlessX11.cpp @@ -0,0 +1,185 @@ +/* $Id: tstSeamlessX11.cpp $ */ +/** @file + * Linux seamless guest additions simulator in host. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <stdlib.h> /* exit() */ + +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/stream.h> +#include <VBox/VBoxGuestLib.h> + +#include "../seamless.h" + +static RTSEMEVENT eventSem; + +void VBClLogError(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, args); + va_end(args); + + AssertPtr(psz); + RTPrintf("Error: %s", psz); + + RTStrFree(psz); +} + +/** Exit with a fatal error. */ +void VBClLogFatalError(const char *pszFormat, ...) +{ + va_list args; + va_start(args, pszFormat); + char *psz = NULL; + RTStrAPrintfV(&psz, pszFormat, args); + va_end(args); + + AssertPtr(psz); + RTPrintf("Fatal error: %s", psz); + + RTStrFree(psz); + + exit(1); +} + +void VBClLogVerbose(unsigned iLevel, const char *pszFormat, ...) +{ + RT_NOREF(iLevel); + + va_list va; + va_start(va, pszFormat); + RTPrintf("%s", pszFormat); + va_end(va); +} + +int VBClStartVTMonitor() +{ + return VINF_SUCCESS; +} + +int VbglR3SeamlessSendRects(uint32_t cRects, PRTRECT pRects) +{ + RTPrintf("Received rectangle update (%u rectangles):\n", cRects); + for (unsigned i = 0; i < cRects; ++i) + { + RTPrintf(" xLeft: %d yTop: %d xRight: %d yBottom: %d\n", + pRects[i].xLeft, pRects[i].yTop, pRects[i].xRight, + pRects[i].yBottom); + } + return VINF_SUCCESS; +} + +int VbglR3SeamlessSetCap(bool bState) +{ + RTPrintf("%s\n", bState ? "Seamless capability set" + : "Seamless capability unset"); + return VINF_SUCCESS; +} + +int VbglR3CtlFilterMask(uint32_t u32OrMask, uint32_t u32NotMask) +{ + RTPrintf("IRQ filter mask changed. Or mask: 0x%x. Not mask: 0x%x\n", + u32OrMask, u32NotMask); + return VINF_SUCCESS; +} + +int VbglR3SeamlessWaitEvent(VMMDevSeamlessMode *pMode) +{ + static bool active = false; + + int rc = VINF_SUCCESS; + if (!active) + { + active = true; + *pMode = VMMDev_Seamless_Visible_Region; + } + else + rc = RTSemEventWait(eventSem, RT_INDEFINITE_WAIT); + return rc; +} + +VBGLR3DECL(int) VbglR3InitUser(void) { return VINF_SUCCESS; } +VBGLR3DECL(void) VbglR3Term(void) {} + +/** + * Xlib error handler for certain errors that we can't avoid. + */ +int vboxClientXLibErrorHandler(Display *pDisplay, XErrorEvent *pError) +{ + char errorText[1024]; + + if (pError->error_code == BadWindow) + { + /* This can be triggered if a guest application destroys a window before we notice. */ + RTPrintf("ignoring BadAtom error and returning\n"); + return 0; + } + XGetErrorText(pDisplay, pError->error_code, errorText, sizeof(errorText)); + RTPrintf("An X Window protocol error occurred: %s\n" + " Request code: %d\n" + " Minor code: %d\n" + " Serial number of the failed request: %d\n\n" + "exiting.\n", + errorText, (int)pError->request_code, (int)pError->minor_code, + (int)pError->serial); + exit(1); +} + +int main( int argc, char **argv) +{ + int rc = VINF_SUCCESS; + + RTR3InitExe(argc, &argv, 0); + RTPrintf("VirtualBox guest additions X11 seamless mode testcase\n"); + if (0 == XInitThreads()) + { + RTPrintf("Failed to initialise X11 threading, exiting.\n"); + exit(1); + } + /* Set an X11 error handler, so that we don't die when we get unavoidable errors. */ + XSetErrorHandler(vboxClientXLibErrorHandler); + RTPrintf("\nType Ctrl-C to exit...\n"); + RTSemEventCreate(&eventSem); + /** Our instance of the seamless class. */ + SeamlessMain seamless; + LogRel(("Starting seamless Guest Additions...\n")); + rc = seamless.init(); + if (rc != VINF_SUCCESS) + { + RTPrintf("Failed to initialise seamless Additions, rc = %Rrc\n", rc); + } + bool fShutdown = false; + rc = seamless.worker(&fShutdown); + if (rc != VINF_SUCCESS) + { + RTPrintf("Failed to run seamless Additions, rc = %Rrc\n", rc); + } + return rc; +} diff --git a/src/VBox/Additions/x11/undefined_xfree86 b/src/VBox/Additions/x11/undefined_xfree86 new file mode 100644 index 00000000..e80b6819 --- /dev/null +++ b/src/VBox/Additions/x11/undefined_xfree86 @@ -0,0 +1,1188 @@ +# This file contains the complete list, excluding modules of undefined symbols +# which are allowed in XFree86 4.3 binary driver modules. + +AddCallback +AddEnabledDevice +AddExtension +AddExtensionAlias +AddResource +AdjustWaitForDelay +AllocateClientPrivate +AllocateClientPrivateIndex +AllocateColormapPrivateIndex +AllocateGCPrivate +AllocateGCPrivateIndex +AllocatePixmap +AllocatePixmapPrivate +AllocatePixmapPrivateIndex +AllocateScreenPrivateIndex +AllocateWindowPrivate +AllocateWindowPrivateIndex +AllocColor +_alpha_inb +_alpha_inl +_alpha_inw +_alpha_outb +_alpha_outl +_alpha_outw +AltICD2061SetClock +AssignTypeAndName +Att409SetClock +AttendClient +BadShmSegCode +BitOrderInvert +BufFileRead +BufFileWrite +_bus_base +byte_reversed +ChangeGC +ChangeResourceValue +ChangeWindowAttributes +ChangeWindowProperty +CheckCursorConfinement +CheckExtension +CheckFSFormat +CheckWindowOptionalNeed +Chrontel8391SetClock +CirrusFindClock +CirrusSetClock +clients +ClientSleep +ClientSleepUntil +ClientStateCallback +ClientTimeToServerTime +ClientWakeup +CloseFont +commonCalcClock +CompareTimeStamps +ConfiguredMonitor +CopyGC +CopyISOLatin1Lowered +CopySwap32Write +CreateColormap +CreateFontRec +CreateGC +CreateNewResourceClass +CreateNewResourceType +CreateScratchGC +CreateUnclippedWinSize +CreateWindow +currentMaxClients +CurrentSelections +currentTime +dacInTi3026IndReg +dacOutTi3026IndReg +debug_inb +debug_inl +debug_inw +debug_outb +debug_outl +debug_outw +DeclareExtensionSecurity +defaultColorVisualClass +defaultDPMSEnabled +DeleteCallback +DeleteProperty +DeliverEvents +deltaSaveUndersViewable +DestroyFontRec +DeviceEventCallback +DGAActive +DGAAvailable +DGABlitRect +DGABlitTransRect +DGAChangePixmapMode +DGACloseFramebuffer +DGACreateColormap +DGAFillRect +DGAGetModeInfo +DGAGetModes +DGAGetOldDGAMode +DGAGetViewportStatus +DGAInit +DGAInstallCmap +DGAOpenFramebuffer +DGASelectInput +DGASetInputMode +DGASetMode +DGASetViewport +DGASync +dispatchException +div +__div64 +__divdf3 +__divdi3 +__divl +__divlu +__divq +__divqu +__divsf3 +__divsi3 +dixChangeGC +DoChangeGC +DPMSCapableFlag +DPMSDisabledSwitch +DPMSEnabled +DPMSEnabledSwitch +DPMSGet +DPMSOffTime +DPMSPowerLevel +DPMSSet +DPMSStandbyTime +DPMSSupported +DPMSSuspendTime +DuplicateModule +eieio +erel->rel->r_info +Error +ErrorF +Et4000AltICD2061SetClock +ET4000gendacSetClock +ET4000stg1703SetClock +ET6000SetClock +EventCallback +EventSwapVector +ex +FakeAllocColor +FakeClientID +FakeFreeColor +FatalError +FindAllClientResources +FindClientResourcesByType +FindWindowWithOptional +FlushCallback +FontCacheChangeSettings +FontCacheCloseCache +FontCacheGetBitmap +FontCacheGetEntry +FontCacheGetSettings +FontCacheGetStatistics +FontCacheInsertEntry +FontCacheOpenCache +FontCacheSearchEntry +FontComputeInfoAccelerators +FontCouldBeTerminal +FontDefaultFormat +FontEncDirectory +FontEncFind +FontEncFromXLFD +FontEncMapFind +FontEncName +font_encoding_find +font_encoding_from_xlfd +font_encoding_name +font_encoding_recode +FontEncRecode +FontFileBitmapSources +FontFileClose +FontFileCloseFont +FontFileCompleteXLFD +FontFileCountDashes +FontFileFindNameInDir +FontFileMatchRenderer +FontFileOpen +FontFileOpenBitmap +FontFilePriorityRegisterRenderer +FontFileRegisterRenderer +FontMapFind +FontMapReverse +FontMapReverseFree +FontParseXLFDName +FontToXError +FourByteSwap +fpe_functions +FreeColors +FreeCursor +FreeGC +FreeResource +FreeResourceByType +FreeScratchGC +FreeScratchPixmapHeader +func + func +gendacMNToClock +GetGlyphs +GetScratchGC +GetScratchPixmapHeader +GetTimeInMillis +GetXIDList +GetXIDRange +GiveUp +globalSerialNumber +GrabInProgress +GravityTranslate +header +IBMRGBSetClock +ICS2595SetClock +ICS5342SetClock +identifyEncodingFile +IgnoreClient +inb +_inb +InitButtonClassDeviceStruct +InitFocusClassDeviceStruct +InitKeyboardDeviceStruct +InitKeyClassDeviceStruct +InitLedFeedbackClassDeviceStruct +InitPointerDeviceStruct +InitProximityClassDeviceStruct +InitPtrFeedbackClassDeviceStruct +InitValuatorAxisStruct +InitValuatorClassDeviceStruct +inl +_inl +inputInfo +inw +_inw +ioBase +isItTimeToYield +lastDeviceEventTime +lastResourceType +ldl_brx +ldl_u +ldq_u +ldw_brx +ldw_u +LegalNewID +LoaderCheckUnresolved +LoaderDefaultFunc +LoaderErrorMsg +LoaderFreeDirList +LoaderGetOS +LoaderListDirs +LoaderRefSymbols +LoaderRefSymLists +LoaderReqSymbols +LoaderReqSymLists +LoaderSymbol +LoadExtension +LoadFont +LoadGlyphs +LoadSubModule +LocalClient +LookupClient +LookupDrawable +LookupIDByClass +LookupIDByType +LookupKeyboardDevice +LookupPointerDevice +LookupWindow +MakeAtom +MakeClientGrabImpervious +MakeClientGrabPervious +MakeWindowOptional +MapWindow +mem_barrier +memcpy +"memcpy",xf86memcpy +memset +"memset",xf86memset +miAllocateGCPrivateIndex +miChangeBorderWidth +miChangeClip +miChangeGC +miClearDrawable +miClearToBackground +miClearVisualTypes +miClipNotify +miClipSpans +miCompositeRects +miComputeCompositeClip +miComputeCompositeRegion +miCopyArea +miCopyClip +miCopyGC +miCopyPlane +miCreateDefColormap +miCreateGCOps +miCreateScreenResources +miDCInitialize +miDestroyClip +miDestroyGC +miDestroyGCOps +miEmptyBox +miEmptyData +miExpandDirectColors +miFillArcSetup +miFillArcSliceSetup +miFillConvexPoly +miFillPolygon +miFindMaxBand +miGetDefaultVisualMask +miGetImage +miGetScreenPixmap +miGlyphExtents +miGlyphs +miHandleExposures +miHandleValidateExposures +miHookInitVisuals +miImageGlyphBlt +miImageText16 +miImageText8 +miInitializeBackingStore +miInitializeBanking +miInitializeColormap +miInitOverlay +miInitVisuals +miInitVisualsProc +miInstallColormap +miInstalledMaps +miIntersect +miInverse +miListInstalledColormaps +miModifyBanking +miModifyPixmapHeader +MinorOpcodeOfRequest +miOverlayCollectUnderlayRegions +miOverlayComputeCompositeClip +miOverlayCopyUnderlay +miOverlayGetPrivateClips +miOverlaySetRootClip +miOverlaySetTransFunction +miPaintWindow +miPictureInit +miPointerAbsoluteCursor +miPointerCurrentScreen +miPointerGetMotionBufferSize +miPointerGetMotionEvents +miPointerInitialize +miPointerPosition +miPointerScreenIndex +miPointerWarpCursor +miPointInRegion +miPolyArc +miPolyBuildEdge +miPolyBuildPoly +miPolyFillArc +miPolyFillRect +miPolyGlyphBlt +miPolyPoint +miPolyRectangle +miPolySegment +miPolyText16 +miPolyText8 +miPushPixels +miPutImage +miRecolorCursor +miRectAlloc +miRectIn +miRectsToRegion +miRegionAppend +miRegionCopy +miRegionCreate +miRegionDestroy +miRegionEmpty +miRegionExtents +miRegionInit +miRegionNotEmpty +miRegionReset +miRegionUninit +miRegionValidate +miResolveColor +miRoundCapClip +miRoundJoinClip +MiscExtApply +MiscExtCreateStruct +MiscExtDestroyStruct +MiscExtGetFilePaths +MiscExtGetKbdSettings +MiscExtGetKbdValue +MiscExtGetMouseSettings +MiscExtGetMouseValue +MiscExtSetGrabKeysState +MiscExtSetKbdValue +MiscExtSetMouseDevice +MiscExtSetMouseValue +miScreenInit +miSegregateChildren +miSendGraphicsExpose +miSetPixmapDepths +miSetScreenPixmap +miSetShape +miSetVisualTypes +miSetVisualTypesAndMasks +miSetZeroLineBias +miShapedWindowIn +miSpritePointerFuncs +miStepDash +miSubtract +miTranslateRegion +miUninstallColormap +miUnion +miWideDash +miWideLine +miWindowExposures +miZeroArcSetup +miZeroClipLine +miZeroDashLine +miZeroLine +miZeroLineScreenIndex +miZeroPolyArc +__moddi3 +__modsi3 +monitorResolution +MoveWindowInStack +mul +__mul64 +__muldf3 +__muldi3 +__mulsf3 +__mulsi3 +Must_have_memory +NameForAtom + name, func + name, var +NewCurrentScreen +NoopDDA +noPanoramiXExtension +NotClippedByChildren +noTestExtensions +NotImplemented +noXkbExtension +NumCurrentSelections +numSaveUndersViewable +Ones +outb +_outb +outl +_outl +outw +_outw +panoramiXdataPtr +PanoramiXNumScreens +PciAvoid +pciBusAddrToHostAddr +pciFindFirst +pciFindNext +pciHostAddrToBusAddr +pciNumBuses +pciReadByte +pciReadLong +pciReadWord +pciSetBitsLong +pciTag +pciWriteByte +pciWriteLong +pciWriteWord +permitOldBugs +PictureAddFilter +PictureGetSubpixelOrder +PictureInit +PictureScreenPrivateIndex +PictureSetFilterAlias +PictureSetSubpixelOrder +PictureTransformPoint +PixmapWidthPaddingInfo +PointerConfinedToScreen +ProcBadRequest +ProcVector +QueryColors +QueryGlyphExtents +QueueWorkProc +RegisterBlockAndWakeupHandlers +RegisterResourceName +rel[i].r_info +rel->r_info +rem +__reml +__remlu +RemoveBlockAndWakeupHandlers +RemoveEnabledDevice +__remq +__remqu +RepadBitmap +ReplyCallback +ReplySwapVector +res8514Exclusive +res8514Shared +ResetCurrentRequest +ResizeChildrenWinSize +ResourceNames +_restf14 +_restf17 +_restf18 +_restf19 +_restf20 +_restf22 +_restf23 +_restf24 +_restf25 +_restf26 +_restf27 +_restf28 +_restf29 +resVgaExclusive +resVgaIoShared +resVgaMemShared +resVgaShared +resVgaSparseExclusive +resVgaSparseShared +resVgaUnusedExclusive +resVgaUnusedShared +S3AuroraSetClock +S3gendacSetClock +s3IBMRGB_Init +s3IBMRGB_Probe +s3InIBMRGBIndReg +s3OutIBMRGBIndReg +S3Trio64V2SetClock +S3TrioSetClock +savedScreenInfo +_savef14 +_savef17 +_savef18 +_savef19 +_savef20 +_savef22 +_savef23 +_savef24 +_savef25 +_savef26 +_savef27 +_savef28 +_savef29 +SaveScreens +SC11412SetClock +screenInfo +screenIsSaved +ScreenSaverBlanking +ScreenSaverTime +SecurityLookupDrawable +SecurityLookupIDByClass +SecurityLookupIDByType +SecurityLookupWindow +SendErrorToClient +SendMappingNotify +SendVisibilityNotify +serverClient +serverGeneration +ServerGrabCallback +SetBorderSize +SetClipRects +SetCriticalEvent +SetCriticalOutputPending +SetDashes +SetInputCheck +SetTimeSinceLastInputEvent +SetWinSize +ShmCompletionCode +ShmSegType +SkippedRequestsCallback +sparcPromClose +sparcPromGetBool +sparcPromGetProperty +sparcPromInit +StandardMinorOpcode +STG1703getIndex +STG1703magic +STG1703SetClock +STG1703setIndex +stl_brx +stl_u +StoreColors +stq_u +stw_brx +stw_u +Swap32Write +SwapColorItem +SwapConnSetupInfo +SwapConnSetupPrefix +SwapLongs +SwapShorts +sysctlbyname +TellGainedMap +TellLostMap +Ti3025SetClock +Ti3026SetClock +Ti3030SetClock +TimerCancel +TimerFree +TimerSet +TraverseTree +TryClientEvents +TwoByteSwap +TypeMask +udiv +__udivdi3 +__udivsi3 +__umoddi3 +__umodsi3 +umul +UnloadSubModule +UnmapWindow +UpdateCurrentTime +UpdateCurrentTimeIf +urem +ValidateGC +ValidAtom + var +VerifyRectOrder +VErrorF +VidModeAddModeline +VidModeCheckModeForDriver +VidModeCheckModeForMonitor +VidModeCopyMode +VidModeCreateMode +VidModeDeleteModeline +VidModeExtensionInit +VidModeGetClocks +VidModeGetCurrentModeline +VidModeGetDotClock +VidModeGetFirstModeline +VidModeGetGamma +VidModeGetGammaRamp +VidModeGetGammaRampSize +VidModeGetModeValue +VidModeGetMonitor +VidModeGetMonitorValue +VidModeGetNextModeline +VidModeGetNumOfClocks +VidModeGetNumOfModes +VidModeGetViewPort +VidModeLockZoom +VidModeSetCrtcForMode +VidModeSetGamma +VidModeSetGammaRamp +VidModeSetModeValue +VidModeSetViewPort +VidModeSwitchMode +VidModeZoomViewport +WalkTree +WindowsRestructured +WindowTable +WriteEventsToClient +write_mem_barrier +WriteToClient +x +Xalloc +Xcalloc +XDGAEventBase +xf86abort +xf86abs +xf86access +xf86acos +xf86AcquireGART +xf86AddDeviceToConfigure +xf86AddDriver +xf86AddEnabledDevice +xf86AddEntityToScreen +xf86AddInputDriver +xf86AddInputHandler +xf86AddModuleInfo +xf86AddNewOption +xf86AddPixFormat +xf86AddResToList +xf86AgpGARTSupported +xf86AllocateEntityPrivateIndex +xf86AllocateGARTMemory +xf86AllocateInput +xf86AllocateLinearOffscreenArea +xf86AllocateOffscreenArea +xf86AllocateOffscreenLinear +xf86AllocateScreen +xf86AllocateScrnInfoPrivateIndex +xf86asin +xf86atan +xf86atan2 +xf86atof +xf86atoi +xf86atol +xf86BindGARTMemory +xf86BlockSIGIO +xf86Break1 +xf86Break2 +xf86Break3 +xf86bsearch +xf86BusToMem +xf86bzero +xf86calloc +xf86CaughtSignal +xf86ceil +xf86CheckIfOptionUsed +xf86CheckIfOptionUsedByName +xf86CheckModeForDriver +xf86CheckModeForMonitor +xf86CheckMTRR +xf86CheckPciGAType +xf86CheckPciMemBase +xf86CheckPciSlot +xf86ChkConflict +xf86chmod +xf86chown +xf86ClaimFbSlot +xf86ClaimFixedResources +xf86ClaimIsaSlot +xf86ClaimNoSlot +xf86ClaimPciSlot +xf86clearerr +xf86ClearPrimInitDone +xf86close +xf86closedir +xf86CloseSerial +xf86clrdaccommbit +xf86CollectInputOptions +xf86CollectOptions +xf86CommonSpecialKey +xf86ComparePciBusString +xf86ConfigActiveIsaEntity +xf86ConfigActivePciEntity +xf86ConfigDRI +xf86ConfigFbEntity +xf86ConfigIsaEntity +xf86ConfigIsaEntityInactive +xf86ConfigPciEntity +xf86ConfigPciEntityInactive +xf86cos +xf86CurrentScreen +xf86dactocomm +xf86dactopel +xf86DeallocateResourcesForEntity +xf86DeleteDriver +xf86DeleteInput +xf86DeleteMode +xf86DeleteModuleInfo +xf86DeleteScreen +xf86DeregisterStateChangeNotificationCallback +xf86DisableInputHandler +xf86DisableInterrupts +xf86DisableIO +xf86DisableRandR +xf86DPMSInit +xf86DrvMsg +xf86DrvMsgVerb +xf86DummyVar1 +xf86DummyVar2 +xf86DummyVar3 +xf86DupResList +xf86EnableAccess +xf86EnableAGP +xf86EnableDisableFBAccess +xf86EnableInputHandler +xf86EnableInterrupts +xf86EnableIO +xf86EnablePciBusMaster +xf86EnableVTSwitch +xf86EnterServerState +xf86eqEnqueue +xf86errno +xf86ErrorF +xf86ErrorFVerb +xf86execl +xf86exit +xf86exp +xf86fabs +xf86FBManagerRunning +xf86fclose +xf86feof +xf86ferror +xf86fflush +xf86ffs +xf86fgetc +xf86fgetpos +xf86fgets +xf86FindOption +xf86FindOptionValue +xf86FindPciClass +xf86FindPciDeviceVendor +xf86FindScreenForEntity +xf86FindXvOptions +xf86finite +xf86FirstLocalDevice +xf86FixPciResource +xf86floor +xf86FlushInput +xf86fmod +xf86fopen +xf86FormatPciBusNumber +xf86fpossize +xf86fprintf +xf86fputc +xf86fputs +xf86fread +xf86free +xf86FreeOffscreenArea +xf86FreeOffscreenLinear +xf86FreeResList +xf86freopen +xf86frexp +xf86fscanf +xf86fseek +xf86fsetpos +xf86fstat +xf86ftell +xf86fwrite +xf86GARTCloseScreen +xf86GetAGPInfo +xf86GetAllowMouseOpenFail +xf86GetBlock +xf86GetBppFromDepth +xf86getc +xf86GetClocks +xf86getdaccomm +xf86GetDepth +xf86GetDevFromEntity +xf86getegid +xf86GetEntityForSbusInfo +xf86GetEntityInfo +xf86GetEntityPrivate +xf86getenv +xf86GetErrno +xf86geteuid +xf86GetFlipPixels +xf86GetGamma +xf86getjmptype +xf86GetLastScrnFlag +xf86GetModInDevAllowNonLocal +xf86GetModInDevEnabled +xf86GetModuleVersion +xf86GetMotionEvents +xf86GetNearestClock +xf86GetNumEntityInstances +xf86GetOptValBool +xf86GetOptValFreq +xf86GetOptValInteger +xf86GetOptValReal +xf86GetOptValString +xf86GetOptValULong +xf86GetOS +xf86getpagesize +xf86GetPciConfigInfo +xf86GetPciDomain +xf86GetPciEntity +xf86GetPciInfoForEntity +xf86GetPciVideoInfo +xf86getpid +xf86GetPix24 +xf86GetPixFormat +xf86GetPointerScreenFuncs +xf86GetSbusInfoForEntity +xf86getsecs +xf86GetSerialModemState +xf86GetServerName +xf86GetSparse +xf86GetVerbosity +xf86GetVersion +xf86GetVidModeAllowNonLocal +xf86GetVidModeEnabled +xf86GetVisualName +xf86GetWeight +xf86HandleColormaps +xf86HUGE_VAL +xf86hypot +xf86InitFBManager +xf86InitFBManagerArea +xf86InitFBManagerRegion +xf86InitialCheckModeForDriver +xf86InitValuatorAxisStruct +xf86InitValuatorDefaults +xf86InstallSIGIOHandler +xf86inSuspend +xf86InterceptSignals +xf86ioctl +xf86IODelay +xf86isalnum +xf86isalpha +xf86iscntrl +xf86IsCorePointer +xf86isdigit +xf86IsEntityPrimary +xf86IsEntitySharable +xf86IsEntityShared +xf86isgraph +xf86islower +xf86IsOptionSet +xf86IsPc98 +xf86IsPciDevPresent +xf86IsPrimaryIsa +xf86IsPrimaryPci +xf86IsPrimInitDone +xf86isprint +xf86ispunct +xf86IsScreenPrimary +xf86isspace +xf86IsUnblank +xf86isupper +xf86isxdigit +xf86JoinResLists +xf86labs +xf86ldexp +xf86LinearVidMem +xf86LoadDrvSubModule +xf86LoaderCheckSymbol +xf86LoaderRefSymbols +xf86LoaderRefSymLists +xf86LoaderReqSymbols +xf86LoaderReqSymLists +xf86LoadKernelModule +xf86LoadOneModule +xf86LoadSubModule +xf86log +xf86log10 +"xf86longjmp",longjmp +xf86LookupMode +xf86lseek +xf86malloc +xf86MapDomainIO +xf86MapDomainMemory +xf86MapPciMem +xf86MapReadSideEffects +xf86MapSbusMem +xf86MapVidMem +xf86MarkOptionUsed +xf86MarkOptionUsedByName +xf86MatchDevice +xf86MatchIsaInstances +xf86MatchPciInstances +xf86MatchSbusInstances +xf86memchr +xf86memcmp +xf86memcpy +xf86memmove +xf86memset +xf86MemToBus +xf86mkdir +xf86mknod +xf86mmap +xf86ModeStatusToString +xf86modf +xf86MotionHistoryAllocate +xf86Msg +xf86MsgVerb +xf86munmap +xf86NameCmp +xf86NewOption +xf86NewSerialNumber +xf86NextOption +xf86NoSharedResources +xf86open +xf86opendir +xf86OpenSerial +xf86OptionListCreate +xf86OptionListFree +xf86OptionListMerge +xf86OptionListReport +xf86OptionName +xf86OptionValue +xf86OSKbdPreInit +xf86OSMouseInit +xf86p8bit +xf86ParseIsaBusString +xf86ParsePciBusString +xf86perror +xf86PixmapIndex +xf86PostButtonEvent +xf86PostKeyboardEvent +xf86PostKeyEvent +xf86PostMotionEvent +xf86PostProximityEvent +xf86pow +xf86PrintChipsets +xf86PrintDepthBpp +xf86printf +xf86PrintModes +xf86PrintResList +xf86ProcessCommonOptions +xf86ProcessOptions +xf86PruneDriverModes +xf86PurgeUnlockedOffscreenAreas +xf86qsort +xf86QueryLargestOffscreenArea +xf86QueryLargestOffscreenLinear +xf86QueueAsyncEvent +xf86read +xf86ReadBIOS +xf86readdir +xf86ReadDomainMemory +xf86ReadMmio16 +xf86ReadMmio32 +xf86ReadMmio8 +xf86ReadPciBIOS +xf86ReadSerial +xf86realloc +xf86ReallocatePciResources +xf86RegisterFreeBoxCallback +xf86RegisterOffscreenManager +xf86RegisterResources +xf86RegisterRootWindowProperty +xf86RegisterStateChangeNotificationCallback +xf86ReleaseGART +xf86remove +xf86RemoveEnabledDevice +xf86RemoveEntityFromScreen +xf86RemoveInputHandler +xf86RemoveSIGIOHandler +xf86rename +xf86ReplaceBoolOption +xf86ReplaceIntOption +xf86ReplaceStrOption +xf86ResizeOffscreenArea +xf86ResizeOffscreenLinear +xf86ReturnOptValBool +xf86rewind +xf86rewinddir +xf86SbusHandleColormaps +xf86SbusHideOsHwCursor +xf86SbusSetOsHwCursorCmap +xf86SbusUseBuiltinMode +xf86ScaleAxis +xf86scanpci +xf86ScreenIndex +xf86Screens +xf86SerialModemClearBits +xf86SerialModemSetBits +xf86SerialSendBreak +xf86ServerIsExiting +xf86ServerIsOnlyDetecting +xf86ServerIsOnlyProbing +xf86ServerIsResetting +xf86SetAccessFuncs +xf86SetBackingStore +xf86SetBlackWhitePixels +xf86SetBoolOption +xf86setbuf +xf86SetCrtcForModes +xf86SetCurrentAccess +xf86setdaccomm +xf86setdaccommbit +xf86SetDefaultVisual +xf86SetDepthBpp +xf86SetDpi +xf86SetEntityFuncs +xf86SetEntityInstanceForScreen +xf86SetEntitySharable +xf86SetEntityShared +xf86SetGamma +xf86SetIntOption +xf86setjmp +xf86setjmp1 +xf86setjmp1_arg2 +"xf86setjmp1",__sigsetjmp +xf86setjmperror +"xf86setjmp",setjmp +xf86SetLastScrnFlag +xf86SetOperatingState +xf86SetPciVideo +xf86SetPrimInitDone +xf86SetPriority +xf86SetRealOption +xf86SetSerial +xf86SetSerialModemState +xf86SetSerialSpeed +xf86SetSilkenMouse +xf86SetStrOption +xf86setvbuf +xf86SetWeight +xf86shmat +xf86shmctl +xf86shmdt +xf86shmget +xf86ShowClockRanges +xf86ShowClocks +xf86ShowUnusedOptions +xf86sin +xf86sleep +xf86SlowBcopy +xf86SlowBCopyFromBus +xf86SlowBCopyToBus +xf86snprintf +xf86SoundKbdBell +xf86sprintf +xf86SPTimestamp +xf86sqrt +xf86sscanf +xf86stat +xf86stderr +xf86stdin +xf86stdout +xf86STimestamp +xf86strcasecmp +xf86strcat +xf86strchr +xf86strcmp +xf86strcpy +xf86strcspn +xf86strdup +xf86strerror +xf86StringToToken +xf86strlen +xf86strncasecmp +xf86strncat +xf86strncmp +xf86strncpy +xf86strpbrk +xf86strrchr +xf86strspn +xf86strstr +xf86strtod +xf86strtok +xf86strtol +xf86strtoul +xf86tan +xf86tmpfile +xf86TokenToOptinfo +xf86TokenToOptName +xf86TokenToString +xf86tolower +xf86toupper +xf86UDelay +xf86UnbindGARTMemory +xf86UnblockSIGIO +xf86ungetc +xf86UnloadSubModule +xf86UnmapSbusMem +xf86UnMapVidMem +xf86usleep +xf86ValidateModes +xf86VDrvMsgVerb +xf86vfprintf +xf86vsnprintf +xf86vsprintf +xf86WaitForInput +xf86write +xf86WriteMmio16 +xf86WriteMmio32 +xf86WriteMmio8 +xf86WriteMmioNB16 +xf86WriteMmioNB32 +xf86WriteMmioNB8 +xf86writepci +xf86WriteSerial +xf86XInputSetScreen +xf86XInputSetSendCoreEvents +xf86XVAllocateVideoAdaptorRec +xf86XVClipVideoHelper +xf86XVFillKeyHelper +xf86XVFreeVideoAdaptorRec +xf86XVListGenericAdaptors +xf86XvMCScreenInit +xf86XVQueryOffscreenImages +xf86XVRegisterGenericAdaptorDriver +xf86XVRegisterOffscreenImages +xf86XVScreenInit +Xfree +XineramaDeleteResource +XineramaGetCursorScreen +XineramaRegisterConnectionBlockCallback +XisbBlockDuration +XisbFree +XisbNew +XisbRead +XisbTrace +XisbWrite +XkbInitKeyboardDeviceStruct +XkbSetRulesDflts +XNFalloc +XNFcalloc +XNFrealloc +XNFstrdup +XRC_DRAWABLE +Xrealloc +XRT_COLORMAP +XRT_GC +XRT_PIXMAP +XRT_WINDOW +Xstrdup +XvGetRTPortProc +XvGetScreenIndexProc +XvMCScreenInitProc +XvScreenInitProc diff --git a/src/VBox/Additions/x11/undefined_xfree86_modules b/src/VBox/Additions/x11/undefined_xfree86_modules new file mode 100644 index 00000000..9a800b2c --- /dev/null +++ b/src/VBox/Additions/x11/undefined_xfree86_modules @@ -0,0 +1,16 @@ +# This file contains the list of symbols from XFree86 modules which we use. +# These symbols must also be explicitly declared in the driver code. + +fbPictureInit +fbScreenInit +ShadowFBInit2 +vgaHWFreeHWRec +vgaHWGetHWRec +vgaHWGetIndex +vgaHWGetIOBase +vgaHWRestore +vgaHWSave +vgaHWSetStdFuncs +xf86CreateCursorInfoRec +xf86DestroyCursorInfoRec +xf86InitCursor diff --git a/src/VBox/Additions/x11/undefined_xorg b/src/VBox/Additions/x11/undefined_xorg new file mode 100644 index 00000000..dcc6c8fc --- /dev/null +++ b/src/VBox/Additions/x11/undefined_xorg @@ -0,0 +1,184 @@ +# This file contains a non-exhaustive list of symbols which are allowed to +# be undefined in X.Org Server binary driver modules which were not allowed +# in XFree86 4.3. Some of these may only be allowed on some platforms, but +# it fixing that would add additional complexity. + +__assert_fail +calloc +chdir +chmod +chown +close +closedir +__ctype_b_loc +__ctype_mask +__cxa_finalize +__deregister_frame_info_bases +DRI2CloseScreen +DRI2ScreenInit +DRICloseScreen +DRICreateInfoRec +DRICreatePCIBusID +DRIDestroyInfoRec +DRIFinishScreenInit +DRILock +DRIQueryVersion +DRIScreenInit +DRIUnlock +drmClose +drmDropMaster +drmFreeVersion +drmGetVersion +drmIoctl +drmModeGetResources +drmModeFreeResources +drmSetMaster +___errno +__errno_location +fbPictureInit +fbScreenInit +fchmod +fchown +fcntl +fflush +flock +fprintf +__fprintf_chk +fputs +free +fstat +fsync +ftruncate +ftruncate64 +futimes +fwrite +__fxstat64 +getcwd +getenv +geteuid +GetMotionHistory +GetMotionHistorySize +getpwuid_r +GlxSetVisualConfigs +__gmon_start__ +iconv +iconv_close +iconv_open +__iob +ioctl +__isoc99_sscanf +isspace +_ITM_deregisterTMCloneTable +_ITM_registerTMCloneTable +_Jv_RegisterClasses +lchown +lseek +lseek64 +lstat +__lxstat64 +malloc +memalign +memchr +memcmp +__memcpy_chk +memmove +miPointerGetScreen +mmap64 +mprotect +munmap +nanosleep +nl_langinfo +open +open64 +opendir +pci_device_map_range +pci_device_unmap_range +posix_memalign +pthread_self +pthread_sigmask +pthread_yield +putenv +read +readdir +readdir64 +realloc +realpath +__realpath_chk +__register_frame_info_bases +rename +RRCrtcNotify +RRGetInfo +RRScreenSizeSet +RRTellChanged +setenv +ShadowFBInit2 +sigdelset +sigfillset +snprintf +__snprintf_chk +sprintf +__sprintf_chk +sscanf +__stack_chk_fail +stat +stderr +strchr +strcmp +strcpy +strlen +strncat +__strncat_chk +strncmp +strncpy +__strncpy_chk +strpbrk +strstr +strtoul +__strtoul_internal +SwappedProcVector +symlink +tolower +unlink +unsetenv +utimes +vfprintf +__vfprintf_chk +vgaHWFreeHWRec +vgaHWGetHWRec +vgaHWGetIndex +vgaHWGetIOBase +vgaHWRestore +vgaHWSave +vgaHWSetStdFuncs +vsnprintf +__vsnprintf_chk +write +xf86AddGeneralHandler +xf86CreateCursorInfoRec +xf86CrtcConfigInit +xf86CrtcConfigPrivateIndex +xf86CrtcCreate +xf86CrtcScreenInit +xf86CrtcSetMode +xf86CrtcSetSizeRange +xf86DestroyCursorInfoRec +xf86DPMSSet +xf86InitCursor +xf86InitialConfiguration +xf86InterpretEDID +xf86ModesAdd +xf86OutputCreate +xf86OutputSetEDID +xf86OutputUseScreenMonitor +xf86RandR12GetOriginalVirtualSize +xf86RemoveGeneralHandler +xf86SaveScreen +xf86ScreenToScrn +xf86ScrnToScreen +xf86SetDesiredModes +xf86SetModeDefaultName +xf86SetSingleMode +xf86UpdateDesktopDimensions +XNFcallocarray +__xstat64 +__assert_c99 diff --git a/src/VBox/Additions/x11/vboxmouse/Makefile.kmk b/src/VBox/Additions/x11/vboxmouse/Makefile.kmk new file mode 100644 index 00000000..bebc3234 --- /dev/null +++ b/src/VBox/Additions/x11/vboxmouse/Makefile.kmk @@ -0,0 +1,296 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the VBox Additions XFree86 and X.org mouse drivers. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +vboxmouse_xorg_INCS = \ + $(VBOX_PATH_X11_ROOT)/inputproto-1.9.99.902 \ + $(VBOX_PATH_X11_ROOT)/libpciaccess-0.10.8 \ + $(VBOX_PATH_X11_ROOT)/pixman-0.40.0 \ + $(VBOX_PATH_X11_ROOT)/xextproto-7.1.1 \ + $(VBOX_PATH_X11_ROOT)/xproto-7.0.31 + +# +# vboxmouse_drv +# +if1of ($(KBUILD_TARGET), linux) + SYSMODS += vboxmouse_drv + vboxmouse_drv_TEMPLATE = VBoxGuestR3XFree86Mod + vboxmouse_drv_DEFS.linux = linux + vboxmouse_drv_DEFS.x86 += __i386__ + # This one has to be defined when building server code on systems where + # unsigned long is 64bits + vboxmouse_drv_DEFS.amd64 += _XSERVER64 + vboxmouse_drv_DEFS += \ + _POSIX_C_SOURCE=199309L _POSIX_SOURCE _XOPEN_SOURCE _DEFAULT_SOURCE \ + _BSD_SOURCE _SVID_SOURCE _GNU_SOURCE SHAPE XINPUT XKB LBX XAPPGROUP \ + XCSECURITY TOGCUP XF86BIGFONT DPMSExtension PIXPRIV PANORAMIX RENDER \ + GCCUSESGAS AVOID_GLYPHBLT PIXPRIV SINGLEDEPTH XFreeXDGA XvExtension \ + XFree86LOADER XFree86Server XF86VIDMODE XvMCExtension SMART_SCHEDULE \ + BUILDDEBUG X_BYTE_ORDER=X_LITTLE_ENDIAN DNDEBUG FUNCPROTO=15 NARROWPROTO \ + IN_MODULE XFree86Module PNP_MOUSE IN_XF86_MODULE + vboxmouse_drv_INCS := \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3 \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11 \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11/extensions \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3/Xserver \ + $(PATH_SUB_CURRENT) + vboxmouse_drv_SOURCES = \ + vboxmouse.c + # Any global symbols in the driver object files will be added to XFree86's + # symbol table, which can cause problems if we e.g. define a symbol in two + # modules. + vboxmouse_drv_POST_CMDS = \ + objcopy --keep-global-symbol vboxmouseModuleData $(out) $(out)-objcopy$$(NLTAB) \ + $(MV) -f $(out)-objcopy $(out) +endif + + +# +# vboxmouse_drv_70 +# +DLLS += vboxmouse_drv_70 +vboxmouse_drv_70_TEMPLATE = VBoxGuestR3XOrgMod +vboxmouse_drv_70_DEFS = \ + XFree86Server IN_MODULE XFree86Module XFree86LOADER XINPUT XORG_7X IN_XF86_MODULE DONT_DEFINE_WRAPPERS NO_ANSIC +vboxmouse_drv_70_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.0.1 \ + $(PATH_SUB_CURRENT) +vboxmouse_drv_70_SOURCES = \ + vboxmouse.c + + +# +# vboxmouse_drv_71 +# +DLLS += vboxmouse_drv_71 +vboxmouse_drv_71_TEMPLATE = VBoxGuestR3XOrgMod +vboxmouse_drv_71_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC +vboxmouse_drv_71_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.1.0 \ + $(PATH_SUB_CURRENT) +vboxmouse_drv_71_SOURCES = \ + vboxmouse.c + + +# +# vboxmouse_drv_13 +# +DLLS += vboxmouse_drv_13 +vboxmouse_drv_13_TEMPLATE = VBoxGuestR3XOrgMod +vboxmouse_drv_13_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC +vboxmouse_drv_13_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.3.0.0 \ + $(PATH_SUB_CURRENT) +vboxmouse_drv_13_SOURCES = \ + vboxmouse.c + + +# +# vboxmouse_drv_14 +# +DLLS += vboxmouse_drv_14 +vboxmouse_drv_14_TEMPLATE = VBoxGuestR3XOrgMod +vboxmouse_drv_14_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC +vboxmouse_drv_14_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.4.2 \ + $(PATH_SUB_CURRENT) +vboxmouse_drv_14_SOURCES = \ + vboxmouse.c + + +# +# vboxmouse_drv_15 +# +DLLS += vboxmouse_drv_15 +vboxmouse_drv_15_TEMPLATE = VBoxGuestR3XOrgMod +vboxmouse_drv_15_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC +vboxmouse_drv_15_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.5.3 \ + $(PATH_SUB_CURRENT) +vboxmouse_drv_15_SOURCES = \ + vboxmouse.c + + +# +# vboxmouse_drv_16 +# +DLLS += vboxmouse_drv_16 +vboxmouse_drv_16_TEMPLATE = VBoxGuestR3XOrgMod +vboxmouse_drv_16_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC +vboxmouse_drv_16_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.6.5 \ + $(PATH_SUB_CURRENT) +vboxmouse_drv_16_SOURCES = \ + vboxmouse.c + + +ifneq ($(KBUILD_TARGET),linux) + + # + # vboxmouse_drv_17 + # + DLLS += vboxmouse_drv_17 + vboxmouse_drv_17_TEMPLATE = VBoxGuestR3XOrgMod + vboxmouse_drv_17_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC + vboxmouse_drv_17_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.7.7 \ + $(PATH_SUB_CURRENT) + vboxmouse_drv_17_SOURCES = \ + vboxmouse.c + + + # + # vboxmouse_drv_18 + # + DLLS += vboxmouse_drv_18 + vboxmouse_drv_18_TEMPLATE = VBoxGuestR3XOrgMod + vboxmouse_drv_18_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC + vboxmouse_drv_18_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.8.0 \ + $(PATH_SUB_CURRENT) + vboxmouse_drv_18_SOURCES = \ + vboxmouse.c + + + # + # vboxmouse_drv_19 + # + DLLS += vboxmouse_drv_19 + vboxmouse_drv_19_TEMPLATE = VBoxGuestR3XOrgMod + vboxmouse_drv_19_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC + vboxmouse_drv_19_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.9.0 \ + $(PATH_SUB_CURRENT) + vboxmouse_drv_19_SOURCES = \ + vboxmouse.c + + + # + # vboxmouse_drv_110 + # + DLLS += vboxmouse_drv_110 + vboxmouse_drv_110_TEMPLATE = VBoxGuestR3XOrgMod + vboxmouse_drv_110_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC + vboxmouse_drv_110_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.10.0 \ + $(PATH_SUB_CURRENT) + vboxmouse_drv_110_SOURCES = \ + vboxmouse.c + + DLLS += vboxmouse_drv_111 + vboxmouse_drv_111_TEMPLATE = VBoxGuestR3XOrgMod + vboxmouse_drv_111_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC + vboxmouse_drv_111_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.11.0 \ + $(PATH_SUB_CURRENT) + vboxmouse_drv_111_SOURCES = \ + vboxmouse.c + + DLLS += vboxmouse_drv_112 + vboxmouse_drv_112_TEMPLATE = VBoxGuestR3XOrgMod + vboxmouse_drv_112_DEFS := $(vboxmouse_drv_70_DEFS) NO_ANSIC + vboxmouse_drv_112_INCS := \ + $(vboxmouse_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.12.0 \ + $(PATH_SUB_CURRENT) + vboxmouse_drv_112_SOURCES = \ + vboxmouse.c + +endif # neq ($(KBUILD_TARGET),linux) + + +ifdef VBOX_USE_SYSTEM_XORG_HEADERS + # As vboxmouse_drv is not needed at all for X.Org Server 1.7 and later do not + # build it in this case. + DLLS := $(filter-out vboxmouse_drv_%,$(DLLS)) + SYSMODS := $(filter-out vboxmouse_drv%,$(SYSMODS)) +endif + + +# Check the undefined symbols in the X.Org modules against lists of allowed +# symbols. Not very elegant, but it will catch problems early. + +ifdef VBOX_WITH_TESTCASES + # ifndef VBOX_ONLY_ADDITIONS + ifndef VBOX_USE_SYSTEM_XORG_HEADERS + ifeq ($(KBUILD_TARGET),linux) + ifeq ($(KBUILD_HOST_ARCH),$(KBUILD_TARGET_ARCH)) + ifndef VBOX_ONLY_SDK + VBOXMOUSE_SRC_PATH := $(PATH_SUB_CURRENT) + + ifeq ($(KBUILD_TARGET),linux) + TESTING += $(vboxmouse_drv_0_OUTDIR)/tstvboxmouse68.run + OTHERS += $(vboxmouse_drv_0_OUTDIR)/tstvboxmouse68.run + $$(vboxmouse_drv_0_OUTDIR)/tstvboxmouse68.run: $$(vboxmouse_drv_1_STAGE_TARGET) + $(QUIET)$(call MSG_L1,Checking for unresolved symbols in $<) + $(QUIET)/bin/sh $(PATH_ROOT)/src/bldprogs/checkUndefined.sh $(KBUILD_HOST) \ + "$(vboxmouse_drv_1_STAGE_TARGET)" --static "$(VBOXMOUSE_SRC_PATH)/../undefined_xfree86" "$(VBOXMOUSE_SRC_PATH)/../undefined_xfree86_modules" + $(QUIET)$(APPEND) -t "$@" "done" + endif + + ## + # Using the extra expansion to replace $(ver) before eval, thus everything + # else needs escaped dollars. + define def_vboxmouse_test + TESTING += $$(vboxmouse_drv$(ver)_0_OUTDIR)/tstvboxmouse$(ver).run + OTHERS += $$(vboxmouse_drv$(ver)_0_OUTDIR)/tstvboxmouse$(ver).run + $$$$(vboxmouse_drv$(ver)_0_OUTDIR)/tstvboxmouse$(ver).run: $$$$(vboxmouse_drv$(ver)_1_STAGE_TARGET) + $$(QUIET)$$(call MSG_L1,Checking for unresolved symbols in $$<) + $$(QUIET)$$(ASH) $$(PATH_ROOT)/src/bldprogs/checkUndefined.sh $$(KBUILD_HOST) \ + $$(vboxmouse_drv$(ver)_1_STAGE_TARGET) $$(VBOXMOUSE_SRC_PATH)/../undefined_xfree86 $(VBOXMOUSE_SRC_PATH)/../undefined_xfree86_modules $$(VBOXMOUSE_SRC_PATH)/../undefined_xorg + $$(QUIET)$$(APPEND) -t "$$@" "done" + endef + + $(foreach ver, _70 _71 _13 _14 _15 _16, $(eval $(def_vboxmouse_test))) + + ifneq ($(KBUILD_TARGET),linux) + $(foreach ver, _17 _18 _19 _110 _111 _112 _113, $(eval $(def_vboxmouse_test))) + + endif # neq ($(KBUILD_TARGET),linux) + + endif # ! VBOX_ONLY_SDK + endif # eq ($(KBUILD_HOST_ARCH),$(KBUILD_TARGET_ARCH)) + endif # eq ($(KBUILD_TARGET),linux) + endif # ! VBOX_USE_SYSTEM_XORG_HEADERS + # endif # ! VBOX_ONLY_ADDITIONS +endif # VBOX_WITH_TESTCASES + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/Additions/x11/vboxmouse/vboxmouse.c b/src/VBox/Additions/x11/vboxmouse/vboxmouse.c new file mode 100644 index 00000000..dfdc4f31 --- /dev/null +++ b/src/VBox/Additions/x11/vboxmouse/vboxmouse.c @@ -0,0 +1,374 @@ +/* $Id: vboxmouse.c $ */ +/** @file + * VirtualBox X11 Guest Additions, mouse driver for X.Org server 1.5 + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * 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 GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + * -------------------------------------------------------------------- + * + * This code is based on evdev.c from X.Org with the following copyright + * and permission notice: + * + * Copyright © 2004-2008 Red Hat, Inc. + * + * Permission to use, copy, modify, distribute, and sell this software + * and its documentation for any purpose is hereby granted without + * fee, provided that the above copyright notice appear in all copies + * and that both that copyright notice and this permission notice + * appear in supporting documentation, and that the name of Red Hat + * not be used in advertising or publicity pertaining to distribution + * of the software without specific, written prior permission. Red + * Hat makes no representations about the suitability of this software + * for any purpose. It is provided "as is" without express or implied + * warranty. + * + * THE AUTHORS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, + * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY SPECIAL, INDIRECT OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, + * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Authors: + * Kristian Høgsberg (krh@redhat.com) + * Adam Jackson (ajax@redhat.com) + */ + +#include <VBox/VMMDev.h> /* for VMMDEV_MOUSE_XXX */ +#include <VBox/VBoxGuestLib.h> +#include <iprt/errcore.h> +#include <xf86.h> +#include <xf86Xinput.h> +#include <mipointer.h> + +#include <xf86Module.h> + +#ifdef VBOX_GUESTR3XF86MOD +# define _X_EXPORT +#else +# include <errno.h> +# include <fcntl.h> +# include <unistd.h> +#endif + +#include "product-generated.h" + +static void +VBoxReadInput(InputInfoPtr pInfo) +{ + uint32_t cx, cy, fFeatures; + + /* Read a byte from the device to acknowledge the event */ + char c; + int res = read(pInfo->fd, &c, 1); + NOREF(res); + /* The first test here is a workaround for an apparent bug in Xorg Server 1.5 */ + if ( +#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 2 + miPointerCurrentScreen() != NULL +#else + miPointerGetScreen(pInfo->dev) != NULL +#endif + && RT_SUCCESS(VbglR3GetMouseStatus(&fFeatures, &cx, &cy)) + && (fFeatures & VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE)) + { +#if ABI_XINPUT_VERSION == SET_ABI_VERSION(2, 0) + /* Bug in the 1.4 X server series - conversion_proc was no longer + * called, but the server didn't yet do the conversion itself. */ + cx = (cx * screenInfo.screens[0]->width) / 65535; + cy = (cy * screenInfo.screens[0]->height) / 65535; +#endif + /* send absolute movement */ + xf86PostMotionEvent(pInfo->dev, 1, 0, 2, cx, cy); + } +} + +static void +VBoxPtrCtrlProc(DeviceIntPtr device, PtrCtrl *ctrl) +{ + /* Nothing to do, dix handles all settings */ + RT_NOREF(device, ctrl); +} + +static int +VBoxInit(DeviceIntPtr device) +{ + CARD8 map[2] = { 0, 1 }; +#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7 + Atom axis_labels[2] = { 0, 0 }; + Atom button_labels[2] = { 0, 0 }; +#endif + if (!InitPointerDeviceStruct((DevicePtr)device, map, 2, +#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7 + button_labels, +#endif +#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 2 + miPointerGetMotionEvents, VBoxPtrCtrlProc, + miPointerGetMotionBufferSize() +#elif GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 3 + GetMotionHistory, VBoxPtrCtrlProc, + GetMotionHistorySize(), 2 /* Number of axes */ + +#elif GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 3 + VBoxPtrCtrlProc, GetMotionHistorySize(), + 2 /* Number of axes */ +#else +# error Unsupported version of X.Org +#endif +#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7 + , axis_labels +#endif + )) + return !Success; + + /* Tell the server about the range of axis values we report */ +#if ABI_XINPUT_VERSION <= SET_ABI_VERSION(2, 0) + xf86InitValuatorAxisStruct(device, 0, 0, -1, 1, 0, 1); + xf86InitValuatorAxisStruct(device, 1, 0, -1, 1, 0, 1); +#else + xf86InitValuatorAxisStruct(device, 0, +# if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7 + axis_labels[0], +# endif + VMMDEV_MOUSE_RANGE_MIN /* min X */, VMMDEV_MOUSE_RANGE_MAX /* max X */, + 10000, 0, 10000 +# if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 12 + , Absolute +# endif + ); + + xf86InitValuatorAxisStruct(device, 1, +# if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 7 + axis_labels[1], +# endif + VMMDEV_MOUSE_RANGE_MIN /* min Y */, VMMDEV_MOUSE_RANGE_MAX /* max Y */, + 10000, 0, 10000 +# if GET_ABI_MAJOR(ABI_XINPUT_VERSION) >= 12 + , Absolute +# endif + ); +#endif + xf86InitValuatorDefaults(device, 0); + xf86InitValuatorDefaults(device, 1); + xf86MotionHistoryAllocate(device->public.devicePrivate); + + return Success; +} + +static int +VBoxProc(DeviceIntPtr device, int what) +{ + InputInfoPtr pInfo; + int rc, xrc; + uint32_t fFeatures = 0; + + pInfo = device->public.devicePrivate; + + switch (what) + { + case DEVICE_INIT: + xrc = VBoxInit(device); + if (xrc != Success) { + VbglR3Term(); + return xrc; + } + break; + + case DEVICE_ON: + xf86Msg(X_INFO, "%s: On.\n", pInfo->name); + if (device->public.on) + break; + /* Tell the host that we want absolute co-ordinates */ + rc = VbglR3GetMouseStatus(&fFeatures, NULL, NULL); + fFeatures &= VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR; + if (RT_SUCCESS(rc)) + rc = VbglR3SetMouseStatus( fFeatures + | VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE + | VMMDEV_MOUSE_NEW_PROTOCOL); + if (!RT_SUCCESS(rc)) { + xf86Msg(X_ERROR, "%s: Failed to switch guest mouse into absolute mode\n", + pInfo->name); + return !Success; + } + + xf86AddEnabledDevice(pInfo); + device->public.on = TRUE; + break; + + case DEVICE_OFF: + xf86Msg(X_INFO, "%s: Off.\n", pInfo->name); + rc = VbglR3GetMouseStatus(&fFeatures, NULL, NULL); + fFeatures &= VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR; + if (RT_SUCCESS(rc)) + rc = VbglR3SetMouseStatus( fFeatures + & ~VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE + & ~VMMDEV_MOUSE_NEW_PROTOCOL); + xf86RemoveEnabledDevice(pInfo); + device->public.on = FALSE; + break; + + case DEVICE_CLOSE: + VbglR3Term(); + xf86Msg(X_INFO, "%s: Close\n", pInfo->name); + break; + + default: + return BadValue; + } + + return Success; +} + +static int +VBoxProbe(InputInfoPtr pInfo) +{ + int rc = VbglR3Init(); + if (!RT_SUCCESS(rc)) { + xf86Msg(X_ERROR, "%s: Failed to open the VirtualBox device (error %d)\n", + pInfo->name, rc); + return BadMatch; + } + + return Success; +} + +static Bool +VBoxConvert(InputInfoPtr pInfo, int first, int num, int v0, int v1, int v2, + int v3, int v4, int v5, int *x, int *y) +{ + RT_NOREF(pInfo, num, v2, v3, v4, v5); + + if (first == 0) { + *x = xf86ScaleAxis(v0, 0, screenInfo.screens[0]->width, 0, 65536); + *y = xf86ScaleAxis(v1, 0, screenInfo.screens[0]->height, 0, 65536); + return TRUE; + } + return FALSE; +} + +static int +VBoxPreInitInfo(InputDriverPtr drv, InputInfoPtr pInfo, int flags) +{ + const char *device; + int rc; + RT_NOREF(drv, flags); + + /* Initialise the InputInfoRec. */ + pInfo->device_control = VBoxProc; + pInfo->read_input = VBoxReadInput; + /* Unlike evdev, we set this unconditionally, as we don't handle keyboards. */ + pInfo->type_name = XI_MOUSE; + pInfo->flags |= XI86_ALWAYS_CORE; + + device = xf86SetStrOption(pInfo->options, "Device", + "/dev/vboxguest"); + + xf86Msg(X_CONFIG, "%s: Device: \"%s\"\n", pInfo->name, device); + do { + pInfo->fd = open(device, O_RDWR, 0); + } + while (pInfo->fd < 0 && errno == EINTR); + + if (pInfo->fd < 0) { + xf86Msg(X_ERROR, "Unable to open VirtualBox device \"%s\".\n", device); + return BadMatch; + } + + rc = VBoxProbe(pInfo); + if (rc != Success) + return rc; + + return Success; +} + +#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 12 +static InputInfoPtr +VBoxPreInit(InputDriverPtr drv, IDevPtr dev, int flags) +{ + InputInfoPtr pInfo = xf86AllocateInput(drv, 0); + if (!pInfo) + return NULL; + + /* Initialise the InputInfoRec. */ + pInfo->name = dev->identifier; + pInfo->conf_idev = dev; + pInfo->conversion_proc = VBoxConvert; + pInfo->flags = XI86_POINTER_CAPABLE | XI86_SEND_DRAG_EVENTS; + + xf86CollectInputOptions(pInfo, NULL, NULL); + xf86ProcessCommonOptions(pInfo, pInfo->options); + + if (VBoxPreInitInfo(drv, pInfo, flags) != Success) { + xf86DeleteInput(pInfo, 0); + return NULL; + } + + pInfo->flags |= XI86_CONFIGURED; + return pInfo; +} +#endif + +_X_EXPORT InputDriverRec VBOXMOUSE = { + 1, + "vboxmouse", + NULL, +#if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 12 + VBoxPreInit, +#else + VBoxPreInitInfo, +#endif + NULL, + NULL, + 0 +}; + +static pointer +VBoxPlug(pointer module, pointer options, int *errmaj, int *errmin) +{ + RT_NOREF(options, errmaj, errmin); + xf86AddInputDriver(&VBOXMOUSE, module, 0); + xf86Msg(X_CONFIG, "Load address of symbol \"VBOXMOUSE\" is %p\n", + (void *)&VBOXMOUSE); + return module; +} + +static XF86ModuleVersionInfo VBoxVersionRec = +{ + "vboxmouse", + VBOX_VENDOR, + MODINFOSTRING1, + MODINFOSTRING2, + 0, /* Missing from SDK: XORG_VERSION_CURRENT, */ + 1, 0, 0, + ABI_CLASS_XINPUT, + ABI_XINPUT_VERSION, + MOD_CLASS_XINPUT, + {0, 0, 0, 0} +}; + +_X_EXPORT XF86ModuleData vboxmouseModuleData = +{ + &VBoxVersionRec, + VBoxPlug, + NULL +}; diff --git a/src/VBox/Additions/x11/vboxvideo/HGSMIMemAlloc.h b/src/VBox/Additions/x11/vboxvideo/HGSMIMemAlloc.h new file mode 100644 index 00000000..7c8212a0 --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/HGSMIMemAlloc.h @@ -0,0 +1,63 @@ +/* $Id: HGSMIMemAlloc.h $ */ +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + + +/* In builds inside of the VirtualBox source tree we override the default + * HGSMIMemAlloc.h using -include, therefore this define must match the one + * there. */ + +#ifndef VBOX_INCLUDED_Graphics_HGSMIMemAlloc_h +#define VBOX_INCLUDED_Graphics_HGSMIMemAlloc_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "HGSMIDefs.h" +#include "VBoxVideoIPRT.h" + +#define HGSMI_MA_DESC_ORDER_BASE UINT32_C(5) + +#define HGSMI_MA_BLOCK_SIZE_MIN (UINT32_C(1) << (HGSMI_MA_DESC_ORDER_BASE + 0)) + +typedef struct HGSMIMADATA +{ + HGSMIAREA area; + bool fAllocated; +} HGSMIMADATA; + +RT_C_DECLS_BEGIN + +int HGSMIMAInit(HGSMIMADATA *pMA, const HGSMIAREA *pArea, + HGSMIOFFSET *paDescriptors, uint32_t cDescriptors, + HGSMISIZE cbMaxBlock, const HGSMIENV *pEnv); +void HGSMIMAUninit(HGSMIMADATA *pMA); + +void RT_UNTRUSTED_VOLATILE_GUEST *HGSMIMAAlloc(HGSMIMADATA *pMA, HGSMISIZE cb); +void HGSMIMAFree(HGSMIMADATA *pMA, void RT_UNTRUSTED_VOLATILE_GUEST *pv); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_Graphics_HGSMIMemAlloc_h */ diff --git a/src/VBox/Additions/x11/vboxvideo/Makefile.kmk b/src/VBox/Additions/x11/vboxvideo/Makefile.kmk new file mode 100644 index 00000000..6f98670d --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/Makefile.kmk @@ -0,0 +1,467 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the VBox Linux Additions X.org graphics driver. +# + +# +# Copyright (C) 2006-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# 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 GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +vboxvideo_70_DEFS := \ + IN_MODULE XORG_7X RENDER=1 IN_RT_STATIC X_BYTE_ORDER=X_LITTLE_ENDIAN +ifeq ($(KBUILD_TARGET),solaris) # don't use .solaris or anything here. + vboxvideo_70_DEFS += __EXTENSIONS__ ## @todo Why this? +endif +vboxvideo_13_DEFS := $(vboxvideo_70_DEFS) VBOXVIDEO_13 +vboxvideo_15_DEFS := \ + $(vboxvideo_13_DEFS) NO_ANSIC PCIACCESS XSERVER_LIBPCIACCESS _XORG_SERVER_H_ _DIX_CONFIG_H_ +vboxvideo_xorg_INCS = \ + $(VBOX_PATH_X11_ROOT)/fontsproto-2.1.3 \ + $(VBOX_PATH_X11_ROOT)/inputproto-1.9.99.902 \ + $(VBOX_PATH_X11_ROOT)/kbproto-1.0.7 \ + $(VBOX_PATH_X11_ROOT)/libpciaccess-0.10.8 \ + $(VBOX_PATH_X11_ROOT)/pixman-0.40.0 \ + $(VBOX_PATH_X11_ROOT)/randrproto-1.5.0 \ + $(VBOX_PATH_X11_ROOT)/renderproto-0.11.1 \ + $(VBOX_PATH_X11_ROOT)/xextproto-7.1.1 \ + $(VBOX_PATH_X11_ROOT)/xproto-7.0.31 \ + $(VBOX_GRAPHICS_INCS) +vboxvideo_override_INCLUDES = \ + -include $(PATH_ROOT)/src/VBox/Additions/x11/vboxvideo/VBoxVideoIPRT.h \ + -include $(PATH_ROOT)/src/VBox/Additions/x11/vboxvideo/HGSMIMemAlloc.h + +LIBRARIES += vboxvideo_drv_lib + +# +# vboxvideo_drv_lib +# +vboxvideo_drv_lib_TEMPLATE = VBoxGuestR3XOrgMod +# We are relying in the include guard in the two headers below to stop the more +# generic ones from being included. Not very nice, I know. +vboxvideo_drv_lib_CFLAGS += $(vboxvideo_override_INCLUDES) +vboxvideo_drv_lib_CXXFLAGS += $(vboxvideo_override_INCLUDES) +ifeq ($(KBUILD_TARGET),solaris) # don't use .solaris or anything here. Do we need this? I don't want to find out. + vboxvideo_drv_lib_CFLAGS += -D_XPG6 -Wno-shadow # Use XPG6 until we have moved the C++ bits into a library. +endif +vboxvideo_drv_lib_SOURCES = \ + $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/HGSMIBase.cpp \ + $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/HGSMIBuffers.cpp \ + $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/Modesetting.cpp \ + $(PATH_ROOT)/src/VBox/Additions/common/VBoxVideo/VBVABase.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/HGSMI/HGSMICommon.cpp \ + $(PATH_ROOT)/src/VBox/Additions/x11/vboxvideo/hgsmimemalloc.c +# $(VBOX_PATH_X11_ROOT)/xorg-server-1.18.0 is for in[blw] and out[blw], xproto +# for _X_[UN]LIKELY. +vboxvideo_drv_lib_INCS = \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.18.0 \ + $(VBOX_PATH_X11_ROOT)/xproto-7.0.18 \ + $(VBOX_PATH_X11_ROOT)/pixman-0.40.0 \ + $(PATH_ROOT)/src/VBox/Runtime/include \ + $(VBOX_GRAPHICS_INCS) +vboxvideo_drv_lib_INST = $(INST_LIB) + +# +# vboxvideo_drv +# +if1of ($(KBUILD_TARGET), linux) + SYSMODS += vboxvideo_drv +endif # target linux +vboxvideo_drv_TEMPLATE = VBoxGuestR3XFree86Mod +vboxvideo_drv_CFLAGS += $(vboxvideo_override_INCLUDES) +vboxvideo_drv_CFLAGS += -Wno-shadow # Avoid MBs of warnings in X11 and OpenGL headers (solaris mostly). +vboxvideo_drv_CXXFLAGS += $(vboxvideo_override_INCLUDES) +vboxvideo_drv_DEFS.linux = linux +vboxvideo_drv_DEFS.x86 = __i386__ +# This one has to be defined when building server code on systems where +# unsigned long is 64bits +vboxvideo_drv_DEFS.amd64 += _XSERVER64 +vboxvideo_drv_DEFS = \ + _POSIX_C_SOURCE=199309L _POSIX_SOURCE _XOPEN_SOURCE _DEFAULT_SOURCE \ + _BSD_SOURCE _SVID_SOURCE _GNU_SOURCE SHAPE XINPUT XKB LBX XAPPGROUP \ + XCSECURITY TOGCUP XF86BIGFONT DPMSExtension PIXPRIV PANORAMIX RENDER \ + GCCUSESGAS AVOID_GLYPHBLT PIXPRIV SINGLEDEPTH XFreeXDGA XvExtension \ + XFree86LOADER XFree86Server XF86VIDMODE XvMCExtension SMART_SCHEDULE \ + BUILDDEBUG X_BYTE_ORDER=X_LITTLE_ENDIAN DNDEBUG FUNCPROTO=15 NARROWPROTO \ + IN_MODULE XFree86Module IN_XF86_MODULE IN_RT_STATIC +vboxvideo_drv_INCS = \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3 \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11 \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11/extensions \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3/X11/fonts \ + $(VBOX_PATH_X11_ROOT)/XFree86-4.3/Xserver +vboxvideo_drv_INCS += \ + $(PATH_ROOT)/src/VBox/Runtime/include \ + $(VBOX_GRAPHICS_INCS) +vboxvideo_drv_SOURCES = \ + getmode.c \ + pointer.c \ + setmode.c \ + vboxvideo.c \ + vbva.c \ + $(vboxvideo_drv_lib_SOURCES) +# Any global symbols in the driver object files will be added to XFree86's +# symbol table, which can cause problems if we e.g. define a symbol in two +# modules. +vboxvideo_drv_POST_CMDS = \ + objcopy --keep-global-symbol vboxvideoModuleData $(out) $(out)-objcopy$$(NLTAB) \ + $(MV) -f $(out)-objcopy $(out) + +# +# vboxvideo_drv_70 +# +# Remark: The other X.org drivers below are derived from this one. So, to make +# that as simple as possible we do ifeq/if1of test here and extends the +# base keywords instead of using .solaris or .linux. +# Also it is *important* to use := and not = when deriving a property. +# +DLLS += vboxvideo_drv_70 +vboxvideo_drv_70_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_70_DEFS = $(vboxvideo_70_DEFS) XORG_VERSION_CURRENT=70000000 +vboxvideo_drv_70_CFLAGS += $(vboxvideo_override_INCLUDES) +ifeq ($(KBUILD_TARGET),solaris) # don't use .solaris or anything here. + vboxvideo_drv_70_CFLAGS += -D_XPG6 -Wno-shadow # Use XPG6 until we have moved the C++ bits into a library. +endif +vboxvideo_drv_70_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.0.1 +vboxvideo_drv_70_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_70_SOURCES = $(filter-out $(vboxvideo_drv_lib_SOURCES),$(vboxvideo_drv_SOURCES)) +vboxvideo_drv_70_LIBS = $(PATH_STAGE_LIB)/vboxvideo_drv_lib$(VBOX_SUFF_LIB) + + +# +# vboxvideo_drv_71 +# +DLLS += vboxvideo_drv_71 +vboxvideo_drv_71_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_71_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_71_DEFS := $(vboxvideo_70_DEFS) XORG_VERSION_CURRENT=70100000 +vboxvideo_drv_71_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.1.0 +vboxvideo_drv_71_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_71_SOURCES = $(vboxvideo_drv_70_SOURCES) +vboxvideo_drv_71_LIBS = $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_13 +# +DLLS += vboxvideo_drv_13 +vboxvideo_drv_13_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_13_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_13_DEFS := $(vboxvideo_13_DEFS) XORG_VERSION_CURRENT=10300000 +vboxvideo_drv_13_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.3.0.0 +vboxvideo_drv_13_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_13_SOURCES = $(vboxvideo_drv_70_SOURCES) edid.c +vboxvideo_drv_13_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_14 +# +DLLS += vboxvideo_drv_14 +vboxvideo_drv_14_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_14_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_14_DEFS := $(vboxvideo_13_DEFS) XORG_VERSION_CURRENT=10400000 +vboxvideo_drv_14_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.4.2 +vboxvideo_drv_14_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_14_SOURCES = $(vboxvideo_drv_13_SOURCES) +vboxvideo_drv_14_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_15 +# +DLLS += vboxvideo_drv_15 +vboxvideo_drv_15_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_15_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_15_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=10503000 +vboxvideo_drv_15_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.5.3 +vboxvideo_drv_15_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_15_SOURCES = $(vboxvideo_drv_13_SOURCES) +vboxvideo_drv_15_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_16 +# +DLLS += vboxvideo_drv_16 +vboxvideo_drv_16_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_16_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_16_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=10600000 +vboxvideo_drv_16_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.6.5 \ + vboxvideo_drv_16_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_16_SOURCES := $(vboxvideo_drv_15_SOURCES) +vboxvideo_drv_16_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_17 +# +DLLS += vboxvideo_drv_17 +vboxvideo_drv_17_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_17_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_17_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=10699000 +vboxvideo_drv_17_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.7.7 +vboxvideo_drv_17_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_17_SOURCES := $(vboxvideo_drv_13_SOURCES) +vboxvideo_drv_17_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_18 +# +DLLS += vboxvideo_drv_18 +vboxvideo_drv_18_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_18_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_18_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=10800000 +vboxvideo_drv_18_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.8.0 +vboxvideo_drv_18_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_18_SOURCES := $(vboxvideo_drv_17_SOURCES) +vboxvideo_drv_18_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_19 +# +DLLS += vboxvideo_drv_19 +vboxvideo_drv_19_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_19_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_19_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=10900000 +vboxvideo_drv_19_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.9.0 +vboxvideo_drv_19_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_19_SOURCES := $(vboxvideo_drv_17_SOURCES) +vboxvideo_drv_19_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_110 +# +DLLS += vboxvideo_drv_110 +vboxvideo_drv_110_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_110_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_110_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11000000 +vboxvideo_drv_110_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.10.0 +vboxvideo_drv_110_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_110_SOURCES := $(vboxvideo_drv_17_SOURCES) +vboxvideo_drv_110_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_111 +# +DLLS += vboxvideo_drv_111 +vboxvideo_drv_111_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_111_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_111_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11100000 +vboxvideo_drv_111_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.11.0 +vboxvideo_drv_111_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_111_SOURCES := $(vboxvideo_drv_17_SOURCES) +vboxvideo_drv_111_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_112 +# +DLLS += vboxvideo_drv_112 +vboxvideo_drv_112_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_112_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_112_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11200000 +vboxvideo_drv_112_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.12.0 +vboxvideo_drv_112_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_112_SOURCES := $(vboxvideo_drv_17_SOURCES) +vboxvideo_drv_112_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_113 +# +DLLS += vboxvideo_drv_113 +vboxvideo_drv_113_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_113_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_113_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11300000 +vboxvideo_drv_113_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.13.0 +vboxvideo_drv_113_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_113_SOURCES := $(vboxvideo_drv_17_SOURCES) +vboxvideo_drv_113_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_114 +# +DLLS += vboxvideo_drv_114 +vboxvideo_drv_114_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_114_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_114_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11400000 +vboxvideo_drv_114_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.14.0 +vboxvideo_drv_114_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_114_SOURCES := $(vboxvideo_drv_17_SOURCES) +vboxvideo_drv_114_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_115 +# +DLLS += vboxvideo_drv_115 +vboxvideo_drv_115_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_115_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_115_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11500000 +vboxvideo_drv_115_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.15.0 +vboxvideo_drv_115_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_115_SOURCES := $(vboxvideo_drv_17_SOURCES) +vboxvideo_drv_115_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_116 +# +DLLS += vboxvideo_drv_116 +vboxvideo_drv_116_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_116_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_116_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11600000 +vboxvideo_drv_116_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.16.0 +vboxvideo_drv_116_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_116_SOURCES := $(vboxvideo_drv_17_SOURCES) +vboxvideo_drv_116_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_117 +# +DLLS += vboxvideo_drv_117 +vboxvideo_drv_117_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_117_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_117_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11700000 +vboxvideo_drv_117_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.17.1 +vboxvideo_drv_117_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_117_SOURCES := $(vboxvideo_drv_17_SOURCES) +vboxvideo_drv_117_LIBS += $(vboxvideo_drv_70_LIBS) + + +# +# vboxvideo_drv_118 +# +DLLS += vboxvideo_drv_118 +vboxvideo_drv_118_TEMPLATE = VBoxGuestR3XOrgMod +vboxvideo_drv_118_CFLAGS := $(vboxvideo_drv_70_CFLAGS) +vboxvideo_drv_118_DEFS := $(vboxvideo_15_DEFS) XORG_VERSION_CURRENT=11800000 +vboxvideo_drv_118_INCS = \ + $(vboxvideo_xorg_INCS) \ + $(VBOX_PATH_X11_ROOT)/xorg-server-1.18.0 +vboxvideo_drv_118_INCS += $(PATH_ROOT)/src/VBox/Runtime/include +vboxvideo_drv_118_SOURCES := $(vboxvideo_drv_17_SOURCES) +vboxvideo_drv_118_LIBS += $(vboxvideo_drv_70_LIBS) + +ifdef VBOX_USE_SYSTEM_XORG_HEADERS + # Build using local X.Org headers. We assume X.Org Server 1.7 or later. + DLLS := $(filter-out vboxvideo_drv_%,$(DLLS)) vboxvideo_drv_system + SYSMODS := $(filter-out vboxvideo_drv%,$(SYSMODS)) + vboxvideo_drv_system_TEMPLATE = VBoxGuestR3XOrgMod + vboxvideo_drv_system_CFLAGS := \ + $(vboxvideo_drv_70_CFLAGS) -include xorg-server.h + vboxvideo_drv_system_DEFS := $(filter-out _XORG_SERVER_H_ _DIX_CONFIG_H_, $(vboxvideo_15_DEFS)) + vboxvideo_drv_system_INCS += \ + $(PATH_ROOT)/src/VBox/Runtime/include \ + $(VBOX_GRAPHICS_INCS) \ + /usr/include/xorg \ + /usr/include/pixman-1 + vboxvideo_drv_system_SOURCES := $(vboxvideo_drv_17_SOURCES) +endif + + +# Check the undefined symbols in the X.Org modules against lists of allowed +# symbols. Not very elegant, but it will catch problems early. + +ifdef VBOX_WITH_TESTCASES + # ifndef VBOX_ONLY_ADDITIONS + ifndef VBOX_USE_SYSTEM_XORG_HEADERS + if1of ($(KBUILD_TARGET), linux solaris) + ifeq ($(KBUILD_HOST_ARCH),$(KBUILD_TARGET_ARCH)) + ifndef VBOX_ONLY_SDK + VBOXVIDEO_SRC_PATH := $(PATH_SUB_CURRENT) + + ifeq ($(KBUILD_TARGET),linux) + TESTING += $(vboxvideo_drv_0_OUTDIR)/tstvboxvideo68.run + OTHERS += $(vboxvideo_drv_0_OUTDIR)/tstvboxvideo68.run + $$(vboxvideo_drv_0_OUTDIR)/tstvboxvideo68.run: $$(vboxvideo_drv_1_STAGE_TARGET) + $(QUIET)$(call MSG_L1,Checking for unresolved symbols in $<) + $(QUIET)$(ASH) $(PATH_ROOT)/src/bldprogs/checkUndefined.sh $(KBUILD_HOST) \ + $(vboxvideo_drv_1_STAGE_TARGET) --static $(VBOXVIDEO_SRC_PATH)/../undefined_xfree86 $(VBOXVIDEO_SRC_PATH)/../undefined_xfree86_modules + $(QUIET)$(APPEND) -t "$@" "done" + endif + + ## + # Using the extra expansion to replace $(ver) before eval, thus everything + # else needs escaped dollars. + define def_vboxvideo_test + TESTING += $$(vboxvideo_drv$(ver)_0_OUTDIR)/tstvboxvideo$(ver).run + OTHERS += $$(vboxvideo_drv$(ver)_0_OUTDIR)/tstvboxvideo$(ver).run + $$$$(vboxvideo_drv$(ver)_0_OUTDIR)/tstvboxvideo$(ver).run: $$$$(vboxvideo_drv$(ver)_1_STAGE_TARGET) + $$(QUIET)$$(call MSG_L1,Checking for unresolved symbols in $$<) + $$(QUIET)$$(ASH) $$(PATH_ROOT)/src/bldprogs/checkUndefined.sh $$(KBUILD_HOST) \ + $$(vboxvideo_drv$(ver)_1_STAGE_TARGET) $$(VBOXVIDEO_SRC_PATH)/../undefined_xfree86 $(VBOXVIDEO_SRC_PATH)/../undefined_xfree86_modules $$(VBOXVIDEO_SRC_PATH)/../undefined_xorg + $$(QUIET)$$(APPEND) -t "$$@" "done" + endef + + $(foreach ver, _70 _71 _13 _14 _15 _16 _17 _18 _19 _110 _111 _112 _113 _114 _115 _116 _117 _118, $(eval $(def_vboxvideo_test))) + + endif # ! VBOX_ONLY_SDK + endif # eq ($(KBUILD_HOST_ARCH),$(KBUILD_TARGET_ARCH)) + endif # 1of ($(KBUILD_TARGET),linux solaris) + endif # ! VBOX_USE_SYSTEM_XORG_HEADERS + # endif # ! VBOX_ONLY_ADDITIONS +endif # VBOX_WITH_TESTCASES + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/Additions/x11/vboxvideo/README.testing b/src/VBox/Additions/x11/vboxvideo/README.testing new file mode 100644 index 00000000..03d6482f --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/README.testing @@ -0,0 +1,33 @@ +This file contains some notes about things to try out to give the X.Org video +driver a reasonably thorough test. We will add cases of things which have been +known to fail in the past to this file as we discover them. Tests should be +carried out with Additions installed, and both with and without 3D enabled in +the machine settings. + + * Test XFree86 guests (CentOS 3), early X.Org (CentOS 5) and recent + (CentOS 6 and 7, current Ubuntu/Fedora). Test Solaris guests (10 and 11?). + * Dynamic resizing should work, on CentOS 6 and later Linux guests it should + work without VBoxClient running. + * Disabling and enabling virtual screens (VBoxManage in 4.3). + * Dynamic resizing with one of more virtual screens disabled. + * Test switching to virtual terminals and back from windowed, full screen and + seamless modes (seamless currently only works properly with VBoxClient + running). + * Test switching directly between normal, full-screen, seamless and scaled + modes. + * Test re-ordering the virtual screen using the native guest operating system + tools and make sure that mouse integration still works as expected. + * Test disabling and re-enabling guest screens with the native system tools. + * Try disabling and re-enabling mouse integration and check that capturing + works with multiple guest screens. + * Shutting down and re-starting a virtual machine should restore the last size + for all monitors (note: currently only after log-in). Full shut-down, not + a reboot. + * Test power management by disabling guest screens ("xrandr --output VGA-n + --off") and re-enabling them ("xrandr --output VGA-n --preferred --pos XxY") + where X and Y are the position of the screen before disabling it. + * Test sending video mode hints with screen position information via + VBoxManage. The screen position is a hint only. The approximate position + should be preserved after a shut down and re-start of the guest. + * Test re-starting the X server after resizing all guest windows. The server + should not crash. diff --git a/src/VBox/Additions/x11/vboxvideo/VBoxVideoIPRT.h b/src/VBox/Additions/x11/vboxvideo/VBoxVideoIPRT.h new file mode 100644 index 00000000..7b7ecf6e --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/VBoxVideoIPRT.h @@ -0,0 +1,243 @@ +/* $Id: VBoxVideoIPRT.h $ */ +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/* In builds inside of the VirtualBox source tree we override the default + * VBoxVideoIPRT.h using -include, therefore this define must match the one + * there. */ + +#ifndef VBOX_INCLUDED_Graphics_VBoxVideoIPRT_h +#define VBOX_INCLUDED_Graphics_VBoxVideoIPRT_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +# include "VBoxVideoErr.h" + +#ifndef __cplusplus +typedef enum +{ + false = 0, + true +} bool; +# define RT_C_DECLS_BEGIN +# define RT_C_DECLS_END +#else +# define RT_C_DECLS_BEGIN extern "C" { +# define RT_C_DECLS_END } +#endif + +#if defined(IN_XF86_MODULE) && !defined(NO_ANSIC) +# ifdef __cplusplus +/* xf86Module.h redefines this. */ +# define NULL 0 +# endif +RT_C_DECLS_BEGIN +# include "xf86_ansic.h" +RT_C_DECLS_END +#endif /* defined(IN_XF86_MODULE) && !defined(NO_ANSIC) */ +#define __STDC_LIMIT_MACROS /* define *INT*_MAX on C++ too. */ +#include "compiler.h" /* Can pull in <sdtint.h>. Must come after xf86_ansic.h on XFree86. */ +#include <X11/Xfuncproto.h> +#include <stdint.h> +#if defined(IN_XF86_MODULE) && !defined(NO_ANSIC) +# ifndef offsetof +# define offsetof(type, member) ( (int)(uintptr_t)&( ((type *)(void *)0)->member) ) +# endif +#else /* !(defined(IN_XF86_MODULE) && !defined(NO_ANSIC)) */ +# include <stdarg.h> +# include <stddef.h> +# include <string.h> +#endif /* !(defined(IN_XF86_MODULE) && !defined(NO_ANSIC)) */ + +/* XFree86 (and newer Xfuncproto.h) do not have these. Not that I care much for micro-optimisations + * in most cases anyway. */ +#ifndef _X_LIKELY +# define _X_LIKELY(x) (x) +#endif +#ifndef _X_UNLIKELY +# define _X_UNLIKELY(x) (x) +#endif + +RT_C_DECLS_BEGIN +extern int RTASSERTVAR[1]; +RT_C_DECLS_END + +#define AssertCompile(expr) \ + extern int RTASSERTVAR[1] __attribute__((__unused__)), \ + RTASSERTVAR[(expr) ? 1 : 0] __attribute__((__unused__)) +#define AssertCompileSize(type, size) \ + AssertCompile(sizeof(type) == (size)) +#define AssertPtrNullReturnVoid(a) do { } while(0) + +#if !defined(IN_XF86_MODULE) && defined(DEBUG) +# include <assert.h> +# define Assert assert +# define AssertFailed() assert(0) +# define AssertMsg(expr, msg) \ + do { \ + if (!(expr)) xf86ErrorF msg; \ + assert((expr)); \ + } while (0) +# define AssertPtr assert +# define AssertPtrReturn(pv, rcRet) do { assert(pv); if (pv) {} else return(rcRet); } while(0) +# define AssertRC(expr) assert (!expr) +#else +# define Assert(expr) do { } while(0) +# define AssertFailed() do { } while(0) +# define AssertMsg(expr, msg) do { } while(0) +# define AssertPtr(ptr) do { } while(0) +# define AssertPtrReturn(pv, rcRet) do { if (pv) {} else return(rcRet); } while(0) +# define AssertRC(expr) do { } while(0) +#endif + +#define DECLCALLBACK(a_RetType) a_RetType +#define DECLCALLBACKTYPE(a_RetType, a_Name, a_Args) a_RetType a_Name a_Args +#define DECLCALLBACKMEMBER(a_RetType, a_Name, a_Args) a_RetType (*a_Name) a_Args +#if __GNUC__ >= 4 +# define DECLHIDDEN(type) __attribute__((visibility("hidden"))) type +#else +# define DECLHIDDEN(type) type +#endif +#define DECLINLINE(type) static __inline__ type + +#define _1K 1024 +#define ASMCompilerBarrier mem_barrier +#define RT_BIT(bit) ( 1U << (bit) ) +#define RT_BOOL(Value) ( !!(Value) ) +#define RT_BZERO(pv, cb) do { memset((pv), 0, cb); } while (0) +#define RT_CLAMP(Value, Min, Max) ( (Value) > (Max) ? (Max) : (Value) < (Min) ? (Min) : (Value) ) +#define RT_ELEMENTS(aArray) ( sizeof(aArray) / sizeof((aArray)[0]) ) +#define RTIOPORT unsigned short +#define RT_NOREF(...) (void)(__VA_ARGS__) +#define RT_OFFSETOF(type, member) offsetof(type, member) +#define RT_UOFFSETOF(type, member) offsetof(type, member) +#define RT_ZERO(Obj) RT_BZERO(&(Obj), sizeof(Obj)) +#define RT_VALID_PTR(ptr) ( (uintptr_t)(ptr) + 0x1000U >= 0x2000U ) +#ifndef INT16_C +# define INT16_C(Value) (Value) +#endif +#ifndef UINT16_C +# define UINT16_C(Value) (Value) +#endif +#ifndef INT32_C +# define INT32_C(Value) (Value ## U) +#endif +#ifndef UINT32_C +# define UINT32_C(Value) (Value ## U) +#endif +#define RT_UNTRUSTED_GUEST +#define RT_UNTRUSTED_VOLATILE_GUEST volatile +#define RT_UNTRUSTED_HOST +#define RT_UNTRUSTED_VOLATILE_HOST volatile +#define RT_UNTRUSTED_HSTGST +#define RT_UNTRUSTED_VOLATILE_HSTGST volatile + +#define likely _X_LIKELY +#define unlikely _X_UNLIKELY + +/** + * A point in a two dimentional coordinate system. + */ +typedef struct RTPOINT +{ + /** X coordinate. */ + int32_t x; + /** Y coordinate. */ + int32_t y; +} RTPOINT; + +/** + * Rectangle data type, double point. + */ +typedef struct RTRECT +{ + /** left X coordinate. */ + int32_t xLeft; + /** top Y coordinate. */ + int32_t yTop; + /** right X coordinate. (exclusive) */ + int32_t xRight; + /** bottom Y coordinate. (exclusive) */ + int32_t yBottom; +} RTRECT; + +/** + * Rectangle data type, point + size. + */ +typedef struct RTRECT2 +{ + /** X coordinate. + * Unless stated otherwise, this is the top left corner. */ + int32_t x; + /** Y coordinate. + * Unless stated otherwise, this is the top left corner. */ + int32_t y; + /** The width. + * Unless stated otherwise, this is to the right of (x,y) and will not + * be a negative number. */ + int32_t cx; + /** The height. + * Unless stated otherwise, this is down from (x,y) and will not be a + * negative number. */ + int32_t cy; +} RTRECT2; + +/** + * The size of a rectangle. + */ +typedef struct RTRECTSIZE +{ + /** The width (along the x-axis). */ + uint32_t cx; + /** The height (along the y-axis). */ + uint32_t cy; +} RTRECTSIZE; + +/** @name Port I/O helpers + * @{ */ + +/** Write an 8-bit value to an I/O port. */ +#define VBVO_PORT_WRITE_U8(Port, Value) \ + outb(Port, Value) +/** Write a 16-bit value to an I/O port. */ +#define VBVO_PORT_WRITE_U16(Port, Value) \ + outw(Port, Value) +/** Write a 32-bit value to an I/O port. */ +#define VBVO_PORT_WRITE_U32(Port, Value) \ + outl(Port, Value) +/** Read an 8-bit value from an I/O port. */ +#define VBVO_PORT_READ_U8(Port) \ + inb(Port) +/** Read a 16-bit value from an I/O port. */ +#define VBVO_PORT_READ_U16(Port) \ + inw(Port) +/** Read a 32-bit value from an I/O port. */ +#define VBVO_PORT_READ_U32(Port) \ + inl(Port) + +/** @} */ + +#endif /* !VBOX_INCLUDED_Graphics_VBoxVideoIPRT_h */ diff --git a/src/VBox/Additions/x11/vboxvideo/edid.c b/src/VBox/Additions/x11/vboxvideo/edid.c new file mode 100644 index 00000000..96432848 --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/edid.c @@ -0,0 +1,165 @@ +/* $Id: edid.c $ */ +/** @file + * + * Linux Additions X11 graphics driver, EDID construction + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * This file is based on drmmode_display.c from the X.Org xf86-video-intel + * driver with the following copyright notice: + * + * Copyright © 2007 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * Authors: + * Dave Airlie <airlied@redhat.com> + * Michael Thayer <michael.thayer@oracle.com> + */ + +#include "misc.h" +#include "xf86DDC.h" +#include "xf86Crtc.h" +#include "vboxvideo.h" + +enum { EDID_SIZE = 128 }; + +const unsigned char g_acszEDIDBase[EDID_SIZE] = +{ + 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, /* header */ + 0x58, 0x58, /* manufacturer (VBX) */ + 0x00, 0x00, /* product code */ + 0x00, 0x00,0x00, 0x00, /* serial number goes here */ + 0x01, /* week of manufacture */ + 0x00, /* year of manufacture */ + 0x01, 0x03, /* EDID version */ + 0x80, /* capabilities - digital */ + 0x00, /* horiz. res in cm, zero for projectors */ + 0x00, /* vert. res in cm */ + 0x78, /* display gamma (120 == 2.2). Should we ask the host for this? */ + 0xEE, /* features (standby, suspend, off, RGB, standard colour space, + * preferred timing mode) */ + 0xEE, 0x91, 0xA3, 0x54, 0x4C, 0x99, 0x26, 0x0F, 0x50, 0x54, + /* chromaticity for standard colour space - should we ask the host? */ + 0x00, 0x00, 0x00, /* no default timings */ + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, /* no standard timings */ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* descriptor block 1 goes here */ + 0x00, 0x00, 0x00, 0xFD, 0x00, /* descriptor block 2, monitor ranges */ + 0x00, 0xC8, 0x00, 0xC8, 0x64, 0x00, 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, /* 0-200Hz vertical, 0-200KHz horizontal, 1000MHz pixel clock */ + 0x00, 0x00, 0x00, 0xFC, 0x00, /* descriptor block 3, monitor name */ + 'V', 'B', 'O', 'X', ' ', 'm', 'o', 'n', 'i', 't', 'o', 'r', '\n', + 0x00, 0x00, 0x00, 0x10, 0x00, /* descriptor block 4: dummy data */ + 0x0A, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, + 0x00, /* number of extensions */ + 0x00 /* checksum goes here */ +}; + +static void fillDescBlockTimings(unsigned char *pchDescBlock, + DisplayModePtr mode) +{ + struct detailed_timings timing; + + timing.clock = mode->Clock * 1000; + timing.h_active = mode->HDisplay; + timing.h_blanking = mode->HTotal - mode->HDisplay; + timing.v_active = mode->VDisplay; + timing.v_blanking = mode->VTotal - mode->VDisplay; + timing.h_sync_off = mode->HSyncStart - mode->HDisplay; + timing.h_sync_width = mode->HSyncEnd - mode->HSyncStart; + timing.v_sync_off = mode->VSyncStart - mode->VDisplay; + timing.v_sync_width = mode->VSyncEnd - mode->VSyncStart; + pchDescBlock[0] = (timing.clock / 10000) & 0xff; + pchDescBlock[1] = (timing.clock / 10000) >> 8; + pchDescBlock[2] = timing.h_active & 0xff; + pchDescBlock[3] = timing.h_blanking & 0xff; + pchDescBlock[4] = (timing.h_active >> 4) & 0xf0; + pchDescBlock[4] |= (timing.h_blanking >> 8) & 0xf; + pchDescBlock[5] = timing.v_active & 0xff; + pchDescBlock[6] = timing.v_blanking & 0xff; + pchDescBlock[7] = (timing.v_active >> 4) & 0xf0; + pchDescBlock[7] |= (timing.v_blanking >> 8) & 0xf; + pchDescBlock[8] = timing.h_sync_off & 0xff; + pchDescBlock[9] = timing.h_sync_width & 0xff; + pchDescBlock[10] = (timing.v_sync_off << 4) & 0xf0; + pchDescBlock[10] |= timing.v_sync_width & 0xf; + pchDescBlock[11] = (timing.h_sync_off >> 2) & 0xC0; + pchDescBlock[11] |= (timing.h_sync_width >> 4) & 0x30; + pchDescBlock[11] |= (timing.v_sync_off >> 2) & 0xC; + pchDescBlock[11] |= (timing.v_sync_width >> 4) & 0x3; + pchDescBlock[12] = pchDescBlock[13] = pchDescBlock[14] + = pchDescBlock[15] = pchDescBlock[16] + = pchDescBlock[17] = 0; +} + + +static void setEDIDChecksum(unsigned char *pch) +{ + unsigned i, sum = 0; + for (i = 0; i < EDID_SIZE - 1; ++i) + sum += pch[i]; + pch[EDID_SIZE - 1] = (0x100 - (sum & 0xFF)) & 0xFF; +} + + +/** + * Construct an EDID for an output given a preferred mode. The main reason for + * doing this is to confound gnome-settings-deamon which tries to reset the + * last mode configuration if the same monitors are plugged in again, which is + * a reasonable thing to do but not what we want in a VM. We evily store + * the (empty) raw EDID data at the end of the structure so that it gets + * freed automatically along with the structure. + */ +Bool VBOXEDIDSet(xf86OutputPtr output, DisplayModePtr pmode) +{ + unsigned char *pch, *pchEDID; + xf86MonPtr pEDIDMon; + + pch = calloc(1, sizeof(xf86Monitor) + EDID_SIZE); + if (!pch) + { + xf86DrvMsg(output->scrn->scrnIndex, X_ERROR, + "Can't allocate memory for EDID structure.\n"); + return FALSE; + } + pchEDID = pch + sizeof(xf86Monitor); + memcpy(pchEDID, g_acszEDIDBase, EDID_SIZE); + pchEDID[12] = pmode->HDisplay & 0xff; + pchEDID[13] = pmode->HDisplay >> 8; + pchEDID[14] = pmode->VDisplay & 0xff; + pchEDID[15] = pmode->VDisplay >> 8; + fillDescBlockTimings(pchEDID + 54, pmode); + setEDIDChecksum(pchEDID); + pEDIDMon = xf86InterpretEDID(output->scrn->scrnIndex, pchEDID); + if (!pEDIDMon) + { + free(pch); + return FALSE; + } + memcpy(pch, pEDIDMon, sizeof(xf86Monitor)); + free(pEDIDMon); + pEDIDMon = (xf86MonPtr)pch; + xf86OutputSetEDID(output, pEDIDMon); + return TRUE; +} diff --git a/src/VBox/Additions/x11/vboxvideo/getmode.c b/src/VBox/Additions/x11/vboxvideo/getmode.c new file mode 100644 index 00000000..8f182b93 --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/getmode.c @@ -0,0 +1,325 @@ +/* $Id: getmode.c $ */ +/** @file + * VirtualBox X11 Additions graphics driver dynamic video mode functions. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#include "vboxvideo.h" + +#define NEED_XF86_TYPES +#include "xf86.h" + +#ifdef XORG_7X +# include <stdio.h> +# include <stdlib.h> +# include <string.h> +#endif + +#ifdef VBOXVIDEO_13 +# ifdef RT_OS_LINUX +# include <linux/input.h> +# ifndef EVIOCGRAB +# define EVIOCGRAB _IOW('E', 0x90, int) +# endif +# ifndef KEY_SWITCHVIDEOMODE +# define KEY_SWITCHVIDEOMODE 227 +# endif +# include <dirent.h> +# include <errno.h> +# include <fcntl.h> +# include <unistd.h> +# endif /* RT_OS_LINUX */ +#endif /* VBOXVIDEO_13 */ + +/************************************************************************** +* Main functions * +**************************************************************************/ + +/** + * Fills a display mode M with a built-in mode of name pszName and dimensions + * cx and cy. + */ +static void vboxFillDisplayMode(ScrnInfoPtr pScrn, DisplayModePtr m, + const char *pszName, unsigned cx, unsigned cy) +{ + VBOXPtr pVBox = pScrn->driverPrivate; + char szName[256]; + DisplayModePtr pPrev = m->prev; + DisplayModePtr pNext = m->next; + + if (!pszName) + { + sprintf(szName, "%ux%u", cx, cy); + pszName = szName; + } + TRACE_LOG("pszName=%s, cx=%u, cy=%u\n", pszName, cx, cy); + if (m->name) + free((void*)m->name); + memset(m, '\0', sizeof(*m)); + m->prev = pPrev; + m->next = pNext; + m->status = MODE_OK; + m->type = M_T_BUILTIN; + /* Older versions of VBox only support screen widths which are a multiple + * of 8 */ + if (pVBox->fAnyX) + m->HDisplay = cx; + else + m->HDisplay = cx & ~7; + m->HSyncStart = m->HDisplay + 2; + m->HSyncEnd = m->HDisplay + 4; + m->HTotal = m->HDisplay + 6; + m->VDisplay = cy; + m->VSyncStart = m->VDisplay + 2; + m->VSyncEnd = m->VDisplay + 4; + m->VTotal = m->VDisplay + 6; + m->Clock = m->HTotal * m->VTotal * 60 / 1000; /* kHz */ + m->name = xnfstrdup(pszName); +} + +/** + * Allocates an empty display mode and links it into the doubly linked list of + * modes pointed to by pScrn->modes. Returns a pointer to the newly allocated + * memory. + */ +static DisplayModePtr vboxAddEmptyScreenMode(ScrnInfoPtr pScrn) +{ + DisplayModePtr pMode = xnfcalloc(sizeof(DisplayModeRec), 1); + + TRACE_ENTRY(); + if (!pScrn->modes) + { + pScrn->modes = pMode; + pMode->next = pMode; + pMode->prev = pMode; + } + else + { + pMode->next = pScrn->modes; + pMode->prev = pScrn->modes->prev; + pMode->next->prev = pMode; + pMode->prev->next = pMode; + } + return pMode; +} + +/** + * Create display mode entries in the screen information structure for each + * of the graphics modes that we wish to support, that is: + * - A dynamic mode in first place which will be updated by the RandR code. + * - Any modes that the user requested in xorg.conf/XFree86Config. + */ +void vboxAddModes(ScrnInfoPtr pScrn) +{ + unsigned cx = 0; + unsigned cy = 0; + unsigned i; + DisplayModePtr pMode; + + /* Add two dynamic mode entries. When we receive a new size hint we will + * update whichever of these is not current. */ + pMode = vboxAddEmptyScreenMode(pScrn); + vboxFillDisplayMode(pScrn, pMode, NULL, 800, 600); + pMode = vboxAddEmptyScreenMode(pScrn); + vboxFillDisplayMode(pScrn, pMode, NULL, 800, 600); + /* Add any modes specified by the user. We assume here that the mode names + * reflect the mode sizes. */ + for (i = 0; pScrn->display->modes && pScrn->display->modes[i]; i++) + { + if (sscanf(pScrn->display->modes[i], "%ux%u", &cx, &cy) == 2) + { + pMode = vboxAddEmptyScreenMode(pScrn); + vboxFillDisplayMode(pScrn, pMode, pScrn->display->modes[i], cx, cy); + } + } +} + +/** Set the initial values for the guest screen size hints to standard values + * in case nothing else is available. */ +void VBoxInitialiseSizeHints(ScrnInfoPtr pScrn) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + unsigned i; + + for (i = 0; i < pVBox->cScreens; ++i) + { + pVBox->pScreens[i].aPreferredSize.cx = 800; + pVBox->pScreens[i].aPreferredSize.cy = 600; + pVBox->pScreens[i].afConnected = true; + } + /* Set up the first mode correctly to match the requested initial mode. */ + pScrn->modes->HDisplay = pVBox->pScreens[0].aPreferredSize.cx; + pScrn->modes->VDisplay = pVBox->pScreens[0].aPreferredSize.cy; +} + +static Bool useHardwareCursor(uint32_t fCursorCapabilities) +{ + if (fCursorCapabilities & VBOX_VBVA_CURSOR_CAPABILITY_HARDWARE) + return true; + return false; +} + +static void compareAndMaybeSetUseHardwareCursor(VBOXPtr pVBox, uint32_t fCursorCapabilities, Bool *pfChanged, Bool fSet) +{ + if (pVBox->fUseHardwareCursor != useHardwareCursor(fCursorCapabilities)) + *pfChanged = true; + if (fSet) + pVBox->fUseHardwareCursor = useHardwareCursor(fCursorCapabilities); +} + +#define COMPARE_AND_MAYBE_SET(pDest, src, pfChanged, fSet) \ +do { \ + if (*(pDest) != (src)) \ + { \ + if (fSet) \ + *(pDest) = (src); \ + *(pfChanged) = true; \ + } \ +} while(0) + +/** Read in information about the most recent size hints and cursor + * capabilities requested for the guest screens from HGSMI. */ +void vbvxReadSizesAndCursorIntegrationFromHGSMI(ScrnInfoPtr pScrn, Bool *pfNeedUpdate) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + int rc; + unsigned i; + Bool fChanged = false; + uint32_t fCursorCapabilities; + + if (!pVBox->fHaveHGSMIModeHints) + return; + rc = VBoxHGSMIGetModeHints(&pVBox->guestCtx, pVBox->cScreens, pVBox->paVBVAModeHints); + AssertMsg(rc == VINF_SUCCESS, ("VBoxHGSMIGetModeHints failed, rc=%d.\n", rc)); + for (i = 0; i < pVBox->cScreens; ++i) + if (pVBox->paVBVAModeHints[i].magic == VBVAMODEHINT_MAGIC) + { + COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredSize.cx, pVBox->paVBVAModeHints[i].cx & 0x8fff, &fChanged, true); + COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredSize.cy, pVBox->paVBVAModeHints[i].cy & 0x8fff, &fChanged, true); + COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].afConnected, RT_BOOL(pVBox->paVBVAModeHints[i].fEnabled), &fChanged, true); + COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredLocation.x, (int32_t)pVBox->paVBVAModeHints[i].dx & 0x8fff, &fChanged, + true); + COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].aPreferredLocation.y, (int32_t)pVBox->paVBVAModeHints[i].dy & 0x8fff, &fChanged, + true); + if (pVBox->paVBVAModeHints[i].dx != ~(uint32_t)0 && pVBox->paVBVAModeHints[i].dy != ~(uint32_t)0) + COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].afHaveLocation, true, &fChanged, true); + else + COMPARE_AND_MAYBE_SET(&pVBox->pScreens[i].afHaveLocation, false, &fChanged, true); + } + rc = VBoxQueryConfHGSMI(&pVBox->guestCtx, VBOX_VBVA_CONF32_CURSOR_CAPABILITIES, &fCursorCapabilities); + AssertMsg(rc == VINF_SUCCESS, ("Getting VBOX_VBVA_CONF32_CURSOR_CAPABILITIES failed, rc=%d.\n", rc)); + compareAndMaybeSetUseHardwareCursor(pVBox, fCursorCapabilities, &fChanged, true); + if (pfNeedUpdate != NULL && fChanged) + *pfNeedUpdate = true; +} + +#undef COMPARE_AND_MAYBE_SET + +#ifdef VBOXVIDEO_13 +# ifdef RT_OS_LINUX +/** We have this for two purposes: one is to ensure that the X server is woken + * up when we get a video ACPI event. Two is to grab ACPI video events to + * prevent gnome-settings-daemon from seeing them, as older versions ignored + * the time stamp and handled them at the wrong time. */ +static void acpiEventHandler(int fd, void *pvData) +{ + struct input_event event; + ssize_t rc; + RT_NOREF(pvData); + + do + rc = read(fd, &event, sizeof(event)); + while (rc > 0 || (rc == -1 && errno == EINTR)); + /* Why do they return EAGAIN instead of zero bytes read like everyone else does? */ + AssertMsg(rc != -1 || errno == EAGAIN, ("Reading ACPI input event failed.\n")); +} + +void vbvxSetUpLinuxACPI(ScreenPtr pScreen) +{ + static const char s_szDevInput[] = "/dev/input/"; + struct dirent *pDirent; + char szFile[sizeof(s_szDevInput) + sizeof(pDirent->d_name) + 16]; + VBOXPtr pVBox = VBOXGetRec(xf86Screens[pScreen->myNum]); + DIR *pDir; + int fd = -1; + + if (pVBox->fdACPIDevices != -1 || pVBox->hACPIEventHandler != NULL) + FatalError("ACPI input file descriptor not initialised correctly.\n"); + pDir = opendir("/dev/input"); + if (pDir == NULL) + return; + memcpy(szFile, s_szDevInput, sizeof(s_szDevInput)); + for (pDirent = readdir(pDir); pDirent != NULL; pDirent = readdir(pDir)) + { + if (strncmp(pDirent->d_name, "event", sizeof("event") - 1) == 0) + { +#define BITS_PER_BLOCK (sizeof(unsigned long) * 8) + char szDevice[64] = ""; + unsigned long afKeys[KEY_MAX / BITS_PER_BLOCK]; + size_t const cchName = strlen(pDirent->d_name); + if (cchName + sizeof(s_szDevInput) > sizeof(szFile)) + continue; + memcpy(&szFile[sizeof(s_szDevInput) - 1], pDirent->d_name, cchName + 1); + if (fd != -1) + close(fd); + fd = open(szFile, O_RDONLY | O_NONBLOCK); + if ( fd == -1 + || ioctl(fd, EVIOCGNAME(sizeof(szDevice)), szDevice) == -1 + || strcmp(szDevice, "Video Bus") != 0) + continue; + if ( ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(afKeys)), afKeys) == -1 + || (( afKeys[KEY_SWITCHVIDEOMODE / BITS_PER_BLOCK] + >> KEY_SWITCHVIDEOMODE % BITS_PER_BLOCK) & 1) == 0) + break; + if (ioctl(fd, EVIOCGRAB, (void *)1) != 0) + break; + pVBox->hACPIEventHandler + = xf86AddGeneralHandler(fd, acpiEventHandler, pScreen); + if (pVBox->hACPIEventHandler == NULL) + break; + pVBox->fdACPIDevices = fd; + fd = -1; + break; +#undef BITS_PER_BLOCK + } + } + if (fd != -1) + close(fd); + closedir(pDir); +} + +void vbvxCleanUpLinuxACPI(ScreenPtr pScreen) +{ + VBOXPtr pVBox = VBOXGetRec(xf86Screens[pScreen->myNum]); + if (pVBox->fdACPIDevices != -1) + close(pVBox->fdACPIDevices); + pVBox->fdACPIDevices = -1; + xf86RemoveGeneralHandler(pVBox->hACPIEventHandler); + pVBox->hACPIEventHandler = NULL; +} +# endif /* RT_OS_LINUX */ +#endif /* VBOXVIDEO_13 */ diff --git a/src/VBox/Additions/x11/vboxvideo/hgsmimemalloc.c b/src/VBox/Additions/x11/vboxvideo/hgsmimemalloc.c new file mode 100644 index 00000000..3fb35831 --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/hgsmimemalloc.c @@ -0,0 +1,104 @@ +/* $Id: hgsmimemalloc.c $ */ +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * Memory allocator + * ---------------- + * + * Implementation + * -------------- + * + * Since the X.Org driver is single threaded and works using an allocate, + * submit and free pattern, we replace the generic allocator with a simple + * Boolean. Need more be said? + * + * bird> Yes, it's buggy. You never set fAllocated. See HGSMIMAAlloc(). + */ + +#include <VBoxVideoIPRT.h> +#include <HGSMIMemAlloc.h> +#include <HGSMI.h> + +int HGSMIMAInit(HGSMIMADATA *pMA, const HGSMIAREA *pArea, + HGSMIOFFSET *paDescriptors, uint32_t cDescriptors, HGSMISIZE cbMaxBlock, + const HGSMIENV *pEnv) +{ + (void)paDescriptors; + (void)cDescriptors; + (void)cbMaxBlock; + (void)pEnv; + if (!(pArea->cbArea < UINT32_C(0x80000000))) + return VERR_INVALID_PARAMETER; + if (!(pArea->cbArea >= HGSMI_MA_BLOCK_SIZE_MIN)) + return VERR_INVALID_PARAMETER; + + pMA->area = *pArea; + pMA->fAllocated = false; + return VINF_SUCCESS; +} + +void HGSMIMAUninit(HGSMIMADATA *pMA) +{ + (void)pMA; +} + +static HGSMIOFFSET HGSMIMAPointerToOffset(const HGSMIMADATA *pMA, const void RT_UNTRUSTED_VOLATILE_GUEST *pv) +{ + if (HGSMIAreaContainsPointer(&pMA->area, pv)) + return HGSMIPointerToOffset(&pMA->area, pv); + + AssertFailed(); + return HGSMIOFFSET_VOID; +} + +static void RT_UNTRUSTED_VOLATILE_GUEST *HGSMIMAOffsetToPointer(const HGSMIMADATA *pMA, HGSMIOFFSET off) +{ + if (HGSMIAreaContainsOffset(&pMA->area, off)) + return HGSMIOffsetToPointer(&pMA->area, off); + + AssertFailed(); + return NULL; +} + +void RT_UNTRUSTED_VOLATILE_GUEST *HGSMIMAAlloc(HGSMIMADATA *pMA, HGSMISIZE cb) +{ + (void)cb; + if (pMA->fAllocated) + return NULL; + HGSMIOFFSET off = pMA->area.offBase; + return HGSMIMAOffsetToPointer(pMA, off); + pMA->fAllocated = true; /** @todo r=bird: Errr. what's this doing *after* the return statement? */ +} + +void HGSMIMAFree(HGSMIMADATA *pMA, void RT_UNTRUSTED_VOLATILE_GUEST *pv) +{ + HGSMIOFFSET off = HGSMIMAPointerToOffset(pMA, pv); + if (off != HGSMIOFFSET_VOID) + pMA->fAllocated = false; + else + AssertFailed(); +} + diff --git a/src/VBox/Additions/x11/vboxvideo/pointer.c b/src/VBox/Additions/x11/vboxvideo/pointer.c new file mode 100644 index 00000000..9a6ade80 --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/pointer.c @@ -0,0 +1,496 @@ +/* $Id: pointer.c $ */ +/** @file + * VirtualBox X11 Additions graphics driver utility functions + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PCIACCESS +# include "xf86Pci.h" +# include <Pci.h> +#endif + +#include "xf86.h" +#define NEED_XF86_TYPES +#include "compiler.h" +#include "cursorstr.h" +#include "servermd.h" + +#include "vboxvideo.h" + +#ifdef XORG_7X +# include <stdlib.h> +# include <string.h> +#endif + +#define VBOX_MAX_CURSOR_WIDTH 64 +#define VBOX_MAX_CURSOR_HEIGHT 64 + +/************************************************************************** +* Debugging functions and macros * +**************************************************************************/ + +/* #define DEBUG_POINTER */ + +#ifdef DEBUG +# define PUT_PIXEL(c) ErrorF ("%c", c) +#else /* DEBUG_VIDEO not defined */ +# define PUT_PIXEL(c) do { } while(0) +#endif /* DEBUG_VIDEO not defined */ + +/** Macro to printf an error message and return from a function */ +#define RETERROR(scrnIndex, RetVal, ...) \ + do \ + { \ + xf86DrvMsg(scrnIndex, X_ERROR, __VA_ARGS__); \ + return RetVal; \ + } \ + while (0) + +/** Structure to pass cursor image data between realise_cursor() and + * load_cursor_image(). The members match the parameters to + * @a VBoxHGSMIUpdatePointerShape(). */ +struct vboxCursorImage +{ + uint32_t fFlags; + uint32_t cHotX; + uint32_t cHotY; + uint32_t cWidth; + uint32_t cHeight; + uint8_t *pPixels; + uint32_t cbLength; +}; + +#ifdef DEBUG_POINTER +static void +vbox_show_shape(unsigned short w, unsigned short h, CARD32 bg, unsigned char *image) +{ + size_t x, y; + unsigned short pitch; + CARD32 *color; + unsigned char *mask; + size_t sizeMask; + + image += sizeof(struct vboxCursorImage); + mask = image; + pitch = (w + 7) / 8; + sizeMask = (pitch * h + 3) & ~3; + color = (CARD32 *)(image + sizeMask); + + TRACE_ENTRY(); + for (y = 0; y < h; ++y, mask += pitch, color += w) + { + for (x = 0; x < w; ++x) + { + if (mask[x / 8] & (1 << (7 - (x % 8)))) + ErrorF (" "); + else + { + CARD32 c = color[x]; + if (c == bg) + ErrorF("Y"); + else + ErrorF("X"); + } + } + ErrorF("\n"); + } +} +#endif + +/************************************************************************** +* Main functions * +**************************************************************************/ + +void vbvxCursorTerm(VBOXPtr pVBox) +{ + TRACE_ENTRY(); + + xf86DestroyCursorInfoRec(pVBox->pCurs); + pVBox->pCurs = NULL; + TRACE_EXIT(); +} + +static void +vbox_vmm_hide_cursor(ScrnInfoPtr pScrn, VBOXPtr pVBox) +{ + int rc; + RT_NOREF(pScrn); + + rc = VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, 0, 0, 0, 0, 0, NULL, 0); + AssertMsg(rc == VINF_SUCCESS, ("Could not hide the virtual mouse pointer, VBox error %d.\n", rc)); +} + +static void +vbox_vmm_show_cursor(ScrnInfoPtr pScrn, VBOXPtr pVBox) +{ + int rc; + RT_NOREF(pScrn); + + if (!pVBox->fUseHardwareCursor) + return; + rc = VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, VBOX_MOUSE_POINTER_VISIBLE, + 0, 0, 0, 0, NULL, 0); + AssertMsg(rc == VINF_SUCCESS, ("Could not unhide the virtual mouse pointer.\n")); +} + +static void +vbox_vmm_load_cursor_image(ScrnInfoPtr pScrn, VBOXPtr pVBox, + unsigned char *pvImage) +{ + int rc; + struct vboxCursorImage *pImage; + pImage = (struct vboxCursorImage *)pvImage; + RT_NOREF(pScrn); + +#ifdef DEBUG_POINTER + vbox_show_shape(pImage->cWidth, pImage->cHeight, 0, pvImage); +#endif + + rc = VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, pImage->fFlags, + pImage->cHotX, pImage->cHotY, pImage->cWidth, pImage->cHeight, + pImage->pPixels, pImage->cbLength); + AssertMsg(rc == VINF_SUCCESS, ("Unable to set the virtual mouse pointer image.\n")); +} + +static void +vbox_set_cursor_colors(ScrnInfoPtr pScrn, int bg, int fg) +{ + RT_NOREF(pScrn); + RT_NOREF(bg); + RT_NOREF(fg); + /* ErrorF("vbox_set_cursor_colors NOT IMPLEMENTED\n"); */ +} + + +static void +vbox_set_cursor_position(ScrnInfoPtr pScrn, int x, int y) +{ + VBOXPtr pVBox = pScrn->driverPrivate; + + /* This currently does nothing. */ + VBoxHGSMICursorPosition(&pVBox->guestCtx, true, x, y, NULL, NULL); +} + +static void +vbox_hide_cursor(ScrnInfoPtr pScrn) +{ + VBOXPtr pVBox = pScrn->driverPrivate; + + vbox_vmm_hide_cursor(pScrn, pVBox); +} + +static void +vbox_show_cursor(ScrnInfoPtr pScrn) +{ + VBOXPtr pVBox = pScrn->driverPrivate; + + vbox_vmm_show_cursor(pScrn, pVBox); +} + +static void +vbox_load_cursor_image(ScrnInfoPtr pScrn, unsigned char *image) +{ + VBOXPtr pVBox = pScrn->driverPrivate; + + vbox_vmm_load_cursor_image(pScrn, pVBox, image); +} + +static Bool +vbox_use_hw_cursor(ScreenPtr pScreen, CursorPtr pCurs) +{ + ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum]; + VBOXPtr pVBox = pScrn->driverPrivate; + RT_NOREF(pCurs); + return pVBox->fUseHardwareCursor; +} + +static unsigned char +color_to_byte(unsigned c) +{ + return (c >> 8) & 0xff; +} + +static unsigned char * +vbox_realize_cursor(xf86CursorInfoPtr infoPtr, CursorPtr pCurs) +{ + VBOXPtr pVBox; + CursorBitsPtr bitsp; + unsigned short w, h, x, y; + unsigned char *c, *p, *pm, *ps, *m; + size_t sizeRequest, sizeRgba, sizeMask, srcPitch, dstPitch; + CARD32 fc, bc, *cp; + int scrnIndex = infoPtr->pScrn->scrnIndex; + struct vboxCursorImage *pImage; + + pVBox = infoPtr->pScrn->driverPrivate; + bitsp = pCurs->bits; + w = bitsp->width; + h = bitsp->height; + + if (!w || !h || w > VBOX_MAX_CURSOR_WIDTH || h > VBOX_MAX_CURSOR_HEIGHT) + RETERROR(scrnIndex, NULL, + "Error invalid cursor dimensions %dx%d\n", w, h); + + if ((bitsp->xhot > w) || (bitsp->yhot > h)) + RETERROR(scrnIndex, NULL, + "Error invalid cursor hotspot location %dx%d (max %dx%d)\n", + bitsp->xhot, bitsp->yhot, w, h); + + srcPitch = PixmapBytePad (bitsp->width, 1); + dstPitch = (w + 7) / 8; + sizeMask = ((dstPitch * h) + 3) & (size_t) ~3; + sizeRgba = w * h * 4; + sizeRequest = sizeMask + sizeRgba + sizeof(*pImage); + + p = c = calloc (1, sizeRequest); + if (!c) + RETERROR(scrnIndex, NULL, + "Error failed to alloc %lu bytes for cursor\n", + (unsigned long) sizeRequest); + + pImage = (struct vboxCursorImage *)p; + pImage->pPixels = m = p + sizeof(*pImage); + cp = (CARD32 *)(m + sizeMask); + + TRACE_LOG ("w=%d h=%d sm=%d sr=%d p=%d\n", + w, h, (int) sizeMask, (int) sizeRgba, (int) dstPitch); + TRACE_LOG ("m=%p c=%p cp=%p\n", m, c, (void *)cp); + + fc = color_to_byte (pCurs->foreBlue) + | (color_to_byte (pCurs->foreGreen) << 8) + | (color_to_byte (pCurs->foreRed) << 16); + + bc = color_to_byte (pCurs->backBlue) + | (color_to_byte (pCurs->backGreen) << 8) + | (color_to_byte (pCurs->backRed) << 16); + + /* + * Convert the Xorg source/mask bits to the and/xor bits VBox needs. + * Xorg: + * The mask is a bitmap indicating which parts of the cursor are + * transparent and which parts are drawn. The source is a bitmap + * indicating which parts of the non-transparent portion of the + * the cursor should be painted in the foreground color and which + * should be painted in the background color. By default, set bits + * indicate the opaque part of the mask bitmap and clear bits + * indicate the transparent part. + * VBox: + * The color data is the XOR mask. The AND mask bits determine + * which pixels of the color data (XOR mask) will replace (overwrite) + * the screen pixels (AND mask bit = 0) and which ones will be XORed + * with existing screen pixels (AND mask bit = 1). + * For example when you have the AND mask all 0, then you see the + * correct mouse pointer image surrounded by black square. + */ + for (pm = bitsp->mask, ps = bitsp->source, y = 0; + y < h; + ++y, pm += srcPitch, ps += srcPitch, m += dstPitch) + { + for (x = 0; x < w; ++x) + { + if (pm[x / 8] & (1 << (x % 8))) + { + /* opaque, leave AND mask bit at 0 */ + if (ps[x / 8] & (1 << (x % 8))) + { + *cp++ = fc; + PUT_PIXEL('X'); + } + else + { + *cp++ = bc; + PUT_PIXEL('*'); + } + } + else + { + /* transparent, set AND mask bit */ + m[x / 8] |= 1 << (7 - (x % 8)); + /* don't change the screen pixel */ + *cp++ = 0; + PUT_PIXEL(' '); + } + } + PUT_PIXEL('\n'); + } + + pImage->cWidth = w; + pImage->cHeight = h; + pImage->cHotX = bitsp->xhot; + pImage->cHotY = bitsp->yhot; + pImage->fFlags = VBOX_MOUSE_POINTER_VISIBLE | VBOX_MOUSE_POINTER_SHAPE; + pImage->cbLength = sizeRequest - sizeof(*pImage); + +#ifdef DEBUG_POINTER + ErrorF("shape = %p\n", p); + vbox_show_shape(w, h, bc, c); +#endif + + return p; +} + +#ifdef ARGB_CURSOR +static Bool +vbox_use_hw_cursor_argb(ScreenPtr pScreen, CursorPtr pCurs) +{ + ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum]; + VBOXPtr pVBox = pScrn->driverPrivate; + + if (!pVBox->fUseHardwareCursor) + return FALSE; + if ( (pCurs->bits->height > VBOX_MAX_CURSOR_HEIGHT) + || (pCurs->bits->width > VBOX_MAX_CURSOR_WIDTH) + || (pScrn->bitsPerPixel <= 8)) + return FALSE; + return TRUE; +} + + +static void +vbox_load_cursor_argb(ScrnInfoPtr pScrn, CursorPtr pCurs) +{ + VBOXPtr pVBox; + CursorBitsPtr bitsp; + unsigned short w, h; + unsigned short cx, cy; + unsigned char *pm; + CARD32 *pc; + size_t sizeData, sizeMask; + CARD8 *p; + int scrnIndex; + uint32_t fFlags = VBOX_MOUSE_POINTER_VISIBLE | VBOX_MOUSE_POINTER_SHAPE + | VBOX_MOUSE_POINTER_ALPHA; + + pVBox = pScrn->driverPrivate; + bitsp = pCurs->bits; + w = bitsp->width; + h = bitsp->height; + scrnIndex = pScrn->scrnIndex; + + /* Mask must be generated for alpha cursors, that is required by VBox. */ + /* note: (michael) the next struct must be 32bit aligned. */ + sizeMask = ((w + 7) / 8 * h + 3) & ~3; + + if (!w || !h || w > VBOX_MAX_CURSOR_WIDTH || h > VBOX_MAX_CURSOR_HEIGHT) + RETERROR(scrnIndex, , + "Error invalid cursor dimensions %dx%d\n", w, h); + + if ((bitsp->xhot > w) || (bitsp->yhot > h)) + RETERROR(scrnIndex, , + "Error invalid cursor hotspot location %dx%d (max %dx%d)\n", + bitsp->xhot, bitsp->yhot, w, h); + + sizeData = w * h * 4 + sizeMask; + p = calloc(1, sizeData); + if (!p) + RETERROR(scrnIndex, , + "Error failed to alloc %lu bytes for cursor\n", + (unsigned long)sizeData); + + memcpy(p + sizeMask, bitsp->argb, w * h * 4); + + /* Emulate the AND mask. */ + pm = p; + pc = bitsp->argb; + + /* Init AND mask to 1 */ + memset(pm, 0xFF, sizeMask); + + /* + * The additions driver must provide the AND mask for alpha cursors. The host frontend + * which can handle alpha channel, will ignore the AND mask and draw an alpha cursor. + * But if the host does not support ARGB, then it simply uses the AND mask and the color + * data to draw a normal color cursor. + */ + for (cy = 0; cy < h; cy++) + { + unsigned char bitmask = 0x80; + + for (cx = 0; cx < w; cx++, bitmask >>= 1) + { + if (bitmask == 0) + bitmask = 0x80; + + if (pc[cx] >= 0xF0000000) + pm[cx / 8] &= ~bitmask; + } + + /* Point to next source and dest scans */ + pc += w; + pm += (w + 7) / 8; + } + + VBoxHGSMIUpdatePointerShape(&pVBox->guestCtx, fFlags, bitsp->xhot, + bitsp->yhot, w, h, p, sizeData); + free(p); +} +#endif + +Bool vbvxCursorInit(ScreenPtr pScreen) +{ + ScrnInfoPtr pScrn = xf86Screens[pScreen->myNum]; + VBOXPtr pVBox = pScrn->driverPrivate; + xf86CursorInfoPtr pCurs = NULL; + Bool rc = TRUE; + + TRACE_ENTRY(); + pVBox->pCurs = pCurs = xf86CreateCursorInfoRec(); + if (!pCurs) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "Failed to create X Window cursor information structures for virtual mouse.\n"); + rc = FALSE; + } + if (rc) { + pCurs->MaxWidth = VBOX_MAX_CURSOR_WIDTH; + pCurs->MaxHeight = VBOX_MAX_CURSOR_HEIGHT; + pCurs->Flags = HARDWARE_CURSOR_TRUECOLOR_AT_8BPP + | HARDWARE_CURSOR_SOURCE_MASK_INTERLEAVE_1 + | HARDWARE_CURSOR_BIT_ORDER_MSBFIRST + | HARDWARE_CURSOR_UPDATE_UNHIDDEN; + + pCurs->SetCursorColors = vbox_set_cursor_colors; + pCurs->SetCursorPosition = vbox_set_cursor_position; + pCurs->LoadCursorImage = vbox_load_cursor_image; + pCurs->HideCursor = vbox_hide_cursor; + pCurs->ShowCursor = vbox_show_cursor; + pCurs->UseHWCursor = vbox_use_hw_cursor; + pCurs->RealizeCursor = vbox_realize_cursor; + +#ifdef ARGB_CURSOR + pCurs->UseHWCursorARGB = vbox_use_hw_cursor_argb; + pCurs->LoadCursorARGB = vbox_load_cursor_argb; +#endif + + rc = xf86InitCursor(pScreen, pCurs); + } + if (!rc) + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "Failed to enable mouse pointer integration.\n"); + if (!rc && (pCurs != NULL)) + xf86DestroyCursorInfoRec(pCurs); + return rc; +} diff --git a/src/VBox/Additions/x11/vboxvideo/setmode.c b/src/VBox/Additions/x11/vboxvideo/setmode.c new file mode 100644 index 00000000..876c043c --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/setmode.c @@ -0,0 +1,129 @@ +/* $Id: setmode.c $ */ +/** @file + * Linux Additions X11 graphics driver, mode setting + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * This file is based on X11 VESA driver (hardly any traces left here): + * + * Copyright (c) 2000 by Conectiva S.A. (http://www.conectiva.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of Conectiva Linux shall + * not be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization from + * Conectiva Linux. + * + * Authors: Paulo César Pereira de Andrade <pcpa@conectiva.com.br> + * Michael Thayer <michael.thayer@oracle.com> + */ + +#ifdef XORG_7X +/* We include <unistd.h> for Solaris below, and the ANSI C emulation layer + * interferes with that. */ +# define _XF86_ANSIC_H +# define XF86_LIBC_H +# include <string.h> +#endif +#include "vboxvideo.h" +#include "xf86.h" + +/* VGA hardware functions for setting and restoring text mode */ +#include "vgaHW.h" + +#ifdef RT_OS_SOLARIS +# include <sys/vuid_event.h> +# include <sys/msio.h> +# include <errno.h> +# include <fcntl.h> +# include <unistd.h> +#endif + +/** Clear the virtual framebuffer in VRAM. Optionally also clear up to the + * size of a new framebuffer. Framebuffer sizes larger than available VRAM + * be treated as zero and passed over. */ +void vbvxClearVRAM(ScrnInfoPtr pScrn, size_t cbOldSize, size_t cbNewSize) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + + /* Assume 32BPP - this is just a sanity test. */ + AssertMsg( cbOldSize / 4 <= VBOX_VIDEO_MAX_VIRTUAL * VBOX_VIDEO_MAX_VIRTUAL + && cbNewSize / 4 <= VBOX_VIDEO_MAX_VIRTUAL * VBOX_VIDEO_MAX_VIRTUAL, + ("cbOldSize=%llu cbNewSize=%llu, max=%u.\n", (unsigned long long)cbOldSize, (unsigned long long)cbNewSize, + VBOX_VIDEO_MAX_VIRTUAL * VBOX_VIDEO_MAX_VIRTUAL)); + if (cbOldSize > (size_t)pVBox->cbFBMax) + cbOldSize = pVBox->cbFBMax; + if (cbNewSize > (size_t)pVBox->cbFBMax) + cbNewSize = pVBox->cbFBMax; + memset(pVBox->base, 0, max(cbOldSize, cbNewSize)); +} + +/** Set a graphics mode. Poke any required values into registers, do an HGSMI + * mode set and tell the host we support advanced graphics functions. + */ +void vbvxSetMode(ScrnInfoPtr pScrn, unsigned cDisplay, unsigned cWidth, unsigned cHeight, int x, int y, Bool fEnabled, + Bool fConnected, struct vbvxFrameBuffer *pFrameBuffer) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + uint32_t offStart; + uint16_t fFlags; + int rc; + Bool fEnabledAndVisible = fEnabled && x + cWidth <= pFrameBuffer->cWidth && y + cHeight <= pFrameBuffer->cHeight; + /* Recent host code has a flag to blank the screen; older code needs BPP set to zero. */ + uint32_t cBPP = fEnabledAndVisible || pVBox->fHostHasScreenBlankingFlag ? pFrameBuffer->cBPP : 0; + + TRACE_LOG("cDisplay=%u, cWidth=%u, cHeight=%u, x=%d, y=%d, fEnabled=%d, fConnected=%d, pFrameBuffer: { x0=%d, y0=%d, cWidth=%u, cHeight=%u, cBPP=%u }\n", + cDisplay, cWidth, cHeight, x, y, fEnabled, fConnected, pFrameBuffer->x0, pFrameBuffer->y0, pFrameBuffer->cWidth, + pFrameBuffer->cHeight, pFrameBuffer->cBPP); + AssertMsg(cWidth != 0 && cHeight != 0, ("cWidth = 0 or cHeight = 0\n")); + offStart = (y * pFrameBuffer->cWidth + x) * pFrameBuffer->cBPP / 8; + if (cDisplay == 0 && fEnabled) + VBoxVideoSetModeRegisters(cWidth, cHeight, pFrameBuffer->cWidth, pFrameBuffer->cBPP, 0, x, y); + fFlags = VBVA_SCREEN_F_ACTIVE; + fFlags |= (fConnected ? 0 : VBVA_SCREEN_F_DISABLED); + fFlags |= (!fEnabledAndVisible && pVBox->fHostHasScreenBlankingFlag ? VBVA_SCREEN_F_BLANK : 0); + VBoxHGSMIProcessDisplayInfo(&pVBox->guestCtx, cDisplay, x - pFrameBuffer->x0, y - pFrameBuffer->y0, offStart, + pFrameBuffer->cWidth * pFrameBuffer->cBPP / 8, cWidth, cHeight, cBPP, fFlags); + rc = VBoxHGSMIUpdateInputMapping(&pVBox->guestCtx, 0 - pFrameBuffer->x0, 0 - pFrameBuffer->y0, pFrameBuffer->cWidth, + pFrameBuffer->cHeight); + if (RT_FAILURE(rc)) + FatalError("Failed to update the input mapping.\n"); +} + +/** Tell the virtual mouse device about the new virtual desktop size. */ +void vbvxSetSolarisMouseRange(int width, int height) +{ +#ifdef RT_OS_SOLARIS + int rc; + int hMouse = open("/dev/mouse", O_RDWR); + + if (hMouse >= 0) + { + do { + Ms_screen_resolution Res = { height, width }; + rc = ioctl(hMouse, MSIOSRESOLUTION, &Res); + } while ((rc != 0) && (errno == EINTR)); + close(hMouse); + } +#else + (void)width; (void)height; +#endif +} diff --git a/src/VBox/Additions/x11/vboxvideo/vboxvideo.c b/src/VBox/Additions/x11/vboxvideo/vboxvideo.c new file mode 100644 index 00000000..99a8b1fa --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/vboxvideo.c @@ -0,0 +1,1491 @@ +/* $Id: vboxvideo.c $ */ +/** @file + * Linux Additions X11 graphics driver + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * This file is based on the X.Org VESA driver: + * + * Copyright (c) 2000 by Conectiva S.A. (http://www.conectiva.com) + * Copyright 2008 Red Hat, Inc. + * Copyright 2012 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of Conectiva Linux shall + * not be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization from + * Conectiva Linux. + * + * Authors: Paulo César Pereira de Andrade <pcpa@conectiva.com.br> + * David Dawes <dawes@xfree86.org> + * Adam Jackson <ajax@redhat.com> + * Dave Airlie <airlied@redhat.com> + * Michael Thayer <michael.thayer@oracle.com> + */ + +#include "vboxvideo.h" +#include <VBoxVideoVBE.h> + +/* Basic definitions and functions needed by all drivers. */ +#include "xf86.h" +/* For video memory mapping. */ +#include "xf86_OSproc.h" +#if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) < 6 +/* PCI resources. */ +# include "xf86Resources.h" +#endif +/* Generic server linear frame-buffer APIs. */ +#include "fb.h" +/* Colormap and visual handling. */ +#include "micmap.h" +#include "xf86cmap.h" +/* ShadowFB support */ +#include "shadowfb.h" +/* VGA hardware functions for setting and restoring text mode */ +#include "vgaHW.h" +#ifdef VBOXVIDEO_13 +/* X.org 1.3+ mode setting */ +# define _HAVE_STRING_ARCH_strsep /* bits/string2.h, __strsep_1c. */ +# include "xf86Crtc.h" +# include "xf86Modes.h" +/* For xf86RandR12GetOriginalVirtualSize(). */ +# include "xf86RandR12.h" +#endif +/* For setting the root window property. */ +#include "property.h" +#include <X11/Xatom.h> + +#ifdef XORG_7X +# include <stdlib.h> +# include <string.h> +# include <fcntl.h> +# include <unistd.h> +#endif + +/* Mandatory functions */ + +static const OptionInfoRec * VBOXAvailableOptions(int chipid, int busid); +static void VBOXIdentify(int flags); +#ifndef PCIACCESS +static Bool VBOXProbe(DriverPtr drv, int flags); +#else +static Bool VBOXPciProbe(DriverPtr drv, int entity_num, + struct pci_device *dev, intptr_t match_data); +#endif +static Bool VBOXPreInit(ScrnInfoPtr pScrn, int flags); +static Bool VBOXScreenInit(ScreenPtr pScreen, int argc, char **argv); +static Bool VBOXEnterVT(ScrnInfoPtr pScrn); +static void VBOXLeaveVT(ScrnInfoPtr pScrn); +static Bool VBOXCloseScreen(ScreenPtr pScreen); +#ifndef VBOXVIDEO_13 +static Bool VBOXSaveScreen(ScreenPtr pScreen, int mode); +#endif +static Bool VBOXSwitchMode(ScrnInfoPtr pScrn, DisplayModePtr pMode); +static void VBOXAdjustFrame(ScrnInfoPtr pScrn, int x, int y); +static void VBOXFreeScreen(ScrnInfoPtr pScrn); +#ifndef VBOXVIDEO_13 +static void VBOXDisplayPowerManagementSet(ScrnInfoPtr pScrn, int mode, int flags); +#endif + +/* locally used functions */ +static Bool VBOXMapVidMem(ScrnInfoPtr pScrn); +static void VBOXUnmapVidMem(ScrnInfoPtr pScrn); +static void VBOXSaveMode(ScrnInfoPtr pScrn); +static void VBOXRestoreMode(ScrnInfoPtr pScrn); +static void setSizesAndCursorIntegration(ScrnInfoPtr pScrn, Bool fScreenInitTime); + +#ifndef XF86_SCRN_INTERFACE +# define xf86ScreenToScrn(pScreen) xf86Screens[(pScreen)->myNum] +# define xf86ScrnToScreen(pScrn) screenInfo.screens[(pScrn)->scrnIndex] +#endif + +static inline void VBOXSetRec(ScrnInfoPtr pScrn) +{ + if (!pScrn->driverPrivate) + { + VBOXPtr pVBox = (VBOXPtr)xnfcalloc(sizeof(VBOXRec), 1); + pScrn->driverPrivate = pVBox; +#if defined(VBOXVIDEO_13) && defined(RT_OS_LINUX) + pVBox->fdACPIDevices = -1; +#endif + } +} + +enum GenericTypes +{ + CHIP_VBOX_GENERIC +}; + +#ifdef PCIACCESS +static const struct pci_id_match vbox_device_match[] = { + { + VBOX_VENDORID, VBOX_DEVICEID, PCI_MATCH_ANY, PCI_MATCH_ANY, + 0, 0, 0 + }, + + { 0, 0, 0 }, +}; +#endif + +/* Supported chipsets */ +static SymTabRec VBOXChipsets[] = +{ + {VBOX_DEVICEID, "vbox"}, + {-1, NULL} +}; + +static PciChipsets VBOXPCIchipsets[] = { + { VBOX_DEVICEID, VBOX_DEVICEID, RES_SHARED_VGA }, + { -1, -1, RES_UNDEFINED }, +}; + +/* + * This contains the functions needed by the server after loading the + * driver module. It must be supplied, and gets added the driver list by + * the Module Setup function in the dynamic case. In the static case a + * reference to this is compiled in, and this requires that the name of + * this DriverRec be an upper-case version of the driver name. + */ + +#ifdef XORG_7X +_X_EXPORT +#endif +DriverRec VBOXVIDEO = { + VBOX_VERSION, + VBOX_DRIVER_NAME, + VBOXIdentify, +#ifdef PCIACCESS + NULL, +#else + VBOXProbe, +#endif + VBOXAvailableOptions, + NULL, + 0, +#ifdef XORG_7X + NULL, +#endif +#ifdef PCIACCESS + vbox_device_match, + VBOXPciProbe +#endif +}; + +/* No options for now */ +static const OptionInfoRec VBOXOptions[] = { + { -1, NULL, OPTV_NONE, {0}, FALSE } +}; + +#ifndef XORG_7X +/* + * List of symbols from other modules that this module references. This + * list is used to tell the loader that it is OK for symbols here to be + * unresolved providing that it hasn't been told that they haven't been + * told that they are essential via a call to xf86LoaderReqSymbols() or + * xf86LoaderReqSymLists(). The purpose is this is to avoid warnings about + * unresolved symbols that are not required. + */ +static const char *fbSymbols[] = { + "fbPictureInit", + "fbScreenInit", + NULL +}; + +static const char *shadowfbSymbols[] = { + "ShadowFBInit2", + NULL +}; + +static const char *ramdacSymbols[] = { + "xf86DestroyCursorInfoRec", + "xf86InitCursor", + "xf86CreateCursorInfoRec", + NULL +}; + +static const char *vgahwSymbols[] = { + "vgaHWFreeHWRec", + "vgaHWGetHWRec", + "vgaHWGetIOBase", + "vgaHWGetIndex", + "vgaHWRestore", + "vgaHWSave", + "vgaHWSetStdFuncs", + NULL +}; +#endif /* !XORG_7X */ + +/** Resize the virtual framebuffer. */ +static Bool adjustScreenPixmap(ScrnInfoPtr pScrn, int width, int height) +{ + ScreenPtr pScreen = xf86ScrnToScreen(pScrn); + VBOXPtr pVBox = VBOXGetRec(pScrn); + int adjustedWidth = pScrn->bitsPerPixel == 16 ? (width + 1) & ~1 : width; + int cbLine = adjustedWidth * pScrn->bitsPerPixel / 8; + PixmapPtr pPixmap; + + TRACE_LOG("width=%d, height=%d\n", width, height); + AssertMsg(width >= 0 && height >= 0, ("Invalid negative width (%d) or height (%d)\n", width, height)); + if (pScreen == NULL) /* Not yet initialised. */ + return TRUE; + pPixmap = pScreen->GetScreenPixmap(pScreen); + AssertMsg(pPixmap != NULL, ("Failed to get the screen pixmap.\n")); + TRACE_LOG("pPixmap=%p adjustedWidth=%d height=%d pScrn->depth=%d pScrn->bitsPerPixel=%d cbLine=%d pVBox->base=%p pPixmap->drawable.width=%d pPixmap->drawable.height=%d\n", + (void *)pPixmap, adjustedWidth, height, pScrn->depth, + pScrn->bitsPerPixel, cbLine, pVBox->base, + pPixmap->drawable.width, pPixmap->drawable.height); + if ( adjustedWidth != pPixmap->drawable.width + || height != pPixmap->drawable.height) + { + if ( adjustedWidth > VBOX_VIDEO_MAX_VIRTUAL || height > VBOX_VIDEO_MAX_VIRTUAL + || (unsigned)cbLine * (unsigned)height >= pVBox->cbFBMax) + { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "Virtual framebuffer %dx%d too large. For information, video memory: %u Kb.\n", + adjustedWidth, height, (unsigned) pVBox->cbFBMax / 1024); + return FALSE; + } + if (pScrn->vtSema) + vbvxClearVRAM(pScrn, ((size_t)pScrn->virtualX) * pScrn->virtualY * (pScrn->bitsPerPixel / 8), + ((size_t)adjustedWidth) * height * (pScrn->bitsPerPixel / 8)); + pScreen->ModifyPixmapHeader(pPixmap, adjustedWidth, height, pScrn->depth, pScrn->bitsPerPixel, cbLine, pVBox->base); + } + pScrn->displayWidth = pScrn->virtualX = adjustedWidth; + pScrn->virtualY = height; + return TRUE; +} + +#ifndef VBOXVIDEO_13 +/** Set a video mode to the hardware, RandR 1.1 version. + * + * Since we no longer do virtual frame buffers, adjust the screen pixmap + * dimensions to match. The "override" parameters are for when we received a + * mode hint while switched to a virtual terminal. In this case VBoxClient will + * have told us about the mode, but not yet been able to do a mode switch using + * RandR. We solve this by setting the requested mode to the host but keeping + * the virtual frame- + * buffer matching what the X server expects. */ +static void setModeRandR11(ScrnInfoPtr pScrn, DisplayModePtr pMode, Bool fScreenInitTime, Bool fEnterVTTime, + int cXOverRide, int cYOverRide) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + struct vbvxFrameBuffer frameBuffer = { 0, 0, pMode->HDisplay, pMode->VDisplay, pScrn->bitsPerPixel}; + int cXPhysical = cXOverRide > 0 ? min(cXOverRide, pMode->HDisplay) : pMode->HDisplay; + int cYPhysical = cYOverRide > 0 ? min(cYOverRide, pMode->VDisplay) : pMode->VDisplay; + + pVBox->pScreens[0].aScreenLocation.cx = pMode->HDisplay; + pVBox->pScreens[0].aScreenLocation.cy = pMode->VDisplay; + if (fScreenInitTime) + { + /* The screen structure is not fully set up yet, so do not touch it. */ + pScrn->displayWidth = pScrn->virtualX = pMode->HDisplay; + pScrn->virtualY = pMode->VDisplay; + } + else + { + xf86ScrnToScreen(pScrn)->width = pMode->HDisplay; + xf86ScrnToScreen(pScrn)->height = pMode->VDisplay; + /* This prevents a crash in CentOS 3. I was unable to debug it to + * satisfaction, partly due to the lack of symbols. My guess is that + * pScrn->ModifyPixmapHeader() expects certain things to be set up when + * it sees pScrn->vtSema set to true which are not quite done at this + * point of the VT switch. */ + if (fEnterVTTime) + pScrn->vtSema = FALSE; + adjustScreenPixmap(pScrn, pMode->HDisplay, pMode->VDisplay); + if (fEnterVTTime) + pScrn->vtSema = TRUE; + } + if (pMode->HDisplay != 0 && pMode->VDisplay != 0 && pScrn->vtSema) + vbvxSetMode(pScrn, 0, cXPhysical, cYPhysical, 0, 0, true, true, &frameBuffer); + pScrn->currentMode = pMode; +} +#endif + +#ifdef VBOXVIDEO_13 +/* X.org 1.3+ mode-setting support ******************************************/ + +/** Set a video mode to the hardware, RandR 1.2 version. If this is the first + * screen, re-set the current mode for all others (the offset for the first + * screen is always treated as zero by the hardware, so all other screens need + * to be changed to compensate for any changes!). The mode to set is taken + * from the X.Org Crtc structure. */ +static void setModeRandR12(ScrnInfoPtr pScrn, unsigned cScreen) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + unsigned i; + struct vbvxFrameBuffer frameBuffer = { pVBox->pScreens[0].paCrtcs->x, pVBox->pScreens[0].paCrtcs->y, pScrn->virtualX, + pScrn->virtualY, pScrn->bitsPerPixel }; + unsigned cFirst = cScreen; + unsigned cLast = cScreen != 0 ? cScreen + 1 : pVBox->cScreens; + int originalX, originalY; + + /* Check that this code cannot trigger the resizing bug in X.Org Server 1.3. + * See the work-around in ScreenInit. */ + xf86RandR12GetOriginalVirtualSize(pScrn, &originalX, &originalY); + AssertMsg(originalX == VBOX_VIDEO_MAX_VIRTUAL && originalY == VBOX_VIDEO_MAX_VIRTUAL, ("OriginalSize=%dx%d", + originalX, originalY)); + for (i = cFirst; i < cLast; ++i) + if (pVBox->pScreens[i].paCrtcs->mode.HDisplay != 0 && pVBox->pScreens[i].paCrtcs->mode.VDisplay != 0 && pScrn->vtSema) + vbvxSetMode(pScrn, i, pVBox->pScreens[i].paCrtcs->mode.HDisplay, pVBox->pScreens[i].paCrtcs->mode.VDisplay, + pVBox->pScreens[i].paCrtcs->x, pVBox->pScreens[i].paCrtcs->y, pVBox->pScreens[i].fPowerOn, + pVBox->pScreens[i].paOutputs->status == XF86OutputStatusConnected, &frameBuffer); +} + +/** Wrapper around setModeRandR12() to avoid exposing non-obvious semantics. + */ +static void setAllModesRandR12(ScrnInfoPtr pScrn) +{ + setModeRandR12(pScrn, 0); +} + +/* For descriptions of these functions and structures, see + hw/xfree86/modes/xf86Crtc.h and hw/xfree86/modes/xf86Modes.h in the + X.Org source tree. */ + +static Bool vbox_config_resize(ScrnInfoPtr pScrn, int cw, int ch) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + Bool rc; + unsigned i; + + TRACE_LOG("width=%d, height=%d\n", cw, ch); + rc = adjustScreenPixmap(pScrn, cw, ch); + /* Power-on all screens (the server expects this) and set the new pitch to them. */ + for (i = 0; i < pVBox->cScreens; ++i) + pVBox->pScreens[i].fPowerOn = true; + setAllModesRandR12(pScrn); + vbvxSetSolarisMouseRange(cw, ch); + return rc; +} + +static const xf86CrtcConfigFuncsRec VBOXCrtcConfigFuncs = { + vbox_config_resize +}; + +static void +vbox_crtc_dpms(xf86CrtcPtr crtc, int mode) +{ + ScrnInfoPtr pScrn = crtc->scrn; + VBOXPtr pVBox = VBOXGetRec(pScrn); + unsigned cDisplay = (uintptr_t)crtc->driver_private; + + TRACE_LOG("mode=%d\n", mode); + pVBox->pScreens[cDisplay].fPowerOn = (mode != DPMSModeOff); + setModeRandR12(pScrn, cDisplay); +} + +static Bool +vbox_crtc_lock (xf86CrtcPtr crtc) +{ RT_NOREF(crtc); return FALSE; } + + +/* We use this function to check whether the X server owns the active virtual + * terminal before attempting a mode switch, since the RandR extension isn't + * very dilligent here, which can mean crashes if we are unlucky. This is + * not the way it the function is intended - it is meant for reporting modes + * which the hardware can't handle. I hope that this won't confuse any clients + * connecting to us. */ +static Bool +vbox_crtc_mode_fixup (xf86CrtcPtr crtc, DisplayModePtr mode, + DisplayModePtr adjusted_mode) +{ RT_NOREF(crtc, mode, adjusted_mode); return TRUE; } + +static void +vbox_crtc_stub (xf86CrtcPtr crtc) +{ RT_NOREF(crtc); } + +static void +vbox_crtc_mode_set (xf86CrtcPtr crtc, DisplayModePtr mode, + DisplayModePtr adjusted_mode, int x, int y) +{ + RT_NOREF(mode); + VBOXPtr pVBox = VBOXGetRec(crtc->scrn); + unsigned cDisplay = (uintptr_t)crtc->driver_private; + + TRACE_LOG("name=%s, HDisplay=%d, VDisplay=%d, x=%d, y=%d\n", adjusted_mode->name, + adjusted_mode->HDisplay, adjusted_mode->VDisplay, x, y); + pVBox->pScreens[cDisplay].fPowerOn = true; + pVBox->pScreens[cDisplay].aScreenLocation.cx = adjusted_mode->HDisplay; + pVBox->pScreens[cDisplay].aScreenLocation.cy = adjusted_mode->VDisplay; + pVBox->pScreens[cDisplay].aScreenLocation.x = x; + pVBox->pScreens[cDisplay].aScreenLocation.y = y; + setModeRandR12(crtc->scrn, cDisplay); +} + +static void +vbox_crtc_gamma_set (xf86CrtcPtr crtc, CARD16 *red, + CARD16 *green, CARD16 *blue, int size) +{ RT_NOREF(crtc, red, green, blue, size); } + +static void * +vbox_crtc_shadow_allocate (xf86CrtcPtr crtc, int width, int height) +{ RT_NOREF(crtc, width, height); return NULL; } + +static const xf86CrtcFuncsRec VBOXCrtcFuncs = { + .dpms = vbox_crtc_dpms, + .save = NULL, /* These two are never called by the server. */ + .restore = NULL, + .lock = vbox_crtc_lock, + .unlock = NULL, /* This will not be invoked if lock returns FALSE. */ + .mode_fixup = vbox_crtc_mode_fixup, + .prepare = vbox_crtc_stub, + .mode_set = vbox_crtc_mode_set, + .commit = vbox_crtc_stub, + .gamma_set = vbox_crtc_gamma_set, + .shadow_allocate = vbox_crtc_shadow_allocate, + .shadow_create = NULL, /* These two should not be invoked if allocate + returns NULL. */ + .shadow_destroy = NULL, + .set_cursor_colors = NULL, /* We are still using the old cursor API. */ + .set_cursor_position = NULL, + .show_cursor = NULL, + .hide_cursor = NULL, + .load_cursor_argb = NULL, + .destroy = vbox_crtc_stub +}; + +static void +vbox_output_stub (xf86OutputPtr output) +{ RT_NOREF(output); } + +static void +vbox_output_dpms (xf86OutputPtr output, int mode) +{ + RT_NOREF(output, mode); +} + +static int +vbox_output_mode_valid (xf86OutputPtr output, DisplayModePtr mode) +{ + return MODE_OK; +} + +static Bool +vbox_output_mode_fixup (xf86OutputPtr output, DisplayModePtr mode, + DisplayModePtr adjusted_mode) +{ RT_NOREF(output, mode, adjusted_mode); return TRUE; } + +static void +vbox_output_mode_set (xf86OutputPtr output, DisplayModePtr mode, + DisplayModePtr adjusted_mode) +{ RT_NOREF(output, mode, adjusted_mode); } + +static xf86OutputStatus +vbox_output_detect (xf86OutputPtr output) +{ + ScrnInfoPtr pScrn = output->scrn; + VBOXPtr pVBox = VBOXGetRec(pScrn); + uint32_t iScreen = (uintptr_t)output->driver_private; + return pVBox->pScreens[iScreen].afConnected + ? XF86OutputStatusConnected : XF86OutputStatusDisconnected; +} + +static DisplayModePtr vbox_output_add_mode(VBOXPtr pVBox, DisplayModePtr *pModes, const char *pszName, int x, int y, + Bool isPreferred, Bool isUserDef) +{ + TRACE_LOG("pszName=%s, x=%d, y=%d\n", pszName ? pszName : "(null)", x, y); + DisplayModePtr pMode = xnfcalloc(1, sizeof(DisplayModeRec)); + int cRefresh = 60; + + pMode->status = MODE_OK; + /* We don't ask the host whether it likes user defined modes, + * as we assume that the user really wanted that mode. */ + pMode->type = isUserDef ? M_T_USERDEF : M_T_BUILTIN; + if (isPreferred) + pMode->type |= M_T_PREFERRED; + /* Older versions of VBox only support screen widths which are a multiple + * of 8 */ + if (pVBox->fAnyX) + pMode->HDisplay = x; + else + pMode->HDisplay = x & ~7; + pMode->HSyncStart = pMode->HDisplay + 2; + pMode->HSyncEnd = pMode->HDisplay + 4; + pMode->HTotal = pMode->HDisplay + 6; + pMode->VDisplay = y; + pMode->VSyncStart = pMode->VDisplay + 2; + pMode->VSyncEnd = pMode->VDisplay + 4; + pMode->VTotal = pMode->VDisplay + 6; + pMode->Clock = pMode->HTotal * pMode->VTotal * cRefresh / 1000; /* kHz */ + if (NULL == pszName) { + xf86SetModeDefaultName(pMode); + } else { + pMode->name = xnfstrdup(pszName); + } + *pModes = xf86ModesAdd(*pModes, pMode); + return pMode; +} + +static DisplayModePtr +vbox_output_get_modes (xf86OutputPtr output) +{ + DisplayModePtr pModes = NULL; + DisplayModePtr pPreferred = NULL; + ScrnInfoPtr pScrn = output->scrn; + VBOXPtr pVBox = VBOXGetRec(pScrn); + + TRACE_ENTRY(); + uint32_t iScreen = (uintptr_t)output->driver_private; + pPreferred = vbox_output_add_mode(pVBox, &pModes, NULL, + RT_CLAMP(pVBox->pScreens[iScreen].aPreferredSize.cx, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL), + RT_CLAMP(pVBox->pScreens[iScreen].aPreferredSize.cy, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL), + TRUE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 2560, 1600, FALSE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 2560, 1440, FALSE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 2048, 1536, FALSE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 1920, 1600, FALSE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 1920, 1080, FALSE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 1680, 1050, FALSE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 1600, 1200, FALSE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 1400, 1050, FALSE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 1280, 1024, FALSE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 1024, 768, FALSE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 800, 600, FALSE, FALSE); + vbox_output_add_mode(pVBox, &pModes, NULL, 640, 480, FALSE, FALSE); + VBOXEDIDSet(output, pPreferred); + TRACE_EXIT(); + return pModes; +} + +static const xf86OutputFuncsRec VBOXOutputFuncs = { + .create_resources = vbox_output_stub, + .dpms = vbox_output_dpms, + .save = NULL, /* These two are never called by the server. */ + .restore = NULL, + .mode_valid = vbox_output_mode_valid, + .mode_fixup = vbox_output_mode_fixup, + .prepare = vbox_output_stub, + .commit = vbox_output_stub, + .mode_set = vbox_output_mode_set, + .detect = vbox_output_detect, + .get_modes = vbox_output_get_modes, +#ifdef RANDR_12_INTERFACE + .set_property = NULL, +#endif + .destroy = vbox_output_stub +}; +#endif /* VBOXVIDEO_13 */ + +/* Module loader interface */ +static MODULESETUPPROTO(vboxSetup); + +static XF86ModuleVersionInfo vboxVersionRec = +{ + VBOX_DRIVER_NAME, + "Oracle Corporation", + MODINFOSTRING1, + MODINFOSTRING2, +#ifdef XORG_7X + XORG_VERSION_CURRENT, +#else + XF86_VERSION_CURRENT, +#endif + 1, /* Module major version. Xorg-specific */ + 0, /* Module minor version. Xorg-specific */ + 1, /* Module patchlevel. Xorg-specific */ + ABI_CLASS_VIDEODRV, /* This is a video driver */ + ABI_VIDEODRV_VERSION, + MOD_CLASS_VIDEODRV, + {0, 0, 0, 0} +}; + +/* + * This data is accessed by the loader. The name must be the module name + * followed by "ModuleData". + */ +#ifdef XORG_7X +_X_EXPORT +#endif +XF86ModuleData vboxvideoModuleData = { &vboxVersionRec, vboxSetup, NULL }; + +static pointer +vboxSetup(pointer Module, pointer Options, int *ErrorMajor, int *ErrorMinor) +{ + static Bool Initialised = FALSE; + RT_NOREF(Options, ErrorMinor); + + if (!Initialised) + { + Initialised = TRUE; +#ifdef PCIACCESS + xf86AddDriver(&VBOXVIDEO, Module, HaveDriverFuncs); +#else + xf86AddDriver(&VBOXVIDEO, Module, 0); +#endif +#ifndef XORG_7X + LoaderRefSymLists(fbSymbols, + shadowfbSymbols, + ramdacSymbols, + vgahwSymbols, + NULL); +#endif + xf86Msg(X_CONFIG, "Load address of symbol \"VBOXVIDEO\" is %p\n", + (void *)&VBOXVIDEO); + return (pointer)TRUE; + } + + if (ErrorMajor) + *ErrorMajor = LDR_ONCEONLY; + return (NULL); +} + + +static const OptionInfoRec * +VBOXAvailableOptions(int chipid, int busid) +{ + RT_NOREF(chipid, busid); + return (VBOXOptions); +} + +static void +VBOXIdentify(int flags) +{ + RT_NOREF(flags); + xf86PrintChipsets(VBOX_NAME, "guest driver for VirtualBox", VBOXChipsets); +} + +#ifndef XF86_SCRN_INTERFACE +# define SCRNINDEXAPI(pfn) pfn ## Index +static Bool VBOXScreenInitIndex(int scrnIndex, ScreenPtr pScreen, int argc, char **argv) +{ + RT_NOREF(scrnIndex); + return VBOXScreenInit(pScreen, argc, argv); +} + +static Bool VBOXEnterVTIndex(int scrnIndex, int flags) +{ RT_NOREF(flags); return VBOXEnterVT(xf86Screens[scrnIndex]); } + +static void VBOXLeaveVTIndex(int scrnIndex, int flags) +{ RT_NOREF(flags); VBOXLeaveVT(xf86Screens[scrnIndex]); } + +static Bool VBOXCloseScreenIndex(int scrnIndex, ScreenPtr pScreen) +{ RT_NOREF(scrnIndex); return VBOXCloseScreen(pScreen); } + +static Bool VBOXSwitchModeIndex(int scrnIndex, DisplayModePtr pMode, int flags) +{ RT_NOREF(flags); return VBOXSwitchMode(xf86Screens[scrnIndex], pMode); } + +static void VBOXAdjustFrameIndex(int scrnIndex, int x, int y, int flags) +{ RT_NOREF(flags); VBOXAdjustFrame(xf86Screens[scrnIndex], x, y); } + +static void VBOXFreeScreenIndex(int scrnIndex, int flags) +{ RT_NOREF(flags); VBOXFreeScreen(xf86Screens[scrnIndex]); } +# else +# define SCRNINDEXAPI(pfn) pfn +#endif /* XF86_SCRN_INTERFACE */ + +static void setScreenFunctions(ScrnInfoPtr pScrn, xf86ProbeProc pfnProbe) +{ + pScrn->driverVersion = VBOX_VERSION; + pScrn->driverName = VBOX_DRIVER_NAME; + pScrn->name = VBOX_NAME; + pScrn->Probe = pfnProbe; + pScrn->PreInit = VBOXPreInit; + pScrn->ScreenInit = SCRNINDEXAPI(VBOXScreenInit); + pScrn->SwitchMode = SCRNINDEXAPI(VBOXSwitchMode); + pScrn->AdjustFrame = SCRNINDEXAPI(VBOXAdjustFrame); + pScrn->EnterVT = SCRNINDEXAPI(VBOXEnterVT); + pScrn->LeaveVT = SCRNINDEXAPI(VBOXLeaveVT); + pScrn->FreeScreen = SCRNINDEXAPI(VBOXFreeScreen); +} + +/* + * One of these functions is called once, at the start of the first server + * generation to do a minimal probe for supported hardware. + */ + +#ifdef PCIACCESS +static Bool +VBOXPciProbe(DriverPtr drv, int entity_num, struct pci_device *dev, + intptr_t match_data) +{ + ScrnInfoPtr pScrn; + int drmFd; + + TRACE_ENTRY(); + + drmFd = open("/dev/dri/card0", O_RDWR, 0); + if (drmFd >= 0) + { + xf86Msg(X_INFO, "vboxvideo: kernel driver found, not loading.\n"); + close(drmFd); + return FALSE; + } + /* It is safe to call this, as the X server enables I/O access before + * calling the probe call-backs. */ + if (!xf86EnableIO()) + { + xf86Msg(X_INFO, "vboxvideo: this driver requires direct hardware access. You may wish to use the kernel driver instead.\n"); + return FALSE; + } + pScrn = xf86ConfigPciEntity(NULL, 0, entity_num, VBOXPCIchipsets, + NULL, NULL, NULL, NULL, NULL); + if (pScrn != NULL) { + VBOXPtr pVBox; + + VBOXSetRec(pScrn); + pVBox = VBOXGetRec(pScrn); + if (!pVBox) + return FALSE; + setScreenFunctions(pScrn, NULL); + pVBox->pciInfo = dev; + } + + TRACE_LOG("returning %s\n", pScrn == NULL ? "false" : "true"); + return (pScrn != NULL); +} +#endif + +#ifndef PCIACCESS +static Bool +VBOXProbe(DriverPtr drv, int flags) +{ + Bool foundScreen = FALSE; + int numDevSections; + GDevPtr *devSections; + + /* + * Find the config file Device sections that match this + * driver, and return if there are none. + */ + if ((numDevSections = xf86MatchDevice(VBOX_NAME, + &devSections)) <= 0) + return (FALSE); + + /* PCI BUS */ + if (xf86GetPciVideoInfo()) + { + int numUsed; + int *usedChips; + int i; + numUsed = xf86MatchPciInstances(VBOX_NAME, VBOX_VENDORID, + VBOXChipsets, VBOXPCIchipsets, + devSections, numDevSections, + drv, &usedChips); + if (numUsed > 0) + { + if (flags & PROBE_DETECT) + foundScreen = TRUE; + else + for (i = 0; i < numUsed; i++) + { + ScrnInfoPtr pScrn = NULL; + /* Allocate a ScrnInfoRec */ + if ((pScrn = xf86ConfigPciEntity(pScrn,0,usedChips[i], + VBOXPCIchipsets,NULL, + NULL,NULL,NULL,NULL))) + { + setScreenFunctions(pScrn, VBOXProbe); + foundScreen = TRUE; + } + } + free(usedChips); + } + } + free(devSections); + return (foundScreen); +} +#endif + + +/* + * QUOTE from the XFree86 DESIGN document: + * + * The purpose of this function is to find out all the information + * required to determine if the configuration is usable, and to initialise + * those parts of the ScrnInfoRec that can be set once at the beginning of + * the first server generation. + * + * (...) + * + * This includes probing for video memory, clocks, ramdac, and all other + * HW info that is needed. It includes determining the depth/bpp/visual + * and related info. It includes validating and determining the set of + * video modes that will be used (and anything that is required to + * determine that). + * + * This information should be determined in the least intrusive way + * possible. The state of the HW must remain unchanged by this function. + * Although video memory (including MMIO) may be mapped within this + * function, it must be unmapped before returning. + * + * END QUOTE + */ + +static Bool +VBOXPreInit(ScrnInfoPtr pScrn, int flags) +{ + VBOXPtr pVBox; + Gamma gzeros = {0.0, 0.0, 0.0}; + rgb rzeros = {0, 0, 0}; + + TRACE_ENTRY(); + /* Are we really starting the server, or is this just a dummy run? */ + if (flags & PROBE_DETECT) + return (FALSE); + + xf86DrvMsg(pScrn->scrnIndex, X_INFO, + "VirtualBox guest additions video driver version %d.%d\n", + VBOX_VERSION_MAJOR, VBOX_VERSION_MINOR); + + /* The ramdac module is needed for the hardware cursor. */ + if (!xf86LoadSubModule(pScrn, "ramdac")) + return FALSE; + + /* The framebuffer module. */ + if (!xf86LoadSubModule(pScrn, "fb")) + return (FALSE); + + if (!xf86LoadSubModule(pScrn, "shadowfb")) + return FALSE; + + if (!xf86LoadSubModule(pScrn, "vgahw")) + return FALSE; + + /* Get our private data from the ScrnInfoRec structure. */ + VBOXSetRec(pScrn); + pVBox = VBOXGetRec(pScrn); + if (!pVBox) + return FALSE; + + /* Entity information seems to mean bus information. */ + pVBox->pEnt = xf86GetEntityInfo(pScrn->entityList[0]); + +#ifndef PCIACCESS + if (pVBox->pEnt->location.type != BUS_PCI) + return FALSE; + + pVBox->pciInfo = xf86GetPciInfoForEntity(pVBox->pEnt->index); + pVBox->pciTag = pciTag(pVBox->pciInfo->bus, + pVBox->pciInfo->device, + pVBox->pciInfo->func); +#endif + + /* Set up our ScrnInfoRec structure to describe our virtual + capabilities to X. */ + + pScrn->chipset = "vbox"; + /** @note needed during colourmap initialisation */ + pScrn->rgbBits = 8; + + /* Let's create a nice, capable virtual monitor. */ + pScrn->monitor = pScrn->confScreen->monitor; + pScrn->monitor->DDC = NULL; + pScrn->monitor->nHsync = 1; + pScrn->monitor->hsync[0].lo = 1; + pScrn->monitor->hsync[0].hi = 10000; + pScrn->monitor->nVrefresh = 1; + pScrn->monitor->vrefresh[0].lo = 1; + pScrn->monitor->vrefresh[0].hi = 100; + + pScrn->progClock = TRUE; + + /* Using the PCI information caused problems with non-powers-of-two + sized video RAM configurations */ + pVBox->cbFBMax = VBoxVideoGetVRAMSize(); + pScrn->videoRam = pVBox->cbFBMax / 1024; + + /* Check if the chip restricts horizontal resolution or not. */ + pVBox->fAnyX = VBoxVideoAnyWidthAllowed(); + + /* Set up clock information that will support all modes we need. */ + pScrn->clockRanges = xnfcalloc(sizeof(ClockRange), 1); + pScrn->clockRanges->minClock = 1000; + pScrn->clockRanges->maxClock = 1000000000; + pScrn->clockRanges->clockIndex = -1; + pScrn->clockRanges->ClockMulFactor = 1; + pScrn->clockRanges->ClockDivFactor = 1; + + if (!xf86SetDepthBpp(pScrn, 24, 0, 0, Support32bppFb)) + return FALSE; + /* We only support 16 and 24 bits depth (i.e. 16 and 32bpp) */ + if (pScrn->bitsPerPixel != 32 && pScrn->bitsPerPixel != 16) + { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "The VBox additions only support 16 and 32bpp graphics modes\n"); + return FALSE; + } + xf86PrintDepthBpp(pScrn); + vboxAddModes(pScrn); + +#ifdef VBOXVIDEO_13 + pScrn->virtualX = VBOX_VIDEO_MAX_VIRTUAL; + pScrn->virtualY = VBOX_VIDEO_MAX_VIRTUAL; +#else + /* We don't validate with xf86ValidateModes and xf86PruneModes as we + * already know what we like and what we don't. */ + + pScrn->currentMode = pScrn->modes; + + /* Set the right virtual resolution. */ + pScrn->virtualX = pScrn->bitsPerPixel == 16 ? (pScrn->currentMode->HDisplay + 1) & ~1 : pScrn->currentMode->HDisplay; + pScrn->virtualY = pScrn->currentMode->VDisplay; + +#endif /* !VBOXVIDEO_13 */ + + pScrn->displayWidth = pScrn->virtualX; + + xf86PrintModes(pScrn); + + /* VGA hardware initialisation */ + if (!vgaHWGetHWRec(pScrn)) + return FALSE; + /* Must be called before any VGA registers are saved or restored */ + vgaHWSetStdFuncs(VGAHWPTR(pScrn)); + vgaHWGetIOBase(VGAHWPTR(pScrn)); + + /* Colour weight - we always call this, since we are always in + truecolour. */ + if (!xf86SetWeight(pScrn, rzeros, rzeros)) + return (FALSE); + + /* visual init */ + if (!xf86SetDefaultVisual(pScrn, -1)) + return (FALSE); + + xf86SetGamma(pScrn, gzeros); + + /* Set the DPI. Perhaps we should read this from the host? */ + xf86SetDpi(pScrn, 96, 96); + + if (pScrn->memPhysBase == 0) { +#ifdef PCIACCESS + pScrn->memPhysBase = pVBox->pciInfo->regions[0].base_addr; +#else + pScrn->memPhysBase = pVBox->pciInfo->memBase[0]; +#endif + pScrn->fbOffset = 0; + } + + TRACE_EXIT(); + return (TRUE); +} + +/** + * Dummy function for setting the colour palette, which we actually never + * touch. However, the server still requires us to provide this. + */ +static void +vboxLoadPalette(ScrnInfoPtr pScrn, int numColors, int *indices, + LOCO *colors, VisualPtr pVisual) +{ + RT_NOREF(pScrn, numColors, indices, colors, pVisual); +} + +/** Set the graphics and guest cursor support capabilities to the host if + * the user-space helper is running. */ +static void updateGraphicsCapability(ScrnInfoPtr pScrn, Bool hasVT) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + + if (!pVBox->fHaveHGSMIModeHints) + return; + VBoxHGSMISendCapsInfo(&pVBox->guestCtx, hasVT + ? VBVACAPS_VIDEO_MODE_HINTS | VBVACAPS_DISABLE_CURSOR_INTEGRATION + : VBVACAPS_DISABLE_CURSOR_INTEGRATION); +} + +#ifndef VBOXVIDEO_13 + +#define PREFERRED_MODE_ATOM_NAME "VBOXVIDEO_PREFERRED_MODE" + +static void setSizesRandR11(ScrnInfoPtr pScrn) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + DisplayModePtr pNewMode; + int32_t propertyValue; + + pNewMode = pScrn->modes != pScrn->currentMode ? pScrn->modes : pScrn->modes->next; + pNewMode->HDisplay = RT_CLAMP(pVBox->pScreens[0].aPreferredSize.cx, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL); + pNewMode->VDisplay = RT_CLAMP(pVBox->pScreens[0].aPreferredSize.cy, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL); + propertyValue = (pNewMode->HDisplay << 16) + pNewMode->VDisplay; + ChangeWindowProperty(ROOT_WINDOW(pScrn), MakeAtom(PREFERRED_MODE_ATOM_NAME, + sizeof(PREFERRED_MODE_ATOM_NAME) - 1, TRUE), XA_INTEGER, 32, + PropModeReplace, 1, &propertyValue, TRUE); +} + +#endif + +static void reprobeCursor(ScrnInfoPtr pScrn) +{ + if (ROOT_WINDOW(pScrn) == NULL) + return; +#ifdef XF86_SCRN_INTERFACE + pScrn->EnableDisableFBAccess(pScrn, FALSE); + pScrn->EnableDisableFBAccess(pScrn, TRUE); +#else + pScrn->EnableDisableFBAccess(pScrn->scrnIndex, FALSE); + pScrn->EnableDisableFBAccess(pScrn->scrnIndex, TRUE); +#endif +} + +static void setSizesAndCursorIntegration(ScrnInfoPtr pScrn, Bool fScreenInitTime) +{ + RT_NOREF(fScreenInitTime); + TRACE_LOG("fScreenInitTime=%d\n", (int)fScreenInitTime); +#ifdef VBOXVIDEO_13 +# if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) >= 5 + RRGetInfo(xf86ScrnToScreen(pScrn), TRUE); +# else + RRGetInfo(xf86ScrnToScreen(pScrn)); +# endif +#else + setSizesRandR11(pScrn); +#endif + /* This calls EnableDisableFBAccess(), so only use when switched in. */ + if (pScrn->vtSema) + reprobeCursor(pScrn); +} + +/* We update the size hints from the X11 property set by VBoxClient every time + * that the X server goes to sleep (to catch the property change request). + * Although this is far more often than necessary it should not have real-life + * performance consequences and allows us to simplify the code quite a bit. */ +static void vboxBlockHandler(pointer pData, +#if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) < 23 + OSTimePtr pTimeout, + pointer pReadmask +#else + void *pTimeout +#endif + ) +{ + ScrnInfoPtr pScrn = (ScrnInfoPtr)pData; + Bool fNeedUpdate = false; + + RT_NOREF(pTimeout); +#if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) < 23 + RT_NOREF(pReadmask); +#endif + if (pScrn->vtSema) + vbvxReadSizesAndCursorIntegrationFromHGSMI(pScrn, &fNeedUpdate); + if (fNeedUpdate) + setSizesAndCursorIntegration(pScrn, false); +} + +/* + * QUOTE from the XFree86 DESIGN document: + * + * This is called at the start of each server generation. + * + * (...) + * + * Decide which operations need to be placed under resource access + * control. (...) Map any video memory or other memory regions. (...) + * Save the video card state. (...) Initialise the initial video + * mode. + * + * End QUOTE. + */ +static Bool VBOXScreenInit(ScreenPtr pScreen, int argc, char **argv) +{ + ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen); + VBOXPtr pVBox = VBOXGetRec(pScrn); + VisualPtr visual; + RT_NOREF(argc, argv); + + TRACE_ENTRY(); + + if (!VBOXMapVidMem(pScrn)) + return (FALSE); + + /* save current video state */ + VBOXSaveMode(pScrn); + + /* mi layer - reset the visual list (?)*/ + miClearVisualTypes(); + if (!miSetVisualTypes(pScrn->depth, TrueColorMask, + pScrn->rgbBits, TrueColor)) + return (FALSE); + if (!miSetPixmapDepths()) + return (FALSE); + + if (!fbScreenInit(pScreen, pVBox->base, + pScrn->virtualX, pScrn->virtualY, + pScrn->xDpi, pScrn->yDpi, + pScrn->displayWidth, pScrn->bitsPerPixel)) + return (FALSE); + + /* Fixup RGB ordering */ + /** @note the X server uses this even in true colour. */ + visual = pScreen->visuals + pScreen->numVisuals; + while (--visual >= pScreen->visuals) { + if ((visual->class | DynamicClass) == DirectColor) { + visual->offsetRed = pScrn->offset.red; + visual->offsetGreen = pScrn->offset.green; + visual->offsetBlue = pScrn->offset.blue; + visual->redMask = pScrn->mask.red; + visual->greenMask = pScrn->mask.green; + visual->blueMask = pScrn->mask.blue; + } + } + + /* must be after RGB ordering fixed */ + fbPictureInit(pScreen, 0, 0); + + xf86SetBlackWhitePixels(pScreen); + pScrn->vtSema = TRUE; + +#if defined(VBOXVIDEO_13) && defined(RT_OS_LINUX) + vbvxSetUpLinuxACPI(pScreen); +#endif + + if (!VBoxHGSMIIsSupported()) + { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Graphics device too old to support.\n"); + return FALSE; + } + vbvxSetUpHGSMIHeapInGuest(pVBox, pScrn->videoRam * 1024); + pVBox->cScreens = VBoxHGSMIGetMonitorCount(&pVBox->guestCtx); + pVBox->pScreens = xnfcalloc(pVBox->cScreens, sizeof(*pVBox->pScreens)); + pVBox->paVBVAModeHints = xnfcalloc(pVBox->cScreens, sizeof(*pVBox->paVBVAModeHints)); + xf86DrvMsg(pScrn->scrnIndex, X_INFO, "Requested monitor count: %u\n", pVBox->cScreens); + vboxEnableVbva(pScrn); + /* Set up the dirty rectangle handler. It will be added into a function + * chain and gets removed when the screen is cleaned up. */ + if (ShadowFBInit2(pScreen, NULL, vbvxHandleDirtyRect) != TRUE) + return FALSE; + VBoxInitialiseSizeHints(pScrn); + +#ifdef VBOXVIDEO_13 + /* Initialise CRTC and output configuration for use with randr1.2. */ + xf86CrtcConfigInit(pScrn, &VBOXCrtcConfigFuncs); + + { + uint32_t i; + + for (i = 0; i < pVBox->cScreens; ++i) + { + char szOutput[256]; + + /* Setup our virtual CRTCs. */ + pVBox->pScreens[i].paCrtcs = xf86CrtcCreate(pScrn, &VBOXCrtcFuncs); + pVBox->pScreens[i].paCrtcs->driver_private = (void *)(uintptr_t)i; + + /* Set up our virtual outputs. */ + snprintf(szOutput, sizeof(szOutput), "VGA-%u", i); + pVBox->pScreens[i].paOutputs + = xf86OutputCreate(pScrn, &VBOXOutputFuncs, szOutput); + + /* We are not interested in the monitor section in the + * configuration file. */ + xf86OutputUseScreenMonitor(pVBox->pScreens[i].paOutputs, FALSE); + pVBox->pScreens[i].paOutputs->possible_crtcs = 1 << i; + pVBox->pScreens[i].paOutputs->possible_clones = 0; + pVBox->pScreens[i].paOutputs->driver_private = (void *)(uintptr_t)i; + TRACE_LOG("Created crtc (%p) and output %s (%p)\n", + (void *)pVBox->pScreens[i].paCrtcs, szOutput, + (void *)pVBox->pScreens[i].paOutputs); + } + } + + /* Set a sane minimum and maximum mode size to match what the hardware + * supports. */ + xf86CrtcSetSizeRange(pScrn, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL, VBOX_VIDEO_MAX_VIRTUAL); + + /* Now create our initial CRTC/output configuration. */ + if (!xf86InitialConfiguration(pScrn, TRUE)) { + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, "Initial CRTC configuration failed!\n"); + return (FALSE); + } + + /* Work around a bug in the original X server modesetting code, which took + * the first valid values set to these two as maxima over the server + * lifetime. This bug was introduced on Feb 15 2007 and was fixed in commit + * fa877d7f three months later, so it was present in X.Org Server 1.3. */ + pScrn->virtualX = VBOX_VIDEO_MAX_VIRTUAL; + pScrn->virtualY = VBOX_VIDEO_MAX_VIRTUAL; + + /* Initialise randr 1.2 mode-setting functions. */ + if (!xf86CrtcScreenInit(pScreen)) { + return FALSE; + } + + /* set first video mode */ + if (!xf86SetDesiredModes(pScrn)) { + return FALSE; + } +#else /* !VBOXVIDEO_13 */ + /* set first video mode */ + setModeRandR11(pScrn, pScrn->currentMode, true, false, 0, 0); +#endif /* !VBOXVIDEO_13 */ + + /* Say that we support graphics. */ + updateGraphicsCapability(pScrn, TRUE); + +#if GET_ABI_MAJOR(ABI_VIDEODRV_VERSION) >= 23 +# define WakeupHandlerProcPtr ServerWakeupHandlerProcPtr +#endif + + /* Register block and wake-up handlers for getting new screen size hints. */ + RegisterBlockAndWakeupHandlers(vboxBlockHandler, (WakeupHandlerProcPtr)NoopDDA, (pointer)pScrn); + + /* software cursor */ + miDCInitialize(pScreen, xf86GetPointerScreenFuncs()); + + /* colourmap code */ + if (!miCreateDefColormap(pScreen)) + return (FALSE); + + if(!xf86HandleColormaps(pScreen, 256, 8, vboxLoadPalette, NULL, 0)) + return (FALSE); + + pVBox->CloseScreen = pScreen->CloseScreen; + pScreen->CloseScreen = SCRNINDEXAPI(VBOXCloseScreen); +#ifdef VBOXVIDEO_13 + pScreen->SaveScreen = xf86SaveScreen; +#else + pScreen->SaveScreen = VBOXSaveScreen; +#endif + +#ifdef VBOXVIDEO_13 + xf86DPMSInit(pScreen, xf86DPMSSet, 0); +#else + /* We probably do want to support power management - even if we just use + a dummy function. */ + xf86DPMSInit(pScreen, VBOXDisplayPowerManagementSet, 0); +#endif + + /* Report any unused options (only for the first generation) */ + if (serverGeneration == 1) + xf86ShowUnusedOptions(pScrn->scrnIndex, pScrn->options); + + if (vbvxCursorInit(pScreen) != TRUE) + xf86DrvMsg(pScrn->scrnIndex, X_ERROR, + "Unable to start the VirtualBox mouse pointer integration with the host system.\n"); + + return (TRUE); +} + +#define NO_VT_ATOM_NAME "VBOXVIDEO_NO_VT" + +static Bool VBOXEnterVT(ScrnInfoPtr pScrn) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); +#ifndef VBOXVIDEO_13 + /* If we got a mode request while we were switched out, temporarily override + * the physical mode set to the device while keeping things consistent from + * the server's point of view. */ + int cXOverRide = RT_CLAMP(pVBox->pScreens[0].aPreferredSize.cx, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL); + int cYOverRide = RT_CLAMP(pVBox->pScreens[0].aPreferredSize.cy, VBOX_VIDEO_MIN_SIZE, VBOX_VIDEO_MAX_VIRTUAL); +#endif + + TRACE_ENTRY(); + vbvxSetUpHGSMIHeapInGuest(pVBox, pScrn->videoRam * 1024); + vboxEnableVbva(pScrn); + /* Re-set video mode */ +#ifdef VBOXVIDEO_13 + if (!xf86SetDesiredModes(pScrn)) { + return FALSE; + } +#else + setModeRandR11(pScrn, pScrn->currentMode, false, true, cXOverRide, cYOverRide); + DeleteProperty(ROOT_WINDOW(pScrn), MakeAtom(NO_VT_ATOM_NAME, sizeof(NO_VT_ATOM_NAME) - 1, TRUE)); +#endif + updateGraphicsCapability(pScrn, TRUE); + return TRUE; +} + +static void VBOXLeaveVT(ScrnInfoPtr pScrn) +{ +#ifdef VBOXVIDEO_13 + VBOXPtr pVBox = VBOXGetRec(pScrn); + unsigned i; +#else + int32_t propertyValue = 0; +#endif + + TRACE_ENTRY(); +#ifdef VBOXVIDEO_13 + for (i = 0; i < pVBox->cScreens; ++i) + vbox_crtc_dpms(pVBox->pScreens[i].paCrtcs, DPMSModeOff); +#else + ChangeWindowProperty(ROOT_WINDOW(pScrn), MakeAtom(NO_VT_ATOM_NAME, sizeof(NO_VT_ATOM_NAME) - 1, FALSE), XA_INTEGER, 32, + PropModeReplace, 1, &propertyValue, TRUE); +#endif + updateGraphicsCapability(pScrn, FALSE); + vboxDisableVbva(pScrn); + vbvxClearVRAM(pScrn, ((size_t)pScrn->virtualX) * pScrn->virtualY * (pScrn->bitsPerPixel / 8), 0); + VBOXRestoreMode(pScrn); + TRACE_EXIT(); +} + +static Bool VBOXCloseScreen(ScreenPtr pScreen) +{ + ScrnInfoPtr pScrn = xf86ScreenToScrn(pScreen); + VBOXPtr pVBox = VBOXGetRec(pScrn); + BOOL ret; + + if (pScrn->vtSema) + { +#ifdef VBOXVIDEO_13 + unsigned i; + + for (i = 0; i < pVBox->cScreens; ++i) + vbox_crtc_dpms(pVBox->pScreens[i].paCrtcs, DPMSModeOff); +#endif + vboxDisableVbva(pScrn); + vbvxClearVRAM(pScrn, ((size_t)pScrn->virtualX) * pScrn->virtualY * (pScrn->bitsPerPixel / 8), 0); + } + if (pScrn->vtSema) + VBOXRestoreMode(pScrn); + if (pScrn->vtSema) + VBOXUnmapVidMem(pScrn); + pScrn->vtSema = FALSE; + + vbvxCursorTerm(pVBox); + + pScreen->CloseScreen = pVBox->CloseScreen; +#if defined(VBOXVIDEO_13) && defined(RT_OS_LINUX) + vbvxCleanUpLinuxACPI(pScreen); +#endif +#ifndef XF86_SCRN_INTERFACE + ret = pScreen->CloseScreen(pScreen->myNum, pScreen); +#else + ret = pScreen->CloseScreen(pScreen); +#endif + return ret; +} + +static Bool VBOXSwitchMode(ScrnInfoPtr pScrn, DisplayModePtr pMode) +{ + Bool rc = TRUE; + + TRACE_LOG("HDisplay=%d, VDisplay=%d\n", pMode->HDisplay, pMode->VDisplay); +#ifdef VBOXVIDEO_13 + rc = xf86SetSingleMode(pScrn, pMode, RR_Rotate_0); +#else + setModeRandR11(pScrn, pMode, false, false, 0, 0); +#endif + TRACE_LOG("returning %s\n", rc ? "TRUE" : "FALSE"); + return rc; +} + +static void VBOXAdjustFrame(ScrnInfoPtr pScrn, int x, int y) +{ RT_NOREF(pScrn, x, y); } + +static void VBOXFreeScreen(ScrnInfoPtr pScrn) +{ + /* Destroy the VGA hardware record */ + vgaHWFreeHWRec(pScrn); + /* And our private record */ + free(pScrn->driverPrivate); + pScrn->driverPrivate = NULL; +} + +static Bool +VBOXMapVidMem(ScrnInfoPtr pScrn) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + Bool rc = TRUE; + + TRACE_ENTRY(); + if (!pVBox->base) + { +#ifdef PCIACCESS + (void) pci_device_map_range(pVBox->pciInfo, + pScrn->memPhysBase, + pScrn->videoRam * 1024, + PCI_DEV_MAP_FLAG_WRITABLE, + & pVBox->base); +#else + pVBox->base = xf86MapPciMem(pScrn->scrnIndex, + VIDMEM_FRAMEBUFFER, + pVBox->pciTag, pScrn->memPhysBase, + (unsigned) pScrn->videoRam * 1024); +#endif + if (!pVBox->base) + rc = FALSE; + } + TRACE_LOG("returning %s\n", rc ? "TRUE" : "FALSE"); + return rc; +} + +static void +VBOXUnmapVidMem(ScrnInfoPtr pScrn) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + + TRACE_ENTRY(); + if (pVBox->base == NULL) + return; + +#ifdef PCIACCESS + (void) pci_device_unmap_range(pVBox->pciInfo, + pVBox->base, + pScrn->videoRam * 1024); +#else + xf86UnMapVidMem(pScrn->scrnIndex, pVBox->base, + (unsigned) pScrn->videoRam * 1024); +#endif + pVBox->base = NULL; + TRACE_EXIT(); +} + +#ifndef VBOXVIDEO_13 +static Bool +VBOXSaveScreen(ScreenPtr pScreen, int mode) +{ + RT_NOREF(pScreen, mode); + return TRUE; +} +#endif + +void +VBOXSaveMode(ScrnInfoPtr pScrn) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + vgaRegPtr vgaReg; + + TRACE_ENTRY(); + vgaReg = &VGAHWPTR(pScrn)->SavedReg; + vgaHWSave(pScrn, vgaReg, VGA_SR_ALL); + pVBox->fSavedVBEMode = VBoxVideoGetModeRegisters(&pVBox->cSavedWidth, + &pVBox->cSavedHeight, + &pVBox->cSavedPitch, + &pVBox->cSavedBPP, + &pVBox->fSavedFlags); +} + +void +VBOXRestoreMode(ScrnInfoPtr pScrn) +{ + VBOXPtr pVBox = VBOXGetRec(pScrn); + vgaRegPtr vgaReg; + + TRACE_ENTRY(); + vgaReg = &VGAHWPTR(pScrn)->SavedReg; + vgaHWRestore(pScrn, vgaReg, VGA_SR_ALL); + if (pVBox->fSavedVBEMode) + VBoxVideoSetModeRegisters(pVBox->cSavedWidth, pVBox->cSavedHeight, + pVBox->cSavedPitch, pVBox->cSavedBPP, + pVBox->fSavedFlags, 0, 0); + else + VBoxVideoDisableVBE(); +} + +#ifndef VBOXVIDEO_13 +static void +VBOXDisplayPowerManagementSet(ScrnInfoPtr pScrn, int mode, int flags) +{ + RT_NOREF(pScrn, mode, flags); +} +#endif diff --git a/src/VBox/Additions/x11/vboxvideo/vboxvideo.h b/src/VBox/Additions/x11/vboxvideo/vboxvideo.h new file mode 100644 index 00000000..8c47c61c --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/vboxvideo.h @@ -0,0 +1,240 @@ +/* $Id: vboxvideo.h $ */ +/** @file + * VirtualBox X11 Additions graphics driver + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * This file is based on the X11 VESA driver: + * + * Copyright (c) 2000 by Conectiva S.A. (http://www.conectiva.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of Conectiva Linux shall + * not be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization from + * Conectiva Linux. + * + * Authors: Paulo César Pereira de Andrade <pcpa@conectiva.com.br> + * Michael Thayer <michael.thayer@oracle.com> + */ + +#ifndef GA_INCLUDED_SRC_x11_vboxvideo_vboxvideo_h +#define GA_INCLUDED_SRC_x11_vboxvideo_vboxvideo_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBoxVideoGuest.h> +#include <VBoxVideo.h> +#include "version-generated.h" + +#define VBOX_VENDORID 0x80EE +#define VBOX_DEVICEID 0xBEEF + +#ifndef VBVA_SCREEN_F_BLANK +# define VBVA_SCREEN_F_BLANK 0x0004 +#endif + +#include <VBoxVideoVBE.h> + +#include "xf86.h" +#include "xf86str.h" +#include "xf86Cursor.h" + +#ifdef DEBUG + +#define TRACE_ENTRY() do { xf86ErrorF("%s: entering\n", __func__); } while(0) +#define TRACE_EXIT() do { xf86ErrorF("%s: leaving\n", __func__); } while(0) +#define TRACE_LINE() \ + do { xf86ErrorF("%s: line\n", __func__, __LINE__); } while(0) +#define TRACE_LOG(...) \ +do { \ + xf86ErrorF("%s: ", __func__); \ + xf86ErrorF(__VA_ARGS__); \ +} while(0) + +#else /* !DEBUG */ + +#define TRACE_ENTRY() do { } while (0) +#define TRACE_EXIT() do { } while (0) +#define TRACE_LOG(...) do { } while (0) + +#endif /* !DEBUG */ + +#define VBOX_VERSION VBOX_VERSION_MAJOR * 10000 \ + + VBOX_VERSION_MINOR * 100 +#define VBOX_NAME "VBoxVideo" +#define VBOX_DRIVER_NAME "vboxvideo" + +#define VBOX_VIDEO_MAJOR VBOX_VERSION_MAJOR +#define VBOX_VIDEO_MINOR VBOX_VERSION_MINOR + +#define VBOX_VIDEO_MIN_SIZE 64 +#define VBOX_VIDEO_MAX_VIRTUAL (INT16_MAX - 1) + +#define VBOXPTR(p) ((VBOXPtr)((p)->driverPrivate)) + +/** Helper to work round different ways of getting the root window in different + * server versions. */ +#if defined(XORG_VERSION_CURRENT) && XORG_VERSION_CURRENT < 70000000 \ + && XORG_VERSION_CURRENT >= 10900000 +# define ROOT_WINDOW(pScrn) screenInfo.screens[(pScrn)->scrnIndex]->root +#else +# define ROOT_WINDOW(pScrn) WindowTable[(pScrn)->scrnIndex] +#endif + +/** ChangeWindowProperty for X.Org Server 1.19 and later */ +#if defined(XORG_VERSION_CURRENT) && XORG_VERSION_CURRENT < 70000000 \ + && XORG_VERSION_CURRENT >= 11900000 +# define ChangeWindowProperty(pWin, property, type, format, mode, \ + len, value, sendevent) \ + dixChangeWindowProperty(serverClient, pWin, property, type, format, \ + mode, len, value, sendevent) +#endif + +/** Structure containing all virtual monitor-specific information. */ +struct VBoxScreen +{ + /** Position information for each virtual screen for the purposes of + * sending dirty rectangle information to the right one. */ + RTRECT2 aScreenLocation; + /** Is this CRTC enabled or in DPMS off state? */ + Bool fPowerOn; +#ifdef VBOXVIDEO_13 + /** The virtual crtcs. */ + struct _xf86Crtc *paCrtcs; + /** The virtual outputs, logically not distinct from crtcs. */ + struct _xf86Output *paOutputs; +#endif + /** Offsets of VBVA buffers in video RAM */ + uint32_t aoffVBVABuffer; + /** Context information about the VBVA buffers for each screen */ + struct VBVABUFFERCONTEXT aVbvaCtx; + /** The current preferred resolution for the screen */ + RTRECTSIZE aPreferredSize; + /** The current preferred location for the screen. */ + RTPOINT aPreferredLocation; + /** Has this screen been enabled by the host? */ + Bool afConnected; + /** Does this screen have a preferred location? */ + Bool afHaveLocation; +}; + +typedef struct VBOXRec +{ + EntityInfoPtr pEnt; +#ifdef PCIACCESS + struct pci_device *pciInfo; +#else + pciVideoPtr pciInfo; + PCITAG pciTag; +#endif + void *base; + /** The amount of VRAM available for use as a framebuffer */ + unsigned long cbFBMax; + /** The size of the framebuffer and the VBVA buffers at the end of it. */ + unsigned long cbView; + /** Whether the pre-X-server mode was a VBE mode */ + Bool fSavedVBEMode; + /** Paramters of the saved pre-X-server VBE mode, invalid if there is none + */ + uint16_t cSavedWidth, cSavedHeight, cSavedPitch, cSavedBPP, fSavedFlags; + CloseScreenProcPtr CloseScreen; + /** Default X server procedure for enabling and disabling framebuffer access */ + xf86EnableDisableFBAccessProc *EnableDisableFBAccess; + OptionInfoPtr Options; + /** @todo we never actually free this */ + xf86CursorInfoPtr pCurs; + /** Do we currently want to use the host cursor? */ + Bool fUseHardwareCursor; + /** Number of screens attached */ + uint32_t cScreens; + /** Information about each virtual screen. */ + struct VBoxScreen *pScreens; + /** Can we get mode hint and cursor integration information from HGSMI? */ + Bool fHaveHGSMIModeHints; + /** Does the host support the screen blanking flag? */ + Bool fHostHasScreenBlankingFlag; + /** Array of structures for receiving mode hints. */ + VBVAMODEHINT *paVBVAModeHints; +#ifdef VBOXVIDEO_13 +# ifdef RT_OS_LINUX + /** Input device file descriptor for getting ACPI hot-plug events. */ + int fdACPIDevices; + /** Input handler handle for ACPI hot-plug listener. */ + void *hACPIEventHandler; +# endif +#endif + /** HGSMI guest heap context */ + HGSMIGUESTCOMMANDCONTEXT guestCtx; + /** Unrestricted horizontal resolution flag. */ + Bool fAnyX; +} VBOXRec, *VBOXPtr; + +#define VBOXGetRec(pScrn) ((VBOXPtr)(pScrn)->driverPrivate) + +/* setmode.c */ + +/** Structure describing the virtual frame buffer. It starts at the beginning + * of the video RAM. */ +struct vbvxFrameBuffer { + /** X offset of first screen in frame buffer. */ + int x0; + /** Y offset of first screen in frame buffer. */ + int y0; + /** Frame buffer virtual width. */ + unsigned cWidth; + /** Frame buffer virtual height. */ + unsigned cHeight; + /** Bits per pixel. */ + unsigned cBPP; +}; + +extern void vbvxClearVRAM(ScrnInfoPtr pScrn, size_t cbOldSize, size_t cbNewSize); +extern void vbvxSetMode(ScrnInfoPtr pScrn, unsigned cDisplay, unsigned cWidth, unsigned cHeight, int x, int y, Bool fEnabled, + Bool fConnected, struct vbvxFrameBuffer *pFrameBuffer); +extern void vbvxSetSolarisMouseRange(int width, int height); + +/* pointer.h */ +extern Bool vbvxCursorInit(ScreenPtr pScreen); +extern void vbvxCursorTerm(VBOXPtr pVBox); + +/* vbva.c */ +extern void vbvxHandleDirtyRect(ScrnInfoPtr pScrn, int iRects, BoxPtr aRects); +extern void vbvxSetUpHGSMIHeapInGuest(VBOXPtr pVBox, uint32_t cbVRAM); +extern Bool vboxEnableVbva(ScrnInfoPtr pScrn); +extern void vboxDisableVbva(ScrnInfoPtr pScrn); + +/* getmode.c */ +extern void vboxAddModes(ScrnInfoPtr pScrn); +extern void VBoxInitialiseSizeHints(ScrnInfoPtr pScrn); +extern void vbvxReadSizesAndCursorIntegrationFromProperties(ScrnInfoPtr pScrn, Bool *pfNeedUpdate); +extern void vbvxReadSizesAndCursorIntegrationFromHGSMI(ScrnInfoPtr pScrn, Bool *pfNeedUpdate); +extern void vbvxSetUpLinuxACPI(ScreenPtr pScreen); +extern void vbvxCleanUpLinuxACPI(ScreenPtr pScreen); + +/* EDID generation */ +#ifdef VBOXVIDEO_13 +extern Bool VBOXEDIDSet(struct _xf86Output *output, DisplayModePtr pmode); +#endif + +#endif /* !GA_INCLUDED_SRC_x11_vboxvideo_vboxvideo_h */ + diff --git a/src/VBox/Additions/x11/vboxvideo/vbva.c b/src/VBox/Additions/x11/vboxvideo/vbva.c new file mode 100644 index 00000000..e885d082 --- /dev/null +++ b/src/VBox/Additions/x11/vboxvideo/vbva.c @@ -0,0 +1,254 @@ +/* $Id: vbva.c $ */ +/** @file + * VirtualBox X11 Additions graphics driver 2D acceleration functions + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + */ + +#if defined(IN_XF86_MODULE) && !defined(NO_ANSIC) +# include "xf86_ansic.h" +#endif +#include "compiler.h" + +#include "vboxvideo.h" + +#ifdef XORG_7X +# include <stdlib.h> +# include <string.h> +#endif + +/************************************************************************** +* Main functions * +**************************************************************************/ + +/** + * Callback function called by the X server to tell us about dirty + * rectangles in the video buffer. + * + * @param pScrn pointer to the information structure for the current + * screen + * @param iRects Number of dirty rectangles to update + * @param aRects Array of structures containing the coordinates of the + * rectangles + */ +void vbvxHandleDirtyRect(ScrnInfoPtr pScrn, int iRects, BoxPtr aRects) +{ + VBVACMDHDR cmdHdr; + VBOXPtr pVBox; + int i; + unsigned j; + + pVBox = pScrn->driverPrivate; + if (!pScrn->vtSema) + return; + + for (j = 0; j < pVBox->cScreens; ++j) + { + /* Just continue quietly if VBVA is not currently active. */ + struct VBVABUFFER *pVBVA = pVBox->pScreens[j].aVbvaCtx.pVBVA; + if ( !pVBVA + || !(pVBVA->hostFlags.u32HostEvents & VBVA_F_MODE_ENABLED)) + continue; + for (i = 0; i < iRects; ++i) + { + if ( aRects[i].x1 > pVBox->pScreens[j].aScreenLocation.x + + pVBox->pScreens[j].aScreenLocation.cx + || aRects[i].y1 > pVBox->pScreens[j].aScreenLocation.y + + pVBox->pScreens[j].aScreenLocation.cy + || aRects[i].x2 < pVBox->pScreens[j].aScreenLocation.x + || aRects[i].y2 < pVBox->pScreens[j].aScreenLocation.y) + continue; + cmdHdr.x = (int16_t)aRects[i].x1 - pVBox->pScreens[0].aScreenLocation.x; + cmdHdr.y = (int16_t)aRects[i].y1 - pVBox->pScreens[0].aScreenLocation.y; + cmdHdr.w = (uint16_t)(aRects[i].x2 - aRects[i].x1); + cmdHdr.h = (uint16_t)(aRects[i].y2 - aRects[i].y1); + +#if 0 + TRACE_LOG("display=%u, x=%d, y=%d, w=%d, h=%d\n", + j, cmdHdr.x, cmdHdr.y, cmdHdr.w, cmdHdr.h); +#endif + + if (VBoxVBVABufferBeginUpdate(&pVBox->pScreens[j].aVbvaCtx, + &pVBox->guestCtx)) + { + VBoxVBVAWrite(&pVBox->pScreens[j].aVbvaCtx, &pVBox->guestCtx, &cmdHdr, + sizeof(cmdHdr)); + VBoxVBVABufferEndUpdate(&pVBox->pScreens[j].aVbvaCtx); + } + } + } +} + +static DECLCALLBACK(void *) hgsmiEnvAlloc(void *pvEnv, HGSMISIZE cb) +{ + RT_NOREF(pvEnv); + return calloc(1, cb); +} + +static DECLCALLBACK(void) hgsmiEnvFree(void *pvEnv, void *pv) +{ + RT_NOREF(pvEnv); + free(pv); +} + +static HGSMIENV g_hgsmiEnv = +{ + NULL, + hgsmiEnvAlloc, + hgsmiEnvFree +}; + +/** + * Calculate the location in video RAM of and initialise the heap for guest to + * host messages. + */ +void vbvxSetUpHGSMIHeapInGuest(VBOXPtr pVBox, uint32_t cbVRAM) +{ + int rc; + uint32_t offVRAMBaseMapping, offGuestHeapMemory, cbGuestHeapMemory; + void *pvGuestHeapMemory; + + VBoxHGSMIGetBaseMappingInfo(cbVRAM, &offVRAMBaseMapping, NULL, &offGuestHeapMemory, &cbGuestHeapMemory, NULL); + pvGuestHeapMemory = ((uint8_t *)pVBox->base) + offVRAMBaseMapping + offGuestHeapMemory; + rc = VBoxHGSMISetupGuestContext(&pVBox->guestCtx, pvGuestHeapMemory, cbGuestHeapMemory, + offVRAMBaseMapping + offGuestHeapMemory, &g_hgsmiEnv); + AssertMsg(RT_SUCCESS(rc), ("Failed to set up the guest-to-host message buffer heap, rc=%d\n", rc)); + pVBox->cbView = offVRAMBaseMapping; +} + +/** Callback to fill in the view structures */ +static DECLCALLBACK(int) vboxFillViewInfo(void *pvVBox, struct VBVAINFOVIEW *pViews, uint32_t cViews) +{ + VBOXPtr pVBox = (VBOXPtr)pvVBox; + unsigned i; + for (i = 0; i < cViews; ++i) + { + pViews[i].u32ViewIndex = i; + pViews[i].u32ViewOffset = 0; + pViews[i].u32ViewSize = pVBox->cbView; + pViews[i].u32MaxScreenSize = pVBox->cbFBMax; + } + return VINF_SUCCESS; +} + +/** + * Initialise VirtualBox's accelerated video extensions. + * + * @returns TRUE on success, FALSE on failure + */ +static Bool vboxSetupVRAMVbva(VBOXPtr pVBox) +{ + int rc = VINF_SUCCESS; + unsigned i; + + pVBox->cbFBMax = pVBox->cbView; + for (i = 0; i < pVBox->cScreens; ++i) + { + pVBox->cbFBMax -= VBVA_MIN_BUFFER_SIZE; + pVBox->pScreens[i].aoffVBVABuffer = pVBox->cbFBMax; + TRACE_LOG("VBVA buffer offset for screen %u: 0x%lx\n", i, + (unsigned long) pVBox->cbFBMax); + VBoxVBVASetupBufferContext(&pVBox->pScreens[i].aVbvaCtx, + pVBox->pScreens[i].aoffVBVABuffer, + VBVA_MIN_BUFFER_SIZE); + } + TRACE_LOG("Maximum framebuffer size: %lu (0x%lx)\n", + (unsigned long) pVBox->cbFBMax, + (unsigned long) pVBox->cbFBMax); + rc = VBoxHGSMISendViewInfo(&pVBox->guestCtx, pVBox->cScreens, + vboxFillViewInfo, (void *)pVBox); + AssertMsg(RT_SUCCESS(rc), ("Failed to send the view information to the host, rc=%d\n", rc)); + return TRUE; +} + +static Bool haveHGSMIModeHintAndCursorReportingInterface(VBOXPtr pVBox) +{ + uint32_t fModeHintReporting, fCursorReporting; + + return RT_SUCCESS(VBoxQueryConfHGSMI(&pVBox->guestCtx, VBOX_VBVA_CONF32_MODE_HINT_REPORTING, &fModeHintReporting)) + && RT_SUCCESS(VBoxQueryConfHGSMI(&pVBox->guestCtx, VBOX_VBVA_CONF32_GUEST_CURSOR_REPORTING, &fCursorReporting)) + && fModeHintReporting == VINF_SUCCESS + && fCursorReporting == VINF_SUCCESS; +} + +static Bool hostHasScreenBlankingFlag(VBOXPtr pVBox) +{ + uint32_t fScreenFlags; + + return RT_SUCCESS(VBoxQueryConfHGSMI(&pVBox->guestCtx, VBOX_VBVA_CONF32_SCREEN_FLAGS, &fScreenFlags)) + && fScreenFlags & VBVA_SCREEN_F_BLANK; +} + +/** + * Inform VBox that we will supply it with dirty rectangle information + * and install the dirty rectangle handler. + * + * @returns TRUE for success, FALSE for failure + * @param pScrn Pointer to a structure describing the X screen in use + */ +Bool +vboxEnableVbva(ScrnInfoPtr pScrn) +{ + Bool rc = TRUE; + unsigned i; + VBOXPtr pVBox = pScrn->driverPrivate; + + TRACE_ENTRY(); + if (!vboxSetupVRAMVbva(pVBox)) + return FALSE; + for (i = 0; i < pVBox->cScreens; ++i) + { + struct VBVABUFFER *pVBVA; + + pVBVA = (struct VBVABUFFER *) ( ((uint8_t *)pVBox->base) + + pVBox->pScreens[i].aoffVBVABuffer); + if (!VBoxVBVAEnable(&pVBox->pScreens[i].aVbvaCtx, &pVBox->guestCtx, + pVBVA, i)) + rc = FALSE; + } + AssertMsg(rc, ("Failed to enable screen update reporting for at least one virtual monitor.\n")); + pVBox->fHaveHGSMIModeHints = haveHGSMIModeHintAndCursorReportingInterface(pVBox); + pVBox->fHostHasScreenBlankingFlag = hostHasScreenBlankingFlag(pVBox); + return rc; +} + +/** + * Inform VBox that we will stop supplying it with dirty rectangle + * information. This function is intended to be called when an X + * virtual terminal is disabled, or the X server is terminated. + * + * @param pScrn Pointer to a structure describing the X screen in use + */ +void +vboxDisableVbva(ScrnInfoPtr pScrn) +{ + unsigned i; + VBOXPtr pVBox = pScrn->driverPrivate; + + TRACE_ENTRY(); + for (i = 0; i < pVBox->cScreens; ++i) + VBoxVBVADisable(&pVBox->pScreens[i].aVbvaCtx, &pVBox->guestCtx, i); +} |