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/Frontends/VirtualBox/src/platform | |
parent | Initial commit. (diff) | |
download | virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.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 'src/VBox/Frontends/VirtualBox/src/platform')
49 files changed, 9529 insertions, 0 deletions
diff --git a/src/VBox/Frontends/VirtualBox/src/platform/Makefile.kup b/src/VBox/Frontends/VirtualBox/src/platform/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/Makefile.kup diff --git a/src/VBox/Frontends/VirtualBox/src/platform/UIDesktopServices.h b/src/VBox/Frontends/VirtualBox/src/platform/UIDesktopServices.h new file mode 100644 index 00000000..95bfa6a0 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/UIDesktopServices.h @@ -0,0 +1,51 @@ +/* $Id: UIDesktopServices.h $ */ +/** @file + * VBox Qt GUI - Desktop Services.. + */ + +/* + * 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 + */ + +#ifndef FEQT_INCLUDED_SRC_platform_UIDesktopServices_h +#define FEQT_INCLUDED_SRC_platform_UIDesktopServices_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* Qt includes */ +#include <QUuid> + +/** Name of the executable (image) used to start VMs. */ +#define VBOX_GUI_VMRUNNER_IMAGE "VirtualBoxVM" + +/* Qt forward declarations */ +class QString; + +class UIDesktopServices +{ +public: + static bool createMachineShortcut(const QString &strSrcFile, const QString &strDstPath, const QString &strName, const QUuid &uUuid); + static bool openInFileManager(const QString &strFile); +}; + +#endif /* !FEQT_INCLUDED_SRC_platform_UIDesktopServices_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/CocoaEventHelper.h b/src/VBox/Frontends/VirtualBox/src/platform/darwin/CocoaEventHelper.h new file mode 100644 index 00000000..bbf8abcb --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/CocoaEventHelper.h @@ -0,0 +1,65 @@ +/* $Id: CocoaEventHelper.h $ */ +/** @file + * VBox Qt GUI - Declarations of utility functions for handling Darwin Cocoa specific event-handling tasks. + */ + +/* + * 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 + */ + +#ifndef FEQT_INCLUDED_SRC_platform_darwin_CocoaEventHelper_h +#define FEQT_INCLUDED_SRC_platform_darwin_CocoaEventHelper_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* GUI includes: */ +#include "UILibraryDefs.h" + +/* Other VBox includes: */ +#include <iprt/cdefs.h> +#include <VBox/VBoxCocoa.h> + +/* Cocoa declarations: */ +ADD_COCOA_NATIVE_REF(NSEvent); + + +RT_C_DECLS_BEGIN + +/** Calls the -(NSUInteger)modifierFlags method on @a pEvent object and converts the flags to carbon style. */ +uint32_t darwinEventModifierFlagsXlated(ConstNativeNSEventRef pEvent); + +/** Get the name for a Cocoa @a enmEventType. */ +const char *darwinEventTypeName(unsigned long enmEventType); + +/** Debug helper function for dumping a Cocoa event to stdout. + * @param pszPrefix Brings the message prefix. + * @param pEvent Brings the Cocoa event. */ +void darwinPrintEvent(const char *pszPrefix, ConstNativeNSEventRef pEvent); + +/** Posts stripped mouse event based on passed @a pEvent. */ +SHARED_LIBRARY_STUFF void darwinPostStrippedMouseEvent(ConstNativeNSEventRef pEvent); + +RT_C_DECLS_END + + +#endif /* !FEQT_INCLUDED_SRC_platform_darwin_CocoaEventHelper_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/CocoaEventHelper.mm b/src/VBox/Frontends/VirtualBox/src/platform/darwin/CocoaEventHelper.mm new file mode 100644 index 00000000..09152b37 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/CocoaEventHelper.mm @@ -0,0 +1,366 @@ +/* $Id: CocoaEventHelper.mm $ */ +/** @file + * VBox Qt GUI - Declarations of utility functions for handling Darwin Cocoa specific event handling tasks. + */ + +/* + * Copyright (C) 2009-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 + */ + +/* GUI includes: */ +#include "CocoaEventHelper.h" +#include "DarwinKeyboard.h" + +/* External includes: */ +#import <Cocoa/Cocoa.h> +#import <AppKit/NSEvent.h> +#include <Carbon/Carbon.h> + +/* They just had to rename a whole load of constants in 10.12. Just wrap the carp up + in some defines for now as we need to keep building with both 10.9 and 10.13+: */ +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 +# define VBOX_NSAlphaShiftKeyMask NSEventModifierFlagCapsLock +# define VBOX_NSAlternateKeyMask NSEventModifierFlagOption +# define VBOX_NSAppKitDefined NSEventTypeAppKitDefined +# define VBOX_NSApplicationDefined NSEventTypeApplicationDefined +# define VBOX_NSCommandKeyMask NSEventModifierFlagCommand +# define VBOX_NSControlKeyMask NSEventModifierFlagControl +# define VBOX_NSCursorUpdate NSEventTypeCursorUpdate +# define VBOX_NSFlagsChanged NSEventTypeFlagsChanged +# define VBOX_NSFunctionKeyMask NSEventModifierFlagFunction +# define VBOX_NSHelpKeyMask NSEventModifierFlagHelp +# define VBOX_NSKeyDown NSEventTypeKeyDown +# define VBOX_NSKeyUp NSEventTypeKeyUp +# define VBOX_NSLeftMouseDown NSEventTypeLeftMouseDown +# define VBOX_NSLeftMouseDragged NSEventTypeLeftMouseDragged +# define VBOX_NSLeftMouseUp NSEventTypeLeftMouseUp +# define VBOX_NSMouseEntered NSEventTypeMouseEntered +# define VBOX_NSMouseExited NSEventTypeMouseExited +# define VBOX_NSMouseMoved NSEventTypeMouseMoved +# define VBOX_NSNumericPadKeyMask NSEventModifierFlagNumericPad +# define VBOX_NSOtherMouseDown NSEventTypeOtherMouseDown +# define VBOX_NSOtherMouseDragged NSEventTypeOtherMouseDragged +# define VBOX_NSOtherMouseUp NSEventTypeOtherMouseUp +# define VBOX_NSPeriodic NSEventTypePeriodic +# define VBOX_NSRightMouseDown NSEventTypeRightMouseDown +# define VBOX_NSRightMouseDragged NSEventTypeRightMouseDragged +# define VBOX_NSRightMouseUp NSEventTypeRightMouseUp +# define VBOX_NSScrollWheel NSEventTypeScrollWheel +# define VBOX_NSShiftKeyMask NSEventModifierFlagShift +# define VBOX_NSSystemDefined NSEventTypeSystemDefined +# define VBOX_NSTabletPoint NSEventTypeTabletPoint +# define VBOX_NSTabletProximity NSEventTypeTabletProximity +#else +# define VBOX_NSAlphaShiftKeyMask NSAlphaShiftKeyMask +# define VBOX_NSAlternateKeyMask NSAlternateKeyMask +# define VBOX_NSAppKitDefined NSAppKitDefined +# define VBOX_NSApplicationDefined NSApplicationDefined +# define VBOX_NSCommandKeyMask NSCommandKeyMask +# define VBOX_NSControlKeyMask NSControlKeyMask +# define VBOX_NSCursorUpdate NSCursorUpdate +# define VBOX_NSFlagsChanged NSFlagsChanged +# define VBOX_NSFunctionKeyMask NSFunctionKeyMask +# define VBOX_NSHelpKeyMask NSHelpKeyMask +# define VBOX_NSKeyDown NSKeyDown +# define VBOX_NSKeyUp NSKeyUp +# define VBOX_NSLeftMouseDown NSLeftMouseDown +# define VBOX_NSLeftMouseDragged NSLeftMouseDragged +# define VBOX_NSLeftMouseUp NSLeftMouseUp +# define VBOX_NSMouseEntered NSMouseEntered +# define VBOX_NSMouseExited NSMouseExited +# define VBOX_NSMouseMoved NSMouseMoved +# define VBOX_NSNumericPadKeyMask NSNumericPadKeyMask +# define VBOX_NSOtherMouseDown NSOtherMouseDown +# define VBOX_NSOtherMouseDragged NSOtherMouseDragged +# define VBOX_NSOtherMouseUp NSOtherMouseUp +# define VBOX_NSPeriodic NSPeriodic +# define VBOX_NSRightMouseDown NSRightMouseDown +# define VBOX_NSRightMouseDragged NSRightMouseDragged +# define VBOX_NSRightMouseUp NSRightMouseUp +# define VBOX_NSScrollWheel NSScrollWheel +# define VBOX_NSShiftKeyMask NSShiftKeyMask +# define VBOX_NSSystemDefined NSSystemDefined +# define VBOX_NSTabletPoint NSTabletPoint +# define VBOX_NSTabletProximity NSTabletProximity +#endif + +uint32_t darwinEventModifierFlagsXlated(ConstNativeNSEventRef pEvent) +{ + NSUInteger fCocoa = [pEvent modifierFlags]; + uint32_t fCarbon = 0; + if (fCocoa) + { + if (fCocoa & VBOX_NSAlphaShiftKeyMask) + fCarbon |= alphaLock; + if (fCocoa & (VBOX_NSShiftKeyMask | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) + { + if (fCocoa & (NX_DEVICERSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK)) + { + if (fCocoa & NX_DEVICERSHIFTKEYMASK) + fCarbon |= rightShiftKey; + if (fCocoa & NX_DEVICELSHIFTKEYMASK) + fCarbon |= shiftKey; + } + else + fCarbon |= shiftKey; + } + + if (fCocoa & (VBOX_NSControlKeyMask | NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) + { + if (fCocoa & (NX_DEVICELCTLKEYMASK | NX_DEVICERCTLKEYMASK)) + { + if (fCocoa & NX_DEVICERCTLKEYMASK) + fCarbon |= rightControlKey; + if (fCocoa & NX_DEVICELCTLKEYMASK) + fCarbon |= controlKey; + } + else + fCarbon |= controlKey; + } + + if (fCocoa & (VBOX_NSAlternateKeyMask | NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) + { + if (fCocoa & (NX_DEVICELALTKEYMASK | NX_DEVICERALTKEYMASK)) + { + if (fCocoa & NX_DEVICERALTKEYMASK) + fCarbon |= rightOptionKey; + if (fCocoa & NX_DEVICELALTKEYMASK) + fCarbon |= optionKey; + } + else + fCarbon |= optionKey; + } + + if (fCocoa & (VBOX_NSCommandKeyMask | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) + { + if (fCocoa & (NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK)) + { + if (fCocoa & NX_DEVICERCMDKEYMASK) + fCarbon |= kEventKeyModifierRightCmdKeyMask; + if (fCocoa & NX_DEVICELCMDKEYMASK) + fCarbon |= cmdKey; + } + else + fCarbon |= cmdKey; + } + + /* + if (fCocoa & VBOX_NSNumericPadKeyMask) + fCarbon |= ???; + + if (fCocoa & VBOX_NSHelpKeyMask) + fCarbon |= ???; + + if (fCocoa & VBOX_NSFunctionKeyMask) + fCarbon |= ???; + */ + } + + return fCarbon; +} + +const char *darwinEventTypeName(unsigned long enmEventType) +{ + switch (enmEventType) + { +#define EVT_CASE(nm) case nm: return #nm + EVT_CASE(VBOX_NSLeftMouseDown); + EVT_CASE(VBOX_NSLeftMouseUp); + EVT_CASE(VBOX_NSRightMouseDown); + EVT_CASE(VBOX_NSRightMouseUp); + EVT_CASE(VBOX_NSMouseMoved); + EVT_CASE(VBOX_NSLeftMouseDragged); + EVT_CASE(VBOX_NSRightMouseDragged); + EVT_CASE(VBOX_NSMouseEntered); + EVT_CASE(VBOX_NSMouseExited); + EVT_CASE(VBOX_NSKeyDown); + EVT_CASE(VBOX_NSKeyUp); + EVT_CASE(VBOX_NSFlagsChanged); + EVT_CASE(VBOX_NSAppKitDefined); + EVT_CASE(VBOX_NSSystemDefined); + EVT_CASE(VBOX_NSApplicationDefined); + EVT_CASE(VBOX_NSPeriodic); + EVT_CASE(VBOX_NSCursorUpdate); + EVT_CASE(VBOX_NSScrollWheel); + EVT_CASE(VBOX_NSTabletPoint); + EVT_CASE(VBOX_NSTabletProximity); + EVT_CASE(VBOX_NSOtherMouseDown); + EVT_CASE(VBOX_NSOtherMouseUp); + EVT_CASE(VBOX_NSOtherMouseDragged); +#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 + EVT_CASE(NSEventTypeGesture); + EVT_CASE(NSEventTypeMagnify); + EVT_CASE(NSEventTypeSwipe); + EVT_CASE(NSEventTypeRotate); + EVT_CASE(NSEventTypeBeginGesture); + EVT_CASE(NSEventTypeEndGesture); +#endif +#undef EVT_CASE + default: + return "Unknown!"; + } +} + +void darwinPrintEvent(const char *pszPrefix, ConstNativeNSEventRef pEvent) +{ + NSEventType enmEventType = [pEvent type]; + NSUInteger fEventMask = [pEvent modifierFlags]; + NSWindow *pEventWindow = [pEvent window]; + NSInteger iEventWindow = [pEvent windowNumber]; +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 + NSGraphicsContext *pEventGraphicsContext = nil; /* NSEvent::context is deprecated and said to always return nil. */ +#else + NSGraphicsContext *pEventGraphicsContext = [pEvent context]; +#endif + + printf("%s%p: Type=%lu Modifiers=%08lx pWindow=%p #Wnd=%ld pGraphCtx=%p %s\n", + pszPrefix, (void*)pEvent, (unsigned long)enmEventType, (unsigned long)fEventMask, (void*)pEventWindow, + (long)iEventWindow, (void*)pEventGraphicsContext, darwinEventTypeName(enmEventType)); + + /* Dump type specific info: */ + switch (enmEventType) + { + case VBOX_NSLeftMouseDown: + case VBOX_NSLeftMouseUp: + case VBOX_NSRightMouseDown: + case VBOX_NSRightMouseUp: + case VBOX_NSMouseMoved: + + case VBOX_NSLeftMouseDragged: + case VBOX_NSRightMouseDragged: + case VBOX_NSMouseEntered: + case VBOX_NSMouseExited: + break; + + case VBOX_NSKeyDown: + case VBOX_NSKeyUp: + { + NSUInteger i; + NSUInteger cch; + NSString *pChars = [pEvent characters]; + NSString *pCharsIgnMod = [pEvent charactersIgnoringModifiers]; + BOOL fIsARepeat = [pEvent isARepeat]; + unsigned short KeyCode = [pEvent keyCode]; + + printf(" KeyCode=%04x isARepeat=%d", KeyCode, fIsARepeat); + if (pChars) + { + cch = [pChars length]; + printf(" characters={"); + for (i = 0; i < cch; i++) + printf(i == 0 ? "%02x" : ",%02x", [pChars characterAtIndex: i]); + printf("}"); + } + + if (pCharsIgnMod) + { + cch = [pCharsIgnMod length]; + printf(" charactersIgnoringModifiers={"); + for (i = 0; i < cch; i++) + printf(i == 0 ? "%02x" : ",%02x", [pCharsIgnMod characterAtIndex: i]); + printf("}"); + } + printf("\n"); + break; + } + + case VBOX_NSFlagsChanged: + { + NSUInteger fOddBits = VBOX_NSAlphaShiftKeyMask | VBOX_NSShiftKeyMask | VBOX_NSControlKeyMask | VBOX_NSAlternateKeyMask + | VBOX_NSCommandKeyMask | VBOX_NSNumericPadKeyMask | VBOX_NSHelpKeyMask | VBOX_NSFunctionKeyMask + | NX_DEVICELCTLKEYMASK | NX_DEVICELSHIFTKEYMASK | NX_DEVICERSHIFTKEYMASK + | NX_DEVICELCMDKEYMASK | NX_DEVICERCMDKEYMASK | NX_DEVICELALTKEYMASK + | NX_DEVICERALTKEYMASK | NX_DEVICERCTLKEYMASK; + + printf(" KeyCode=%04x", (int)[pEvent keyCode]); +#define PRINT_MOD(cnst, nm) do { if (fEventMask & (cnst)) printf(" %s", #nm); } while (0) + /* device-independent: */ + PRINT_MOD(VBOX_NSAlphaShiftKeyMask, "AlphaShift"); + PRINT_MOD(VBOX_NSShiftKeyMask, "Shift"); + PRINT_MOD(VBOX_NSControlKeyMask, "Ctrl"); + PRINT_MOD(VBOX_NSAlternateKeyMask, "Alt"); + PRINT_MOD(VBOX_NSCommandKeyMask, "Cmd"); + PRINT_MOD(VBOX_NSNumericPadKeyMask, "NumLock"); + PRINT_MOD(VBOX_NSHelpKeyMask, "Help"); + PRINT_MOD(VBOX_NSFunctionKeyMask, "Fn"); + /* device-dependent (sort of): */ + PRINT_MOD(NX_DEVICELCTLKEYMASK, "$L-Ctrl"); + PRINT_MOD(NX_DEVICELSHIFTKEYMASK, "$L-Shift"); + PRINT_MOD(NX_DEVICERSHIFTKEYMASK, "$R-Shift"); + PRINT_MOD(NX_DEVICELCMDKEYMASK, "$L-Cmd"); + PRINT_MOD(NX_DEVICERCMDKEYMASK, "$R-Cmd"); + PRINT_MOD(NX_DEVICELALTKEYMASK, "$L-Alt"); + PRINT_MOD(NX_DEVICERALTKEYMASK, "$R-Alt"); + PRINT_MOD(NX_DEVICERCTLKEYMASK, "$R-Ctrl"); +#undef PRINT_MOD + + fOddBits = fEventMask & ~fOddBits; + if (fOddBits) + printf(" fOddBits=%#08lx", (unsigned long)fOddBits); +#undef KNOWN_BITS + printf("\n"); + break; + } + + case VBOX_NSAppKitDefined: + case VBOX_NSSystemDefined: + case VBOX_NSApplicationDefined: + case VBOX_NSPeriodic: + case VBOX_NSCursorUpdate: + case VBOX_NSScrollWheel: + case VBOX_NSTabletPoint: + case VBOX_NSTabletProximity: + case VBOX_NSOtherMouseDown: + case VBOX_NSOtherMouseUp: + case VBOX_NSOtherMouseDragged: +#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 + case NSEventTypeGesture: + case NSEventTypeMagnify: + case NSEventTypeSwipe: + case NSEventTypeRotate: + case NSEventTypeBeginGesture: + case NSEventTypeEndGesture: +#endif + default: + printf(" Unknown!\n"); + break; + } +} + +void darwinPostStrippedMouseEvent(ConstNativeNSEventRef pEvent) +{ + /* Create and post new stripped event: */ + NSEvent *pNewEvent = [NSEvent mouseEventWithType:[pEvent type] + location:[pEvent locationInWindow] + modifierFlags:0 + timestamp:[pEvent timestamp] // [NSDate timeIntervalSinceReferenceDate] ? + windowNumber:[pEvent windowNumber] +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 + context:nil /* NSEvent::context is deprecated and said to always return nil. */ +#else + context:[pEvent context] +#endif + eventNumber:[pEvent eventNumber] + clickCount:[pEvent clickCount] + pressure:[pEvent pressure]]; + [NSApp postEvent:pNewEvent atStart:YES]; +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/DarwinKeyboard.cpp b/src/VBox/Frontends/VirtualBox/src/platform/darwin/DarwinKeyboard.cpp new file mode 100644 index 00000000..4f2e2971 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/DarwinKeyboard.cpp @@ -0,0 +1,2197 @@ +/* $Id: DarwinKeyboard.cpp $ */ +/** @file + * VBox Qt GUI - Declarations of utility functions for handling Darwin Keyboard specific tasks. + */ + +/* + * 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 + */ + +/* Defines: */ +#define LOG_GROUP LOG_GROUP_GUI +#define VBOX_WITH_KBD_LEDS_SYNC +//#define VBOX_WITHOUT_KBD_LEDS_SYNC_FILTERING + +/* GUI includes: */ +#include "DarwinKeyboard.h" +#ifndef USE_HID_FOR_MODIFIERS +# include "CocoaEventHelper.h" +#endif + +/* Other VBox includes: */ +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/mem.h> +#include <iprt/time.h> +#include <VBox/log.h> +#ifdef DEBUG_PRINTF +# include <iprt/stream.h> +#endif +#ifdef VBOX_WITH_KBD_LEDS_SYNC +# include <iprt/errcore.h> +# include <iprt/semaphore.h> +# include <VBox/sup.h> +#endif + +/* External includes: */ +#include <ApplicationServices/ApplicationServices.h> +#include <Carbon/Carbon.h> +#include <IOKit/IOCFPlugIn.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/hid/IOHIDLib.h> +#include <IOKit/usb/USB.h> +#ifdef USE_HID_FOR_MODIFIERS +# include <CoreFoundation/CoreFoundation.h> +# include <IOKit/hid/IOHIDUsageTables.h> +# include <mach/mach.h> +# include <mach/mach_error.h> +#endif +#ifdef VBOX_WITH_KBD_LEDS_SYNC +# include <IOKit/IOMessage.h> +# include <IOKit/usb/IOUSBLib.h> +#endif + + +RT_C_DECLS_BEGIN +/* Private interface in 10.3 and later. */ +typedef int CGSConnection; +typedef enum +{ + kCGSGlobalHotKeyEnable = 0, + kCGSGlobalHotKeyDisable, + kCGSGlobalHotKeyDisableExceptUniversalAccess, + kCGSGlobalHotKeyInvalid = -1 /* bird */ +} CGSGlobalHotKeyOperatingMode; +extern CGSConnection _CGSDefaultConnection(void); +extern CGError CGSGetGlobalHotKeyOperatingMode(CGSConnection Connection, CGSGlobalHotKeyOperatingMode *enmMode); +extern CGError CGSSetGlobalHotKeyOperatingMode(CGSConnection Connection, CGSGlobalHotKeyOperatingMode enmMode); +RT_C_DECLS_END + + +/* Defined Constants And Macros: */ +#define QZ_RMETA 0x36 +#define QZ_LMETA 0x37 +#define QZ_LSHIFT 0x38 +#define QZ_CAPSLOCK 0x39 +#define QZ_LALT 0x3A +#define QZ_LCTRL 0x3B +#define QZ_RSHIFT 0x3C +#define QZ_RALT 0x3D +#define QZ_RCTRL 0x3E +// Found the definition of the fn-key in: +// http://stuff.mit.edu/afs/sipb/project/darwin/src/modules/IOHIDFamily/IOHIDSystem/IOHIKeyboardMapper.cpp & +// http://stuff.mit.edu/afs/sipb/project/darwin/src/modules/AppleADBKeyboard/AppleADBKeyboard.cpp +// Maybe we need this in the future. +#define QZ_FN 0x3F +#define QZ_NUMLOCK 0x47 +/** Short hand for an extended key. */ +#define K_EX VBOXKEY_EXTENDED +/** Short hand for a modifier key. */ +#define K_MOD VBOXKEY_MODIFIER +/** Short hand for a lock key. */ +#define K_LOCK VBOXKEY_LOCK +#ifdef USE_HID_FOR_MODIFIERS +/** An attempt at catching reference leaks. */ +# define MY_CHECK_CREFS(cRefs) do { AssertMsg(cRefs < 25, ("%ld\n", cRefs)); NOREF(cRefs); } while (0) +#endif + + +/** This is derived partially from SDL_QuartzKeys.h and partially from testing. + * (The funny thing about the virtual scan codes on the mac is that they aren't + * offically documented, which is rather silly to say the least. Thus, the need + * for looking at SDL and other odd places for docs.) */ +static const uint16_t g_aDarwinToSet1[] = +{ + /* set-1 SDL_QuartzKeys.h */ + 0x1e, /* QZ_a 0x00 */ + 0x1f, /* QZ_s 0x01 */ + 0x20, /* QZ_d 0x02 */ + 0x21, /* QZ_f 0x03 */ + 0x23, /* QZ_h 0x04 */ + 0x22, /* QZ_g 0x05 */ + 0x2c, /* QZ_z 0x06 */ + 0x2d, /* QZ_x 0x07 */ + 0x2e, /* QZ_c 0x08 */ + 0x2f, /* QZ_v 0x09 */ + 0x56, /* between lshift and z. 'INT 1'? */ + 0x30, /* QZ_b 0x0B */ + 0x10, /* QZ_q 0x0C */ + 0x11, /* QZ_w 0x0D */ + 0x12, /* QZ_e 0x0E */ + 0x13, /* QZ_r 0x0F */ + 0x15, /* QZ_y 0x10 */ + 0x14, /* QZ_t 0x11 */ + 0x02, /* QZ_1 0x12 */ + 0x03, /* QZ_2 0x13 */ + 0x04, /* QZ_3 0x14 */ + 0x05, /* QZ_4 0x15 */ + 0x07, /* QZ_6 0x16 */ + 0x06, /* QZ_5 0x17 */ + 0x0d, /* QZ_EQUALS 0x18 */ + 0x0a, /* QZ_9 0x19 */ + 0x08, /* QZ_7 0x1A */ + 0x0c, /* QZ_MINUS 0x1B */ + 0x09, /* QZ_8 0x1C */ + 0x0b, /* QZ_0 0x1D */ + 0x1b, /* QZ_RIGHTBRACKET 0x1E */ + 0x18, /* QZ_o 0x1F */ + 0x16, /* QZ_u 0x20 */ + 0x1a, /* QZ_LEFTBRACKET 0x21 */ + 0x17, /* QZ_i 0x22 */ + 0x19, /* QZ_p 0x23 */ + 0x1c, /* QZ_RETURN 0x24 */ + 0x26, /* QZ_l 0x25 */ + 0x24, /* QZ_j 0x26 */ + 0x28, /* QZ_QUOTE 0x27 */ + 0x25, /* QZ_k 0x28 */ + 0x27, /* QZ_SEMICOLON 0x29 */ + 0x2b, /* QZ_BACKSLASH 0x2A */ + 0x33, /* QZ_COMMA 0x2B */ + 0x35, /* QZ_SLASH 0x2C */ + 0x31, /* QZ_n 0x2D */ + 0x32, /* QZ_m 0x2E */ + 0x34, /* QZ_PERIOD 0x2F */ + 0x0f, /* QZ_TAB 0x30 */ + 0x39, /* QZ_SPACE 0x31 */ + 0x29, /* QZ_BACKQUOTE 0x32 */ + 0x0e, /* QZ_BACKSPACE 0x33 */ + 0x9c, /* QZ_IBOOK_ENTER 0x34 */ + 0x01, /* QZ_ESCAPE 0x35 */ + 0x5c|K_EX|K_MOD, /* QZ_RMETA 0x36 */ + 0x5b|K_EX|K_MOD, /* QZ_LMETA 0x37 */ + 0x2a|K_MOD, /* QZ_LSHIFT 0x38 */ + 0x3a|K_LOCK, /* QZ_CAPSLOCK 0x39 */ + 0x38|K_MOD, /* QZ_LALT 0x3A */ + 0x1d|K_MOD, /* QZ_LCTRL 0x3B */ + 0x36|K_MOD, /* QZ_RSHIFT 0x3C */ + 0x38|K_EX|K_MOD, /* QZ_RALT 0x3D */ + 0x1d|K_EX|K_MOD, /* QZ_RCTRL 0x3E */ + 0, /* */ + 0, /* */ + 0x53, /* QZ_KP_PERIOD 0x41 */ + 0, /* */ + 0x37, /* QZ_KP_MULTIPLY 0x43 */ + 0, /* */ + 0x4e, /* QZ_KP_PLUS 0x45 */ + 0, /* */ + 0x45|K_LOCK, /* QZ_NUMLOCK 0x47 */ + 0, /* */ + 0, /* */ + 0, /* */ + 0x35|K_EX, /* QZ_KP_DIVIDE 0x4B */ + 0x1c|K_EX, /* QZ_KP_ENTER 0x4C */ + 0, /* */ + 0x4a, /* QZ_KP_MINUS 0x4E */ + 0, /* */ + 0, /* */ + 0x0d/*?*/, /* QZ_KP_EQUALS 0x51 */ + 0x52, /* QZ_KP0 0x52 */ + 0x4f, /* QZ_KP1 0x53 */ + 0x50, /* QZ_KP2 0x54 */ + 0x51, /* QZ_KP3 0x55 */ + 0x4b, /* QZ_KP4 0x56 */ + 0x4c, /* QZ_KP5 0x57 */ + 0x4d, /* QZ_KP6 0x58 */ + 0x47, /* QZ_KP7 0x59 */ + 0, /* */ + 0x48, /* QZ_KP8 0x5B */ + 0x49, /* QZ_KP9 0x5C */ + 0x7d, /* yen, | (JIS) 0x5D */ + 0x73, /* _, ro (JIS) 0x5E */ + 0, /* */ + 0x3f, /* QZ_F5 0x60 */ + 0x40, /* QZ_F6 0x61 */ + 0x41, /* QZ_F7 0x62 */ + 0x3d, /* QZ_F3 0x63 */ + 0x42, /* QZ_F8 0x64 */ + 0x43, /* QZ_F9 0x65 */ + 0x29, /* Zen/Han (JIS) 0x66 */ + 0x57, /* QZ_F11 0x67 */ + 0x29, /* Zen/Han (JIS) 0x68 */ + 0x37|K_EX, /* QZ_PRINT / F13 0x69 */ + 0x63, /* QZ_F16 0x6A */ + 0x46|K_LOCK, /* QZ_SCROLLOCK 0x6B */ + 0, /* */ + 0x44, /* QZ_F10 0x6D */ + 0x5d|K_EX, /* */ + 0x58, /* QZ_F12 0x6F */ + 0, /* */ + 0/* 0xe1,0x1d,0x45*/, /* QZ_PAUSE 0x71 */ + 0x52|K_EX, /* QZ_INSERT / HELP 0x72 */ + 0x47|K_EX, /* QZ_HOME 0x73 */ + 0x49|K_EX, /* QZ_PAGEUP 0x74 */ + 0x53|K_EX, /* QZ_DELETE 0x75 */ + 0x3e, /* QZ_F4 0x76 */ + 0x4f|K_EX, /* QZ_END 0x77 */ + 0x3c, /* QZ_F2 0x78 */ + 0x51|K_EX, /* QZ_PAGEDOWN 0x79 */ + 0x3b, /* QZ_F1 0x7A */ + 0x4b|K_EX, /* QZ_LEFT 0x7B */ + 0x4d|K_EX, /* QZ_RIGHT 0x7C */ + 0x50|K_EX, /* QZ_DOWN 0x7D */ + 0x48|K_EX, /* QZ_UP 0x7E */ + 0,/*0x5e|K_EX*/ /* QZ_POWER 0x7F */ /* have different break key! */ + /* do NEVER deliver the Power + * scancode as e.g. Windows will + * handle it, @bugref{7692}. */ +}; + + +/** Holds whether we've connected or not. */ +static bool g_fConnectedToCGS = false; +/** Holds the cached connection. */ +static CGSConnection g_CGSConnection; + + +#ifdef USE_HID_FOR_MODIFIERS + +/** Holds the IO Master Port. */ +static mach_port_t g_MasterPort = NULL; + +/** Holds the amount of keyboards in the cache. */ +static unsigned g_cKeyboards = 0; +/** Array of cached keyboard data. */ +static struct KeyboardCacheData +{ + /** The device interface. */ + IOHIDDeviceInterface **ppHidDeviceInterface; + /** The queue interface. */ + IOHIDQueueInterface **ppHidQueueInterface; + + /** Cookie translation array. */ + struct KeyboardCacheCookie + { + /** The cookie. */ + IOHIDElementCookie Cookie; + /** The corresponding modifier mask. */ + uint32_t fMask; + } aCookies[64]; + /** Number of cookies in the array. */ + unsigned cCookies; +} g_aKeyboards[128]; +/** Holds the keyboard cache creation timestamp. */ +static uint64_t g_u64KeyboardTS = 0; + +/** Holds the HID queue status. */ +static bool g_fHIDQueueEnabled; +/** Holds the current modifier mask. */ +static uint32_t g_fHIDModifierMask; +/** Holds the old modifier mask. */ +static uint32_t g_fOldHIDModifierMask; + +#endif /* USE_HID_FOR_MODIFIERS */ + + +#ifdef VBOX_WITH_KBD_LEDS_SYNC + +#define VBOX_BOOL_TO_STR_STATE(x) (x) ? "ON" : "OFF" +/** HID LEDs synchronization data: LED states. */ +typedef struct VBoxLedState_t +{ + /** Holds the state of NUM LOCK. */ + bool fNumLockOn; + /** Holds the state of CAPS LOCK. */ + bool fCapsLockOn; + /** Holds the state of SCROLL LOCK. */ + bool fScrollLockOn; +} VBoxLedState_t; + +/** HID LEDs synchronization data: keyboard states. */ +typedef struct VBoxKbdState_t +{ + /** Holds the reference to IOKit HID device. */ + IOHIDDeviceRef pDevice; + /** Holds the LED states. */ + VBoxLedState_t LED; + /** Holds the pointer to a VBoxHidsState_t instance where VBoxKbdState_t instance is stored. */ + void *pParentContainer; + /** Holds the position in global storage (used to simplify CFArray navigation when removing detached device). */ + CFIndex idxPosition; + /** Holds the KBD CAPS LOCK key hold timeout (some Apple keyboards only). */ + uint64_t cCapsLockTimeout; + /** Holds the HID Location ID: unique for an USB device registered in the system. */ + uint32_t idLocation; +} VBoxKbdState_t; + +/** A struct that used to pass input event info from IOKit callback to a Carbon one */ +typedef struct VBoxKbdEvent_t +{ + VBoxKbdState_t *pKbd; + uint32_t iKeyCode; + uint64_t tsKeyDown; +} VBoxKbdEvent_t; + +/** HID LEDs synchronization data: IOKit specific data. */ +typedef struct VBoxHidsState_t +{ + /** Holds the IOKit HID manager reference. */ + IOHIDManagerRef hidManagerRef; + /** Holds the array which consists of VBoxKbdState_t elements. */ + CFMutableArrayRef pDeviceCollection; + /** Holds the LED states that were stored during last broadcast and reflect a guest LED states. */ + VBoxLedState_t guestState; + + /** Holds the queue which will be appended in IOKit input callback. Carbon input callback will extract data from it. */ + CFMutableArrayRef pFifoEventQueue; + /** Holds the lock for pFifoEventQueue. */ + RTSEMMUTEX fifoEventQueueLock; + + /** Holds the IOService notification reference: USB HID device matching. */ + io_iterator_t pUsbHidDeviceMatchNotify; + /** Holds the IOService notification reference: USB HID general interest notifications (IOService messages). */ + io_iterator_t pUsbHidGeneralInterestNotify; + /** Holds the IOService notification port reference: device match and device general interest message. */ + IONotificationPortRef pNotificationPrortRef; + + CFMachPortRef pTapRef; + CFRunLoopSourceRef pLoopSourceRef; +} VBoxHidsState_t; + +#endif /* VBOX_WITH_KBD_LEDS_SYNC */ + + +unsigned DarwinKeycodeToSet1Scancode(unsigned uKeyCode) +{ + if (uKeyCode >= RT_ELEMENTS(g_aDarwinToSet1)) + return 0; + return g_aDarwinToSet1[uKeyCode]; +} + +UInt32 DarwinAdjustModifierMask(UInt32 fModifiers, const void *pvCocoaEvent) +{ + /* Check if there is anything to adjust and perform the adjustment. */ + if (fModifiers & (shiftKey | rightShiftKey | controlKey | rightControlKey | optionKey | rightOptionKey | cmdKey | kEventKeyModifierRightCmdKeyMask)) + { +#ifndef USE_HID_FOR_MODIFIERS + // WORKAROUND: + // Convert the Cocoa modifiers to Carbon ones (the Cocoa modifier + // definitions are tucked away in Objective-C headers, unfortunately). + // + // Update: CGEventTypes.h includes what looks like the Cocoa modifiers + // and the NX_* defines should be available as well. We should look + // into ways to intercept the CG (core graphics) events in the Carbon + // based setup and get rid of all this HID mess. */ + AssertPtr(pvCocoaEvent); + //::darwinPrintEvent("dbg-adjMods: ", pvCocoaEvent); + uint32_t fAltModifiers = ::darwinEventModifierFlagsXlated(pvCocoaEvent); +#else /* USE_HID_FOR_MODIFIERS */ + /* Update the keyboard cache. */ + darwinHIDKeyboardCacheUpdate(); + const UInt32 fAltModifiers = g_fHIDModifierMask; +#endif /* USE_HID_FOR_MODIFIERS */ + +#ifdef DEBUG_PRINTF + RTPrintf("dbg-fAltModifiers=%#x fModifiers=%#x", fAltModifiers, fModifiers); +#endif + if ( (fModifiers & (rightShiftKey | shiftKey)) + && (fAltModifiers & (rightShiftKey | shiftKey))) + { + fModifiers &= ~(rightShiftKey | shiftKey); + fModifiers |= fAltModifiers & (rightShiftKey | shiftKey); + } + + if ( (fModifiers & (rightControlKey | controlKey)) + && (fAltModifiers & (rightControlKey | controlKey))) + { + fModifiers &= ~(rightControlKey | controlKey); + fModifiers |= fAltModifiers & (rightControlKey | controlKey); + } + + if ( (fModifiers & (optionKey | rightOptionKey)) + && (fAltModifiers & (optionKey | rightOptionKey))) + { + fModifiers &= ~(optionKey | rightOptionKey); + fModifiers |= fAltModifiers & (optionKey | rightOptionKey); + } + + if ( (fModifiers & (cmdKey | kEventKeyModifierRightCmdKeyMask)) + && (fAltModifiers & (cmdKey | kEventKeyModifierRightCmdKeyMask))) + { + fModifiers &= ~(cmdKey | kEventKeyModifierRightCmdKeyMask); + fModifiers |= fAltModifiers & (cmdKey | kEventKeyModifierRightCmdKeyMask); + } +#ifdef DEBUG_PRINTF + RTPrintf(" -> %#x\n", fModifiers); +#endif + } + return fModifiers; +} + +unsigned DarwinModifierMaskToSet1Scancode(UInt32 fModifiers) +{ + unsigned uScanCode = DarwinModifierMaskToDarwinKeycode(fModifiers); + if (uScanCode < RT_ELEMENTS(g_aDarwinToSet1)) + uScanCode = g_aDarwinToSet1[uScanCode]; + else + Assert(uScanCode == ~0U); + return uScanCode; +} + +unsigned DarwinModifierMaskToDarwinKeycode(UInt32 fModifiers) +{ + unsigned uKeyCode; + + /** @todo find symbols for these keycodes... */ + fModifiers &= shiftKey | rightShiftKey | controlKey | rightControlKey | optionKey | rightOptionKey | cmdKey + | kEventKeyModifierRightCmdKeyMask | kEventKeyModifierNumLockMask | alphaLock | kEventKeyModifierFnMask; + if (fModifiers == shiftKey) + uKeyCode = QZ_LSHIFT; + else if (fModifiers == rightShiftKey) + uKeyCode = QZ_RSHIFT; + else if (fModifiers == controlKey) + uKeyCode = QZ_LCTRL; + else if (fModifiers == rightControlKey) + uKeyCode = QZ_RCTRL; + else if (fModifiers == optionKey) + uKeyCode = QZ_LALT; + else if (fModifiers == rightOptionKey) + uKeyCode = QZ_RALT; + else if (fModifiers == cmdKey) + uKeyCode = QZ_LMETA; + else if (fModifiers == kEventKeyModifierRightCmdKeyMask /* hack */) + uKeyCode = QZ_RMETA; + else if (fModifiers == alphaLock) + uKeyCode = QZ_CAPSLOCK; + else if (fModifiers == kEventKeyModifierNumLockMask) + uKeyCode = QZ_NUMLOCK; + else if (fModifiers == kEventKeyModifierFnMask) + uKeyCode = QZ_FN; + else if (fModifiers == 0) + uKeyCode = 0; + else + uKeyCode = ~0U; /* multiple */ + return uKeyCode; +} + +UInt32 DarwinKeyCodeToDarwinModifierMask(unsigned uKeyCode) +{ + UInt32 fModifiers; + + /** @todo find symbols for these keycodes... */ + if (uKeyCode == QZ_LSHIFT) + fModifiers = shiftKey; + else if (uKeyCode == QZ_RSHIFT) + fModifiers = rightShiftKey; + else if (uKeyCode == QZ_LCTRL) + fModifiers = controlKey; + else if (uKeyCode == QZ_RCTRL) + fModifiers = rightControlKey; + else if (uKeyCode == QZ_LALT) + fModifiers = optionKey; + else if (uKeyCode == QZ_RALT) + fModifiers = rightOptionKey; + else if (uKeyCode == QZ_LMETA) + fModifiers = cmdKey; + else if (uKeyCode == QZ_RMETA) + fModifiers = kEventKeyModifierRightCmdKeyMask; /* hack */ + else if (uKeyCode == QZ_CAPSLOCK) + fModifiers = alphaLock; + else if (uKeyCode == QZ_NUMLOCK) + fModifiers = kEventKeyModifierNumLockMask; + else if (uKeyCode == QZ_FN) + fModifiers = kEventKeyModifierFnMask; + else + fModifiers = 0; + return fModifiers; +} + + +void DarwinDisableGlobalHotKeys(bool fDisable) +{ + static unsigned s_cComplaints = 0; + + /* Lazy connect to the core graphics service. */ + if (!g_fConnectedToCGS) + { + g_CGSConnection = _CGSDefaultConnection(); + g_fConnectedToCGS = true; + } + + /* Get the current mode. */ + CGSGlobalHotKeyOperatingMode enmMode = kCGSGlobalHotKeyInvalid; + CGSGetGlobalHotKeyOperatingMode(g_CGSConnection, &enmMode); + if ( enmMode != kCGSGlobalHotKeyEnable + && enmMode != kCGSGlobalHotKeyDisable + && enmMode != kCGSGlobalHotKeyDisableExceptUniversalAccess) + { + AssertMsgFailed(("%d\n", enmMode)); + if (s_cComplaints++ < 32) + LogRel(("DarwinDisableGlobalHotKeys: Unexpected enmMode=%d\n", enmMode)); + return; + } + + /* Calc the new mode. */ + if (fDisable) + { + if (enmMode != kCGSGlobalHotKeyEnable) + return; + enmMode = kCGSGlobalHotKeyDisableExceptUniversalAccess; + } + else + { + if (enmMode != kCGSGlobalHotKeyDisableExceptUniversalAccess) + return; + enmMode = kCGSGlobalHotKeyEnable; + } + + /* Try set it and check the actual result. */ + CGSSetGlobalHotKeyOperatingMode(g_CGSConnection, enmMode); + CGSGlobalHotKeyOperatingMode enmNewMode = kCGSGlobalHotKeyInvalid; + CGSGetGlobalHotKeyOperatingMode(g_CGSConnection, &enmNewMode); + if (enmNewMode != enmMode) + { + /* If the screensaver kicks in we should ignore failure here. */ + AssertMsg(enmMode == kCGSGlobalHotKeyEnable, ("enmNewMode=%d enmMode=%d\n", enmNewMode, enmMode)); + if (s_cComplaints++ < 32) + LogRel(("DarwinDisableGlobalHotKeys: Failed to change mode; enmNewMode=%d enmMode=%d\n", enmNewMode, enmMode)); + } +} + + +#ifdef USE_HID_FOR_MODIFIERS + +/** Callback function for consuming queued events. + * @param pvTarget Brings the queue? + * @param rcIn Brings what? + * @param pvRefcon Brings the pointer to the keyboard cache entry. + * @param pvSender Brings what? */ +static void darwinQueueCallback(void *pvTarget, IOReturn rcIn, void *pvRefcon, void *pvSender) +{ + struct KeyboardCacheData *pKeyboardEntry = (struct KeyboardCacheData *)pvRefcon; + if (!pKeyboardEntry->ppHidQueueInterface) + return; + NOREF(pvTarget); + NOREF(rcIn); + NOREF(pvSender); + + /* Consume the events. */ + g_fOldHIDModifierMask = g_fHIDModifierMask; + for (;;) + { +#ifdef DEBUG_PRINTF + RTPrintf("dbg-ev: "); RTStrmFlush(g_pStdOut); +#endif + IOHIDEventStruct Event; + AbsoluteTime ZeroTime = {0,0}; + IOReturn rc = (*pKeyboardEntry->ppHidQueueInterface)->getNextEvent(pKeyboardEntry->ppHidQueueInterface, + &Event, ZeroTime, 0); + if (rc != kIOReturnSuccess) + break; + + /* Translate the cookie value to a modifier mask. */ + uint32_t fMask = 0; + unsigned i = pKeyboardEntry->cCookies; + while (i-- > 0) + { + if (pKeyboardEntry->aCookies[i].Cookie == Event.elementCookie) + { + fMask = pKeyboardEntry->aCookies[i].fMask; + break; + } + } + + /* Adjust the modifier mask. */ + if (Event.value) + g_fHIDModifierMask |= fMask; + else + g_fHIDModifierMask &= ~fMask; +#ifdef DEBUG_PRINTF + RTPrintf("t=%d c=%#x v=%#x cblv=%d lv=%p m=%#X\n", Event.type, Event.elementCookie, Event.value, Event.longValueSize, Event.value, fMask); RTStrmFlush(g_pStdOut); +#endif + } +#ifdef DEBUG_PRINTF + RTPrintf("dbg-ev: done\n"); RTStrmFlush(g_pStdOut); +#endif +} + +/* Forward declaration for darwinBruteForcePropertySearch. */ +static void darwinBruteForcePropertySearch(CFDictionaryRef DictRef, struct KeyboardCacheData *pKeyboardEntry); + +/** Element enumeration callback. */ +static void darwinBruteForcePropertySearchApplier(const void *pvValue, void *pvCacheEntry) +{ + if (CFGetTypeID(pvValue) == CFDictionaryGetTypeID()) + darwinBruteForcePropertySearch((CFMutableDictionaryRef)pvValue, (struct KeyboardCacheData *)pvCacheEntry); +} + +/** Recurses through the keyboard properties looking for certain keys. */ +static void darwinBruteForcePropertySearch(CFDictionaryRef DictRef, struct KeyboardCacheData *pKeyboardEntry) +{ + CFTypeRef ObjRef; + + /* Check for the usage page and usage key we want. */ + long lUsage; + ObjRef = CFDictionaryGetValue(DictRef, CFSTR(kIOHIDElementUsageKey)); + if ( ObjRef + && CFGetTypeID(ObjRef) == CFNumberGetTypeID() + && CFNumberGetValue((CFNumberRef)ObjRef, kCFNumberLongType, &lUsage)) + { + switch (lUsage) + { + case kHIDUsage_KeyboardLeftControl: + case kHIDUsage_KeyboardLeftShift: + case kHIDUsage_KeyboardLeftAlt: + case kHIDUsage_KeyboardLeftGUI: + case kHIDUsage_KeyboardRightControl: + case kHIDUsage_KeyboardRightShift: + case kHIDUsage_KeyboardRightAlt: + case kHIDUsage_KeyboardRightGUI: + { + long lPage; + ObjRef = CFDictionaryGetValue(DictRef, CFSTR(kIOHIDElementUsagePageKey)); + if ( !ObjRef + || CFGetTypeID(ObjRef) != CFNumberGetTypeID() + || !CFNumberGetValue((CFNumberRef)ObjRef, kCFNumberLongType, &lPage) + || lPage != kHIDPage_KeyboardOrKeypad) + break; + + if (pKeyboardEntry->cCookies >= RT_ELEMENTS(pKeyboardEntry->aCookies)) + { + AssertMsgFailed(("too many cookies!\n")); + break; + } + + /* Get the cookie and modifier mask. */ + long lCookie; + ObjRef = CFDictionaryGetValue(DictRef, CFSTR(kIOHIDElementCookieKey)); + if ( !ObjRef + || CFGetTypeID(ObjRef) != CFNumberGetTypeID() + || !CFNumberGetValue((CFNumberRef)ObjRef, kCFNumberLongType, &lCookie)) + break; + + uint32_t fMask; + switch (lUsage) + { + case kHIDUsage_KeyboardLeftControl : fMask = controlKey; break; + case kHIDUsage_KeyboardLeftShift : fMask = shiftKey; break; + case kHIDUsage_KeyboardLeftAlt : fMask = optionKey; break; + case kHIDUsage_KeyboardLeftGUI : fMask = cmdKey; break; + case kHIDUsage_KeyboardRightControl: fMask = rightControlKey; break; + case kHIDUsage_KeyboardRightShift : fMask = rightShiftKey; break; + case kHIDUsage_KeyboardRightAlt : fMask = rightOptionKey; break; + case kHIDUsage_KeyboardRightGUI : fMask = kEventKeyModifierRightCmdKeyMask; break; + default: AssertMsgFailed(("%ld\n",lUsage)); fMask = 0; break; + } + + /* If we've got a queue, add the cookie to the queue. */ + if (pKeyboardEntry->ppHidQueueInterface) + { + IOReturn rc = (*pKeyboardEntry->ppHidQueueInterface)->addElement(pKeyboardEntry->ppHidQueueInterface, (IOHIDElementCookie)lCookie, 0); + AssertMsg(rc == kIOReturnSuccess, ("rc=%d\n", rc)); +#ifdef DEBUG_PRINTF + RTPrintf("dbg-add: u=%#lx c=%#lx\n", lUsage, lCookie); +#endif + } + + /* Add the cookie to the keyboard entry. */ + pKeyboardEntry->aCookies[pKeyboardEntry->cCookies].Cookie = (IOHIDElementCookie)lCookie; + pKeyboardEntry->aCookies[pKeyboardEntry->cCookies].fMask = fMask; + ++pKeyboardEntry->cCookies; + break; + } + } + } + + + /* Get the elements key and recursively iterate the elements looking for they key cookies. */ + ObjRef = CFDictionaryGetValue(DictRef, CFSTR(kIOHIDElementKey)); + if ( ObjRef + && CFGetTypeID(ObjRef) == CFArrayGetTypeID()) + { + CFArrayRef ArrayObjRef = (CFArrayRef)ObjRef; + CFRange Range = {0, CFArrayGetCount(ArrayObjRef)}; + CFArrayApplyFunction(ArrayObjRef, Range, darwinBruteForcePropertySearchApplier, pKeyboardEntry); + } +} + +/** Creates a keyboard cache entry. + * @param pKeyboardEntry Brings the pointer to the entry. + * @param KeyboardDevice Brings the keyboard device to create the entry for. */ +static bool darwinHIDKeyboardCacheCreateEntry(struct KeyboardCacheData *pKeyboardEntry, io_object_t KeyboardDevice) +{ + unsigned long cRefs = 0; + memset(pKeyboardEntry, 0, sizeof(*pKeyboardEntry)); + + /* Query the HIDDeviceInterface for this HID (keyboard) object. */ + SInt32 Score = 0; + IOCFPlugInInterface **ppPlugInInterface = NULL; + IOReturn rc = IOCreatePlugInInterfaceForService(KeyboardDevice, kIOHIDDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score); + if (rc == kIOReturnSuccess) + { + IOHIDDeviceInterface **ppHidDeviceInterface = NULL; + HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface, + CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), + (LPVOID *)&ppHidDeviceInterface); + cRefs = (*ppPlugInInterface)->Release(ppPlugInInterface); MY_CHECK_CREFS(cRefs); + ppPlugInInterface = NULL; + if (hrc == S_OK) + { + rc = (*ppHidDeviceInterface)->open(ppHidDeviceInterface, 0); + if (rc == kIOReturnSuccess) + { + /* Create a removal callback. */ + /** @todo */ + + /* Create the queue so we can insert elements while searching the properties. */ + IOHIDQueueInterface **ppHidQueueInterface = (*ppHidDeviceInterface)->allocQueue(ppHidDeviceInterface); + if (ppHidQueueInterface) + { + rc = (*ppHidQueueInterface)->create(ppHidQueueInterface, 0, 32); + if (rc != kIOReturnSuccess) + { + AssertMsgFailed(("rc=%d\n", rc)); + cRefs = (*ppHidQueueInterface)->Release(ppHidQueueInterface); MY_CHECK_CREFS(cRefs); + ppHidQueueInterface = NULL; + } + } + else + AssertFailed(); + pKeyboardEntry->ppHidQueueInterface = ppHidQueueInterface; + + /* Brute force getting of attributes. */ + /** @todo read up on how to do this in a less resource intensive way! Suggestions are welcome! */ + CFMutableDictionaryRef PropertiesRef = 0; + kern_return_t krc = IORegistryEntryCreateCFProperties(KeyboardDevice, &PropertiesRef, kCFAllocatorDefault, kNilOptions); + if (krc == KERN_SUCCESS) + { + darwinBruteForcePropertySearch(PropertiesRef, pKeyboardEntry); + CFRelease(PropertiesRef); + } + else + AssertMsgFailed(("krc=%#x\n", krc)); + + if (ppHidQueueInterface) + { + /* Now install our queue callback. */ + CFRunLoopSourceRef RunLoopSrcRef = NULL; + rc = (*ppHidQueueInterface)->createAsyncEventSource(ppHidQueueInterface, &RunLoopSrcRef); + if (rc == kIOReturnSuccess) + { + CFRunLoopRef RunLoopRef = (CFRunLoopRef)GetCFRunLoopFromEventLoop(GetMainEventLoop()); + CFRunLoopAddSource(RunLoopRef, RunLoopSrcRef, kCFRunLoopDefaultMode); + } + + /* Now install our queue callback. */ + rc = (*ppHidQueueInterface)->setEventCallout(ppHidQueueInterface, darwinQueueCallback, ppHidQueueInterface, pKeyboardEntry); + if (rc != kIOReturnSuccess) + AssertMsgFailed(("rc=%d\n", rc)); + } + + /* Complete the new keyboard cache entry. */ + pKeyboardEntry->ppHidDeviceInterface = ppHidDeviceInterface; + pKeyboardEntry->ppHidQueueInterface = ppHidQueueInterface; + return true; + } + + AssertMsgFailed(("rc=%d\n", rc)); + cRefs = (*ppHidDeviceInterface)->Release(ppHidDeviceInterface); MY_CHECK_CREFS(cRefs); + } + else + AssertMsgFailed(("hrc=%#x\n", hrc)); + } + else + AssertMsgFailed(("rc=%d\n", rc)); + + return false; +} + +/** Destroys a keyboard cache entry. */ +static void darwinHIDKeyboardCacheDestroyEntry(struct KeyboardCacheData *pKeyboardEntry) +{ + unsigned long cRefs; + + /* Destroy the queue. */ + if (pKeyboardEntry->ppHidQueueInterface) + { + IOHIDQueueInterface **ppHidQueueInterface = pKeyboardEntry->ppHidQueueInterface; + pKeyboardEntry->ppHidQueueInterface = NULL; + + /* Stop it just in case we haven't done so. doesn't really matter I think. */ + (*ppHidQueueInterface)->stop(ppHidQueueInterface); + + /* Deal with the run loop source. */ + CFRunLoopSourceRef RunLoopSrcRef = (*ppHidQueueInterface)->getAsyncEventSource(ppHidQueueInterface); + if (RunLoopSrcRef) + { + CFRunLoopRef RunLoopRef = (CFRunLoopRef)GetCFRunLoopFromEventLoop(GetMainEventLoop()); + CFRunLoopRemoveSource(RunLoopRef, RunLoopSrcRef, kCFRunLoopDefaultMode); + + CFRelease(RunLoopSrcRef); + } + + /* Dispose of and release the queue. */ + (*ppHidQueueInterface)->dispose(ppHidQueueInterface); + cRefs = (*ppHidQueueInterface)->Release(ppHidQueueInterface); MY_CHECK_CREFS(cRefs); + } + + /* Release the removal hook? */ + /** @todo */ + + /* Close and release the device interface. */ + if (pKeyboardEntry->ppHidDeviceInterface) + { + IOHIDDeviceInterface **ppHidDeviceInterface = pKeyboardEntry->ppHidDeviceInterface; + pKeyboardEntry->ppHidDeviceInterface = NULL; + + (*ppHidDeviceInterface)->close(ppHidDeviceInterface); + cRefs = (*ppHidDeviceInterface)->Release(ppHidDeviceInterface); MY_CHECK_CREFS(cRefs); + } +} + +/** Zap the keyboard cache. */ +static void darwinHIDKeyboardCacheZap(void) +{ + /* Release the old cache data first. */ + while (g_cKeyboards > 0) + { + unsigned i = --g_cKeyboards; + darwinHIDKeyboardCacheDestroyEntry(&g_aKeyboards[i]); + } +} + +/** Updates the cached keyboard data. + * @todo The current implementation is very brute force... + * Rewrite it so that it doesn't flush the cache completely but simply checks whether + * anything has changed in the HID config. With any luck, there might even be a callback + * or something we can poll for HID config changes... + * setRemovalCallback() is a start... */ +static void darwinHIDKeyboardCacheDoUpdate(void) +{ + g_u64KeyboardTS = RTTimeMilliTS(); + + /* Dispense with the old cache data. */ + darwinHIDKeyboardCacheZap(); + + /* Open the master port on the first invocation. */ + if (!g_MasterPort) + { + kern_return_t krc = IOMasterPort(MACH_PORT_NULL, &g_MasterPort); + AssertReturnVoid(krc == KERN_SUCCESS); + } + + /* Create a matching dictionary for searching for keyboards devices. */ + static const UInt32 s_Page = kHIDPage_GenericDesktop; + static const UInt32 s_Usage = kHIDUsage_GD_Keyboard; + CFMutableDictionaryRef RefMatchingDict = IOServiceMatching(kIOHIDDeviceKey); + AssertReturnVoid(RefMatchingDict); + CFDictionarySetValue(RefMatchingDict, CFSTR(kIOHIDPrimaryUsagePageKey), + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &s_Page)); + CFDictionarySetValue(RefMatchingDict, CFSTR(kIOHIDPrimaryUsageKey), + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &s_Usage)); + + /* Perform the search and get a collection of keyboard devices. */ + io_iterator_t Keyboards = NULL; + IOReturn rc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &Keyboards); + AssertMsgReturnVoid(rc == kIOReturnSuccess, ("rc=%d\n", rc)); + RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */ + + /* Enumerate the keyboards and query the cache data. */ + unsigned i = 0; + io_object_t KeyboardDevice; + while ( i < RT_ELEMENTS(g_aKeyboards) + && (KeyboardDevice = IOIteratorNext(Keyboards)) != 0) + { + if (darwinHIDKeyboardCacheCreateEntry(&g_aKeyboards[i], KeyboardDevice)) + i++; + IOObjectRelease(KeyboardDevice); + } + g_cKeyboards = i; + + IOObjectRelease(Keyboards); +} + +/** Updates the keyboard cache if it's time to do it again. */ +static void darwinHIDKeyboardCacheUpdate(void) +{ + if ( !g_cKeyboards + /*|| g_u64KeyboardTS - RTTimeMilliTS() > 7500*/ /* 7.5sec */) + darwinHIDKeyboardCacheDoUpdate(); +} + +/** Queries the modifier keys from the (IOKit) HID Manager. */ +static UInt32 darwinQueryHIDModifiers(void) +{ + /* Iterate thru the keyboards collecting their modifier masks. */ + UInt32 fHIDModifiers = 0; + unsigned i = g_cKeyboards; + while (i-- > 0) + { + IOHIDDeviceInterface **ppHidDeviceInterface = g_aKeyboards[i].ppHidDeviceInterface; + if (!ppHidDeviceInterface) + continue; + + unsigned j = g_aKeyboards[i].cCookies; + while (j-- > 0) + { + IOHIDEventStruct HidEvent; + IOReturn rc = (*ppHidDeviceInterface)->getElementValue(ppHidDeviceInterface, + g_aKeyboards[i].aCookies[j].Cookie, + &HidEvent); + if (rc == kIOReturnSuccess) + { + if (HidEvent.value) + fHIDModifiers |= g_aKeyboards[i].aCookies[j].fMask; + } + else + AssertMsgFailed(("rc=%#x\n", rc)); + } + } + + return fHIDModifiers; +} + +#endif /* USE_HID_FOR_MODIFIERS */ + + +void DarwinGrabKeyboard(bool fGlobalHotkeys) +{ + LogFlow(("DarwinGrabKeyboard: fGlobalHotkeys=%RTbool\n", fGlobalHotkeys)); + +#ifdef USE_HID_FOR_MODIFIERS + /* Update the keyboard cache. */ + darwinHIDKeyboardCacheUpdate(); + + /* Start the keyboard queues and query the current mask. */ + g_fHIDQueueEnabled = true; + + unsigned i = g_cKeyboards; + while (i-- > 0) + { + if (g_aKeyboards[i].ppHidQueueInterface) + (*g_aKeyboards[i].ppHidQueueInterface)->start(g_aKeyboards[i].ppHidQueueInterface); + } + + g_fHIDModifierMask = darwinQueryHIDModifiers(); +#endif /* USE_HID_FOR_MODIFIERS */ + + /* Disable hotkeys if requested. */ + if (fGlobalHotkeys) + DarwinDisableGlobalHotKeys(true); +} + +void DarwinReleaseKeyboard() +{ + LogFlow(("DarwinReleaseKeyboard\n")); + + /* Re-enable hotkeys. */ + DarwinDisableGlobalHotKeys(false); + +#ifdef USE_HID_FOR_MODIFIERS + /* Stop and drain the keyboard queues. */ + g_fHIDQueueEnabled = false; + +#if 0 + unsigned i = g_cKeyboards; + while (i-- > 0) + { + if (g_aKeyboards[i].ppHidQueueInterface) + { + + (*g_aKeyboards[i].ppHidQueueInterface)->stop(g_aKeyboards[i].ppHidQueueInterface); + + /* drain it */ + IOReturn rc; + unsigned c = 0; + do + { + IOHIDEventStruct Event; + AbsoluteTime MaxTime = {0,0}; + rc = (*g_aKeyboards[i].ppHidQueueInterface)->getNextEvent(g_aKeyboards[i].ppHidQueueInterface, + &Event, MaxTime, 0); + } while ( rc == kIOReturnSuccess + && c++ < 32); + } + } +#else + /* Kill the keyboard cache. */ + darwinHIDKeyboardCacheZap(); +#endif + + /* Clear the modifier mask. */ + g_fHIDModifierMask = 0; +#endif /* USE_HID_FOR_MODIFIERS */ +} + + +#ifdef VBOX_WITH_KBD_LEDS_SYNC + +/** Prepares dictionary that will be used to match HID LED device(s) while discovering. */ +static CFDictionaryRef darwinQueryLedDeviceMatchingDictionary() +{ + CFDictionaryRef deviceMatchingDictRef; + + // Use two (key, value) pairs: + // - (kIOHIDDeviceUsagePageKey, kHIDPage_GenericDesktop), + // - (kIOHIDDeviceUsageKey, kHIDUsage_GD_Keyboard). */ + + CFNumberRef usagePageKeyCFNumberRef; int usagePageKeyCFNumberValue = kHIDPage_GenericDesktop; + CFNumberRef usageKeyCFNumberRef; int usageKeyCFNumberValue = kHIDUsage_GD_Keyboard; + + usagePageKeyCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePageKeyCFNumberValue); + if (usagePageKeyCFNumberRef) + { + usageKeyCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usageKeyCFNumberValue); + if (usageKeyCFNumberRef) + { + CFStringRef dictionaryKeys[2] = { CFSTR(kIOHIDDeviceUsagePageKey), CFSTR(kIOHIDDeviceUsageKey) }; + CFNumberRef dictionaryVals[2] = { usagePageKeyCFNumberRef, usageKeyCFNumberRef }; + + deviceMatchingDictRef = CFDictionaryCreate(kCFAllocatorDefault, + (const void **)dictionaryKeys, + (const void **)dictionaryVals, + 2, /** two (key, value) pairs */ + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (deviceMatchingDictRef) + { + CFRelease(usageKeyCFNumberRef); + CFRelease(usagePageKeyCFNumberRef); + + return deviceMatchingDictRef; + } + + CFRelease(usageKeyCFNumberRef); + } + + CFRelease(usagePageKeyCFNumberRef); + } + + return NULL; +} + +/** Prepare dictionary that will be used to match HID LED device element(s) while discovering. */ +static CFDictionaryRef darwinQueryLedElementMatchingDictionary() +{ + CFDictionaryRef elementMatchingDictRef; + + // Use only one (key, value) pair to match LED device element: + // - (kIOHIDElementUsagePageKey, kHIDPage_LEDs). */ + + CFNumberRef usagePageKeyCFNumberRef; int usagePageKeyCFNumberValue = kHIDPage_LEDs; + + usagePageKeyCFNumberRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePageKeyCFNumberValue); + if (usagePageKeyCFNumberRef) + { + CFStringRef dictionaryKeys[1] = { CFSTR(kIOHIDElementUsagePageKey), }; + CFNumberRef dictionaryVals[1] = { usagePageKeyCFNumberRef, }; + + elementMatchingDictRef = CFDictionaryCreate(kCFAllocatorDefault, + (const void **)dictionaryKeys, + (const void **)dictionaryVals, + 1, /** one (key, value) pair */ + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + if (elementMatchingDictRef) + { + CFRelease(usagePageKeyCFNumberRef); + return elementMatchingDictRef; + } + + CFRelease(usagePageKeyCFNumberRef); + } + + return NULL; +} + +/** Turn ON or OFF a particular LED. */ +static int darwinLedElementSetValue(IOHIDDeviceRef hidDevice, IOHIDElementRef element, bool fEnabled) +{ + IOHIDValueRef valueRef; + IOReturn rc = kIOReturnError; + + /* Try to resume suspended keyboard devices. Abort if failed in order to avoid GUI freezes. */ + int rc1 = SUPR3ResumeSuspendedKeyboards(); + if (RT_FAILURE(rc1)) + return rc1; + + valueRef = IOHIDValueCreateWithIntegerValue(kCFAllocatorDefault, element, 0, (fEnabled) ? 1 : 0); + if (valueRef) + { + rc = IOHIDDeviceSetValue(hidDevice, element, valueRef); + if (rc != kIOReturnSuccess) + LogRel2(("Warning! Something went wrong in attempt to turn %s HID device led (error %d)!\n", ((fEnabled) ? "on" : "off"), rc)); + else + LogRel2(("Led (%d) is turned %s\n", (int)IOHIDElementGetUsage(element), ((fEnabled) ? "on" : "off"))); + + CFRelease(valueRef); + } + + return rc; +} + +/** Get state of a particular led. */ +static int darwinLedElementGetValue(IOHIDDeviceRef hidDevice, IOHIDElementRef element, bool *fEnabled) +{ + /* Try to resume suspended keyboard devices. Abort if failed in order to avoid GUI freezes. */ + int rc1 = SUPR3ResumeSuspendedKeyboards(); + if (RT_FAILURE(rc1)) + return rc1; + + IOHIDValueRef valueRef; + IOReturn rc = IOHIDDeviceGetValue(hidDevice, element, &valueRef); + if (rc == kIOReturnSuccess) + { + CFIndex integerValue = IOHIDValueGetIntegerValue(valueRef); + switch (integerValue) + { + case 0: + *fEnabled = false; + break; + case 1: + *fEnabled = true; + break; + default: + rc = kIOReturnError; + } + + /*CFRelease(valueRef); - IOHIDDeviceGetValue does not return a reference, so no need to release it. */ + } + + return rc; +} + +/** Set corresponding states from NumLock, CapsLock and ScrollLock leds. */ +static int darwinSetDeviceLedsState(IOHIDDeviceRef hidDevice, CFDictionaryRef elementMatchingDict, + bool fNumLockOn, bool fCapsLockOn, bool fScrollLockOn) +{ + CFArrayRef matchingElementsArrayRef; + int rc2 = 0; + + matchingElementsArrayRef = IOHIDDeviceCopyMatchingElements(hidDevice, elementMatchingDict, kIOHIDOptionsTypeNone); + if (matchingElementsArrayRef) + { + CFIndex cElements = CFArrayGetCount(matchingElementsArrayRef); + + /* Cycle though all the elements we found */ + for (CFIndex i = 0; i < cElements; i++) + { + IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(matchingElementsArrayRef, i); + uint32_t usage = IOHIDElementGetUsage(element); + int rc = 0; + + switch (usage) + { + case kHIDUsage_LED_NumLock: + rc = darwinLedElementSetValue(hidDevice, element, fNumLockOn); + break; + + case kHIDUsage_LED_CapsLock: + rc = darwinLedElementSetValue(hidDevice, element, fCapsLockOn); + break; + case kHIDUsage_LED_ScrollLock: + rc = darwinLedElementSetValue(hidDevice, element, fScrollLockOn); + break; + } + if (rc != 0) + { + LogRel2(("Failed to set led (%d) state\n", (int)IOHIDElementGetUsage(element))); + rc2 = kIOReturnError; + } + } + + CFRelease(matchingElementsArrayRef); + } + + return rc2; +} + +/** Get corresponding states for NumLock, CapsLock and ScrollLock leds. */ +static int darwinGetDeviceLedsState(IOHIDDeviceRef hidDevice, CFDictionaryRef elementMatchingDict, + bool *fNumLockOn, bool *fCapsLockOn, bool *fScrollLockOn) +{ + CFArrayRef matchingElementsArrayRef; + int rc2 = 0; + + matchingElementsArrayRef = IOHIDDeviceCopyMatchingElements(hidDevice, elementMatchingDict, kIOHIDOptionsTypeNone); + if (matchingElementsArrayRef) + { + CFIndex cElements = CFArrayGetCount(matchingElementsArrayRef); + + /* Cycle though all the elements we found */ + for (CFIndex i = 0; i < cElements; i++) + { + IOHIDElementRef element = (IOHIDElementRef)CFArrayGetValueAtIndex(matchingElementsArrayRef, i); + uint32_t usage = IOHIDElementGetUsage(element); + int rc = 0; + + switch (usage) + { + case kHIDUsage_LED_NumLock: + rc = darwinLedElementGetValue(hidDevice, element, fNumLockOn); + break; + + case kHIDUsage_LED_CapsLock: + rc = darwinLedElementGetValue(hidDevice, element, fCapsLockOn); + break; + case kHIDUsage_LED_ScrollLock: + rc = darwinLedElementGetValue(hidDevice, element, fScrollLockOn); + break; + } + if (rc != 0) + { + LogRel2(("Failed to get led (%d) state\n", (int)IOHIDElementGetUsage(element))); + rc2 = kIOReturnError; + } + } + + CFRelease(matchingElementsArrayRef); + } + + return rc2; +} + +/** Get integer property of HID device */ +static uint32_t darwinQueryIntProperty(IOHIDDeviceRef pHidDeviceRef, CFStringRef pProperty) +{ + CFTypeRef pNumberRef; + uint32_t value = 0; + + AssertReturn(pHidDeviceRef, 0); + AssertReturn(pProperty, 0); + + pNumberRef = IOHIDDeviceGetProperty(pHidDeviceRef, pProperty); + if (pNumberRef) + { + if (CFGetTypeID(pNumberRef) == CFNumberGetTypeID()) + { + if (CFNumberGetValue((CFNumberRef)pNumberRef, kCFNumberSInt32Type, &value)) + return value; + } + } + + return 0; +} + +/** Get HID Vendor ID */ +static uint32_t darwinHidVendorId(IOHIDDeviceRef pHidDeviceRef) +{ + return darwinQueryIntProperty(pHidDeviceRef, CFSTR(kIOHIDVendorIDKey)); +} + +/** Get HID Product ID */ +static uint32_t darwinHidProductId(IOHIDDeviceRef pHidDeviceRef) +{ + return darwinQueryIntProperty(pHidDeviceRef, CFSTR(kIOHIDProductIDKey)); +} + +/** Get HID Location ID */ +static uint32_t darwinHidLocationId(IOHIDDeviceRef pHidDeviceRef) +{ + return darwinQueryIntProperty(pHidDeviceRef, CFSTR(kIOHIDLocationIDKey)); +} + +/** Some keyboard devices might freeze after LEDs manipulation. We filter out such devices here. + * In the list below, devices that known to have such issues. If you want to add new device, + * then add it here. Currently, we only filter devices by Vendor ID. + * In future it might make sense to take Product ID into account as well. */ +static bool darwinHidDeviceSupported(IOHIDDeviceRef pHidDeviceRef) +{ +#ifndef VBOX_WITHOUT_KBD_LEDS_SYNC_FILTERING + bool fSupported = true; + uint32_t vendorId = darwinHidVendorId(pHidDeviceRef); + uint32_t productId = darwinHidProductId(pHidDeviceRef); + + if (vendorId == 0x05D5) /* Genius */ + { + if (productId == 0x8001) /* GK-04008/C keyboard */ + fSupported = false; + } + if (vendorId == 0xE6A) /* Megawin Technology */ + { + if (productId == 0x6001) /* Japanese flexible keyboard */ + fSupported = false; + } + + LogRel2(("HID device [VendorID=0x%X, ProductId=0x%X] %s in the list of supported devices.\n", vendorId, productId, (fSupported ? "is" : "is not"))); + + return fSupported; +#else /* !VBOX_WITH_KBD_LEDS_SYNC_FILTERING */ + return true; +#endif +} + +/** IOKit key press callback helper: take care about key-down event. + * This code should be executed within a critical section under pHidState->fifoEventQueueLock. */ +static void darwinHidInputCbKeyDown(VBoxKbdState_t *pKbd, uint32_t iKeyCode, VBoxHidsState_t *pHidState) +{ + VBoxKbdEvent_t *pEvent = (VBoxKbdEvent_t *)malloc(sizeof(VBoxKbdEvent_t)); + + if (pEvent) + { + /* Queue Key-Down event. */ + pEvent->tsKeyDown = RTTimeSystemMilliTS(); + pEvent->pKbd = pKbd; + pEvent->iKeyCode = iKeyCode; + + CFArrayAppendValue(pHidState->pFifoEventQueue, (void *)pEvent); + + LogRel2(("IOHID: KBD %d: Modifier Key-Down event\n", (int)pKbd->idxPosition)); + } + else + LogRel2(("IOHID: Modifier Key-Up event. Unable to find memory for KBD %d event\n", (int)pKbd->idxPosition)); +} + +/** IOkit and Carbon key press callbacks helper: CapsLock timeout checker. + * + * Returns FALSE if CAPS LOCK timeout not occurred and its state still was not switched (Apple kbd). + * Returns TRUE if CAPS LOCK timeout occurred and its state was switched (Apple kbd). + * Returns TRUE for non-Apple kbd. */ +static bool darwinKbdCapsEventMatches(VBoxKbdEvent_t *pEvent, bool fCapsLed) +{ + // CapsLock timeout is only applicable if conditions + // below are satisfied: + // + // a) Key pressed on Apple keyboard + // b) CapsLed is OFF at the moment when CapsLock key is pressed + + bool fAppleKeyboard = (pEvent->pKbd->cCapsLockTimeout > 0); + + /* Apple keyboard */ + if (fAppleKeyboard && !fCapsLed) + { + uint64_t tsDiff = RTTimeSystemMilliTS() - pEvent->tsKeyDown; + if (tsDiff < pEvent->pKbd->cCapsLockTimeout) + return false; + } + + return true; +} + +/** IOKit key press callback helper: take care about key-up event. + * This code should be executed within a critical section under pHidState->fifoEventQueueLock. */ +static void darwinHidInputCbKeyUp(VBoxKbdState_t *pKbd, uint32_t iKeyCode, VBoxHidsState_t *pHidState) +{ + CFIndex iQueue = 0; + VBoxKbdEvent_t *pEvent = NULL; + + // Key-up event assumes that key-down event occured previously. If so, an event + // data should be in event queue. Attempt to find it. + for (CFIndex i = 0; i < CFArrayGetCount(pHidState->pFifoEventQueue); i++) + { + VBoxKbdEvent_t *pCachedEvent = (VBoxKbdEvent_t *)CFArrayGetValueAtIndex(pHidState->pFifoEventQueue, i); + + if (pCachedEvent && pCachedEvent->pKbd == pKbd && pCachedEvent->iKeyCode == iKeyCode) + { + pEvent = pCachedEvent; + iQueue = i; + break; + } + } + + /* Event found. */ + if (pEvent) + { + // NUM LOCK should not have timeout and its press should immidiately trigger Carbon callback. + // Therefore, if it is still in queue this is a problem because it was not handled by Carbon callback. + // This mean that NUM LOCK is most likely out of sync. + if (iKeyCode == kHIDUsage_KeypadNumLock) + { + LogRel2(("IOHID: KBD %d: Modifier Key-Up event. Key-Down event was not habdled by Carbon callback. " + "NUM LOCK is most likely out of sync\n", (int)pKbd->idxPosition)); + } + else if (iKeyCode == kHIDUsage_KeyboardCapsLock) + { + // If CAPS LOCK key-press event still not match CAPS LOCK timeout criteria, Carbon callback + // should not be triggered for this event at all. Threfore, event should be removed from queue. + if (!darwinKbdCapsEventMatches(pEvent, pHidState->guestState.fCapsLockOn)) + { + CFArrayRemoveValueAtIndex(pHidState->pFifoEventQueue, iQueue); + + LogRel2(("IOHID: KBD %d: Modifier Key-Up event on Apple keyboard. Key-Down event was triggered %llu ms " + "ago. Carbon event should not be triggered, removed from queue\n", (int)pKbd->idxPosition, + RTTimeSystemMilliTS() - pEvent->tsKeyDown)); + free(pEvent); + } + else + { + // CAPS LOCK key-press event matches to CAPS LOCK timeout criteria and still present in queue. + // This might mean that Carbon callback was triggered for this event, but cached keyboard state was not updated. + // It also might mean that Carbon callback still was not triggered, but it will be soon. + // Threfore, CAPS LOCK might be out of sync. + LogRel2(("IOHID: KBD %d: Modifier Key-Up event. Key-Down event was triggered %llu ms " + "ago and still was not handled by Carbon callback. CAPS LOCK might out of sync if " + "Carbon will not handle this\n", (int)pKbd->idxPosition, RTTimeSystemMilliTS() - pEvent->tsKeyDown)); + } + } + } + else + LogRel2(("IOHID: KBD %d: Modifier Key-Up event. Modifier state change was " + "successfully handled by Carbon callback\n", (int)pKbd->idxPosition)); +} + +/** IOKit key press callback. Triggered before Carbon callback. We remember which keyboard produced a keypress here. */ +static void darwinHidInputCallback(void *pData, IOReturn unused, void *unused1, IOHIDValueRef pValueRef) +{ + (void)unused; + (void)unused1; + + AssertReturnVoid(pValueRef); + + IOHIDElementRef pElementRef = IOHIDValueGetElement(pValueRef); + AssertReturnVoid(pElementRef); + + uint32_t usage = IOHIDElementGetUsage(pElementRef); + + if (IOHIDElementGetUsagePage(pElementRef) == kHIDPage_KeyboardOrKeypad) /* Keyboard or keypad event */ + if (usage == kHIDUsage_KeyboardCapsLock || /* CapsLock key has been pressed */ + usage == kHIDUsage_KeypadNumLock) /* ... or NumLock key has been pressed */ + { + VBoxKbdState_t *pKbd = (VBoxKbdState_t *)pData; + + if (pKbd && pKbd->pParentContainer) + { + bool fKeyDown = (IOHIDValueGetIntegerValue(pValueRef) == 1); + VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pKbd->pParentContainer; + + AssertReturnVoid(pHidState); + + if (RT_FAILURE(RTSemMutexRequest(pHidState->fifoEventQueueLock, RT_INDEFINITE_WAIT))) + return ; + + /* Handle corresponding event. */ + if (fKeyDown) + darwinHidInputCbKeyDown(pKbd, usage, pHidState); + else + darwinHidInputCbKeyUp(pKbd, usage, pHidState); + + RTSemMutexRelease(pHidState->fifoEventQueueLock); + } + else + LogRel2(("IOHID: No KBD: A modifier key has been pressed\n")); + } +} + +/** Carbon key press callback helper: find last occured KBD event in queue + * (ignoring those events which do not match CAPS LOCK timeout criteria). + * Once event found, it is removed from queue. This code should be executed + * within a critical section under pHidState->fifoEventQueueLock. */ +static VBoxKbdEvent_t *darwinCarbonCbFindEvent(VBoxHidsState_t *pHidState) +{ + VBoxKbdEvent_t *pEvent = NULL; + + for (CFIndex i = 0; i < CFArrayGetCount(pHidState->pFifoEventQueue); i++) + { + pEvent = (VBoxKbdEvent_t *)CFArrayGetValueAtIndex(pHidState->pFifoEventQueue, i); + + /* Paranoia: skip potentially dangerous data items. */ + if (!pEvent || !pEvent->pKbd) continue; + + if ( pEvent->iKeyCode == kHIDUsage_KeypadNumLock + || (pEvent->iKeyCode == kHIDUsage_KeyboardCapsLock && darwinKbdCapsEventMatches(pEvent, pHidState->guestState.fCapsLockOn))) + { + /* Found one. Remove it from queue. */ + CFArrayRemoveValueAtIndex(pHidState->pFifoEventQueue, i); + + LogRel2(("CARBON: Found event in queue: %d (KBD %d, tsKeyDown=%llu, pressed %llu ms ago)\n", (int)i, + (int)pEvent->pKbd->idxPosition, pEvent->tsKeyDown, RTTimeSystemMilliTS() - pEvent->tsKeyDown)); + + break; + } + else + LogRel2(("CARBON: Skip keyboard event from KBD %d, key pressed %llu ms ago\n", + (int)pEvent->pKbd->idxPosition, RTTimeSystemMilliTS() - pEvent->tsKeyDown)); + + pEvent = NULL; + } + + return pEvent; +} + +/** Carbon key press callback. Triggered after IOKit callback. */ +static CGEventRef darwinCarbonCallback(CGEventTapProxy unused, CGEventType unused1, CGEventRef pEventRef, void *pData) +{ + (void)unused; + (void)unused1; + + CGEventFlags fMask = CGEventGetFlags(pEventRef); + bool fCaps = (bool)(fMask & NX_ALPHASHIFTMASK); + bool fNum = (bool)(fMask & NX_NUMERICPADMASK); + CGKeyCode key = CGEventGetIntegerValueField(pEventRef, kCGKeyboardEventKeycode); + + VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pData; + AssertReturn(pHidState, pEventRef); + + if (RT_FAILURE(RTSemMutexRequest(pHidState->fifoEventQueueLock, RT_INDEFINITE_WAIT))) + return pEventRef; + + if (key == kHIDUsage_KeyboardCapsLock || + key == kHIDUsage_KeypadNumLock) + { + /* Attempt to find an event queued by IOKit callback. */ + VBoxKbdEvent_t *pEvent = darwinCarbonCbFindEvent(pHidState); + if (pEvent) + { + VBoxKbdState_t *pKbd = pEvent->pKbd; + + LogRel2(("CARBON: KBD %d: caps=%s, num=%s. tsKeyDown=%llu, tsKeyUp=%llu [tsDiff=%llu ms]. %d events in queue.\n", + (int)pKbd->idxPosition, VBOX_BOOL_TO_STR_STATE(fCaps), VBOX_BOOL_TO_STR_STATE(fNum), + pEvent->tsKeyDown, RTTimeSystemMilliTS(), RTTimeSystemMilliTS() - pEvent->tsKeyDown, + CFArrayGetCount(pHidState->pFifoEventQueue))); + + pKbd->LED.fCapsLockOn = fCaps; + pKbd->LED.fNumLockOn = fNum; + + /* Silently resync last touched KBD device */ + if (pHidState) + { + CFDictionaryRef elementMatchingDict = darwinQueryLedElementMatchingDictionary(); + if (elementMatchingDict) + { + (void)darwinSetDeviceLedsState(pKbd->pDevice, + elementMatchingDict, + pHidState->guestState.fNumLockOn, + pHidState->guestState.fCapsLockOn, + pHidState->guestState.fScrollLockOn); + + CFRelease(elementMatchingDict); + } + } + + free(pEvent); + } + else + LogRel2(("CARBON: No KBD to take care when modifier key has been pressed: caps=%s, num=%s (%d events in queue)\n", + VBOX_BOOL_TO_STR_STATE(fCaps), VBOX_BOOL_TO_STR_STATE(fNum), CFArrayGetCount(pHidState->pFifoEventQueue))); + } + + RTSemMutexRelease(pHidState->fifoEventQueueLock); + + return pEventRef; +} + +/** Helper function to obtain interface for IOUSBInterface IOService. */ +static IOUSBDeviceInterface ** darwinQueryUsbHidInterfaceInterface(io_service_t service) +{ + kern_return_t rc; + IOCFPlugInInterface **ppPluginInterface = NULL; + SInt32 iScore; + + rc = IOCreatePlugInInterfaceForService(service, kIOUSBInterfaceUserClientTypeID, + kIOCFPlugInInterfaceID, &ppPluginInterface, &iScore); + + if (rc == kIOReturnSuccess && ppPluginInterface != NULL) + { + IOUSBDeviceInterface **ppUsbDeviceInterface = NULL; + + rc = (*ppPluginInterface)->QueryInterface(ppPluginInterface, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), + (LPVOID *)&ppUsbDeviceInterface); + IODestroyPlugInInterface(ppPluginInterface); + + if (rc == kIOReturnSuccess && ppUsbDeviceInterface != NULL) + return ppUsbDeviceInterface; + else + LogRel2(("Failed to query plugin interface for USB device\n")); + + } + else + LogRel2(("Failed to create plugin interface for USB device\n")); + + return NULL; +} + +/** Helper function for IOUSBInterface IOService general interest notification callback: resync LEDs. */ +static void darwinUsbHidResyncLeds(VBoxKbdState_t *pKbd) +{ + AssertReturnVoid(pKbd); + + VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pKbd->pParentContainer; + CFDictionaryRef elementMatchingDict = darwinQueryLedElementMatchingDictionary(); + if (elementMatchingDict) + { + LogRel2(("Do HID device resync at location 0x%X \n", pKbd->idLocation)); + (void)darwinSetDeviceLedsState(pKbd->pDevice, elementMatchingDict, + pHidState->guestState.fNumLockOn, pHidState->guestState.fCapsLockOn, pHidState->guestState.fScrollLockOn); + CFRelease(elementMatchingDict); + } +} + +/** IOUSBInterface IOService general interest notification callback. When we receive it, we do + * silently resync kbd which has just changed its state. */ +static void darwinUsbHidGeneralInterestCb(void *pData, io_service_t unused1, natural_t msg, void *unused2) +{ + NOREF(unused1); + NOREF(unused2); + + AssertReturnVoid(pData); + VBoxKbdState_t *pKbd = (VBoxKbdState_t *)pData; + + switch (msg) + { + case kIOUSBMessagePortHasBeenSuspended: + { + LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessagePortHasBeenSuspended for KBD %d (Location ID: 0x%X)\n", + (int)(pKbd->idxPosition), pKbd->idLocation)); + break; + } + + case kIOUSBMessagePortHasBeenResumed: + { + LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessagePortHasBeenResumed for KBD %d (Location ID: 0x%X)\n", + (int)(pKbd->idxPosition), pKbd->idLocation)); + break; + } + + case kIOUSBMessagePortHasBeenReset: + { + LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessagePortHasBeenReset for KBD %d (Location ID: 0x%X)\n", + (int)(pKbd->idxPosition), pKbd->idLocation)); + darwinUsbHidResyncLeds(pKbd); + break; + } + + case kIOUSBMessageCompositeDriverReconfigured: + { + LogRel2(("IOUSBInterface IOService general interest notification kIOUSBMessageCompositeDriverReconfigured for KBD %d (Location ID: 0x%X)\n", + (int)(pKbd->idxPosition), pKbd->idLocation)); + break; + } + + case kIOMessageServiceWasClosed: + { + LogRel2(("IOUSBInterface IOService general interest notification kIOMessageServiceWasClosed for KBD %d (Location ID: 0x%X)\n", + (int)(pKbd->idxPosition), pKbd->idLocation)); + break; + } + + default: + LogRel2(("IOUSBInterface IOService general interest notification 0x%X for KBD %d (Location ID: 0x%X)\n", + msg, (int)(pKbd->idxPosition), pKbd->idLocation)); + } +} + +/** Get pre-cached KBD device by its Location ID. */ +static VBoxKbdState_t *darwinUsbHidQueryKbdByLocationId(uint32_t idLocation, VBoxHidsState_t *pHidState) +{ + AssertReturn(pHidState, NULL); + + for (CFIndex i = 0; i < CFArrayGetCount(pHidState->pDeviceCollection); i++) + { + VBoxKbdState_t *pKbd = (VBoxKbdState_t *)CFArrayGetValueAtIndex(pHidState->pDeviceCollection, i); + if (pKbd && pKbd->idLocation == idLocation) + { + LogRel2(("Lookup USB HID Device by location ID 0x%X: found match\n", idLocation)); + return pKbd; + } + } + + LogRel2(("Lookup USB HID Device by location ID 0x%X: no matches found:\n", idLocation)); + + return NULL; +} + +/** IOUSBInterface IOService match notification callback: issued when IOService instantinates. + * We subscribe to general interest notifications for available IOServices here. */ +static void darwinUsbHidDeviceMatchCb(void *pData, io_iterator_t iter) +{ + AssertReturnVoid(pData); + + io_service_t service; + VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pData; + + while ((service = IOIteratorNext(iter))) + { + kern_return_t rc; + + IOUSBDeviceInterface **ppUsbDeviceInterface = darwinQueryUsbHidInterfaceInterface(service); + + if (ppUsbDeviceInterface) + { + uint8_t idDeviceClass, idDeviceSubClass; + UInt32 idLocation; + + rc = (*ppUsbDeviceInterface)->GetLocationID (ppUsbDeviceInterface, &idLocation); AssertMsg(rc == 0, ("Failed to get Location ID")); + rc = (*ppUsbDeviceInterface)->GetDeviceClass (ppUsbDeviceInterface, &idDeviceClass); AssertMsg(rc == 0, ("Failed to get Device Class")); + rc = (*ppUsbDeviceInterface)->GetDeviceSubClass(ppUsbDeviceInterface, &idDeviceSubClass); AssertMsg(rc == 0, ("Failed to get Device Subclass")); + + if (idDeviceClass == kUSBHIDInterfaceClass && idDeviceSubClass == kUSBHIDBootInterfaceSubClass) + { + VBoxKbdState_t *pKbd = darwinUsbHidQueryKbdByLocationId((uint32_t)idLocation, pHidState); + + if (pKbd) + { + rc = IOServiceAddInterestNotification(pHidState->pNotificationPrortRef, service, kIOGeneralInterest, + darwinUsbHidGeneralInterestCb, pKbd, &pHidState->pUsbHidGeneralInterestNotify); + + AssertMsg(rc == 0, ("Failed to add general interest notification")); + + LogRel2(("Found HID device at location 0x%X: class 0x%X, subclass 0x%X\n", idLocation, idDeviceClass, idDeviceSubClass)); + } + } + + rc = (*ppUsbDeviceInterface)->Release(ppUsbDeviceInterface); AssertMsg(rc == 0, ("Failed to release USB device interface")); + } + + IOObjectRelease(service); + } +} + +/** Register IOUSBInterface IOService match notification callback in order to recync KBD + * device when it reports state change. */ +static int darwinUsbHidSubscribeInterestNotifications(VBoxHidsState_t *pHidState) +{ + AssertReturn(pHidState, kIOReturnBadArgument); + + int rc = kIOReturnNoMemory; + CFDictionaryRef pDictionary = IOServiceMatching(kIOUSBInterfaceClassName); + + if (pDictionary) + { + pHidState->pNotificationPrortRef = IONotificationPortCreate(kIOMasterPortDefault); + if (pHidState->pNotificationPrortRef) + { + CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(pHidState->pNotificationPrortRef), kCFRunLoopDefaultMode); + + rc = IOServiceAddMatchingNotification(pHidState->pNotificationPrortRef, kIOMatchedNotification, + pDictionary, darwinUsbHidDeviceMatchCb, pHidState, + &pHidState->pUsbHidDeviceMatchNotify); + + if (rc == kIOReturnSuccess && pHidState->pUsbHidDeviceMatchNotify != IO_OBJECT_NULL) + { + darwinUsbHidDeviceMatchCb(pHidState, pHidState->pUsbHidDeviceMatchNotify); + LogRel2(("Successfully subscribed to IOUSBInterface IOService match notifications\n")); + } + else + LogRel2(("Failed to subscribe to IOUSBInterface IOService match notifications: subscription error 0x%X\n", rc)); + } + else + LogRel2(("Failed to subscribe to IOUSBInterface IOService match notifications: unable to create notification port\n")); + } + else + LogRel2(("Failed to subscribe to IOUSBInterface IOService match notifications: no memory\n")); + + return rc; +} + +/** Remove IOUSBInterface IOService match notification subscription. */ +static void darwinUsbHidUnsubscribeInterestNotifications(VBoxHidsState_t *pHidState) +{ + AssertReturnVoid(pHidState); + + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(pHidState->pNotificationPrortRef), kCFRunLoopDefaultMode); + IONotificationPortDestroy(pHidState->pNotificationPrortRef); + pHidState->pNotificationPrortRef = NULL; + + LogRel2(("Successfully un-subscribed from IOUSBInterface IOService match notifications\n")); +} + +/** This callback is called when user physically removes HID device. We remove device from cache here. */ +static void darwinHidRemovalCallback(void *pData, IOReturn unused, void *unused1) +{ + (void)unused; + (void)unused1; + + VBoxKbdState_t *pKbd = (VBoxKbdState_t *)pData; AssertReturnVoid(pKbd); + VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pKbd->pParentContainer; AssertReturnVoid(pHidState); + + AssertReturnVoid(pHidState->pDeviceCollection); + + LogRel2(("Forget KBD %d\n", (int)pKbd->idxPosition)); + + //if (RT_FAILURE(RTSemMutexRequest(pHidState->fifoEventQueueLock, RT_INDEFINITE_WAIT))) + // return ; + + CFArrayRemoveValueAtIndex(pHidState->pDeviceCollection, pKbd->idxPosition); + free(pKbd); + + //RTSemMutexRelease(pHidState->fifoEventQueueLock); +} + +/** Check if we already cached given device */ +static bool darwinIsDeviceInCache(VBoxHidsState_t *pState, IOHIDDeviceRef pDevice) +{ + AssertReturn(pState, false); + AssertReturn(pState->pDeviceCollection, false); + + for (CFIndex i = 0; i < CFArrayGetCount(pState->pDeviceCollection); i++) + { + VBoxKbdState_t *pKbd = (VBoxKbdState_t *)CFArrayGetValueAtIndex(pState->pDeviceCollection, i); + if (pKbd && pKbd->pDevice == pDevice) + return true; + } + + return false; +} + +/** Add device to cache. */ +static void darwinHidAddDevice(VBoxHidsState_t *pHidState, IOHIDDeviceRef pDevice, bool fApplyLedState) +{ + int rc; + + if (!darwinIsDeviceInCache(pHidState, pDevice)) + { + if (IOHIDDeviceConformsTo(pDevice, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard) + && darwinHidDeviceSupported(pDevice)) + { + VBoxKbdState_t *pKbd = (VBoxKbdState_t *)malloc(sizeof(VBoxKbdState_t)); + if (pKbd) + { + pKbd->pDevice = pDevice; + pKbd->pParentContainer = (void *)pHidState; + pKbd->idxPosition = CFArrayGetCount(pHidState->pDeviceCollection); + pKbd->idLocation = darwinHidLocationId(pDevice); + + // Some Apple keyboards have CAPS LOCK key timeout. According to corresponding + // kext plist files, it is equals to 75 ms. For such devices we only add info into our FIFO event + // queue if the time between Key-Down and Key-Up events >= 75 ms. + pKbd->cCapsLockTimeout = (darwinHidVendorId(pKbd->pDevice) == kIOUSBVendorIDAppleComputer) ? 75 : 0; + + CFDictionaryRef elementMatchingDict = darwinQueryLedElementMatchingDictionary(); + if (elementMatchingDict) + { + rc = darwinGetDeviceLedsState(pKbd->pDevice, + elementMatchingDict, + &pKbd->LED.fNumLockOn, + &pKbd->LED.fCapsLockOn, + &pKbd->LED.fScrollLockOn); + + // This should never happen, but if happened -- mark all the leds of current + // device as turned OFF. + if (rc != 0) + { + LogRel2(("Unable to get leds state for device %d. Mark leds as turned off\n", (int)(pKbd->idxPosition))); + pKbd->LED.fNumLockOn = + pKbd->LED.fCapsLockOn = + pKbd->LED.fScrollLockOn = false; + } + + /* Register per-device removal callback */ + IOHIDDeviceRegisterRemovalCallback(pKbd->pDevice, darwinHidRemovalCallback, (void *)pKbd); + + /* Register per-device input callback */ + IOHIDDeviceRegisterInputValueCallback(pKbd->pDevice, darwinHidInputCallback, (void *)pKbd); + IOHIDDeviceScheduleWithRunLoop(pKbd->pDevice, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + + CFArrayAppendValue(pHidState->pDeviceCollection, (void *)pKbd); + + LogRel2(("Saved LEDs for KBD %d (%p): fNumLockOn=%s, fCapsLockOn=%s, fScrollLockOn=%s\n", + (int)pKbd->idxPosition, pKbd, VBOX_BOOL_TO_STR_STATE(pKbd->LED.fNumLockOn), VBOX_BOOL_TO_STR_STATE(pKbd->LED.fCapsLockOn), + VBOX_BOOL_TO_STR_STATE(pKbd->LED.fScrollLockOn))); + + if (fApplyLedState) + { + rc = darwinSetDeviceLedsState(pKbd->pDevice, elementMatchingDict, pHidState->guestState.fNumLockOn, + pHidState->guestState.fCapsLockOn, pHidState->guestState.fScrollLockOn); + if (rc != 0) + LogRel2(("Unable to apply guest state to newly attached device\n")); + } + + CFRelease(elementMatchingDict); + return; + } + + free(pKbd); + } + } + } +} + +/** This callback is called when new HID device discovered by IOHIDManager. We add devices to cache here and only here! */ +static void darwinHidMatchingCallback(void *pData, IOReturn unused, void *unused1, IOHIDDeviceRef pDevice) +{ + (void)unused; + (void)unused1; + + VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pData; + + AssertReturnVoid(pHidState); + AssertReturnVoid(pHidState->pDeviceCollection); + AssertReturnVoid(pDevice); + + darwinHidAddDevice(pHidState, pDevice, true); +} + +/** Register Carbon key press callback. */ +static int darwinAddCarbonHandler(VBoxHidsState_t *pHidState) +{ + CFMachPortRef pTapRef; + CGEventMask fMask = CGEventMaskBit(kCGEventFlagsChanged); + + AssertReturn(pHidState, kIOReturnError); + + /* Create FIFO event queue for keyboard events */ + pHidState->pFifoEventQueue = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); + AssertReturn(pHidState->pFifoEventQueue, kIOReturnError); + + /* Create Lock for FIFO event queue */ + if (RT_FAILURE(RTSemMutexCreate(&pHidState->fifoEventQueueLock))) + { + LogRel2(("Unable to create Lock for FIFO event queue\n")); + CFRelease(pHidState->pFifoEventQueue); + pHidState->pFifoEventQueue = NULL; + return kIOReturnError; + } + + pTapRef = CGEventTapCreate(kCGSessionEventTap, kCGTailAppendEventTap, kCGEventTapOptionDefault, fMask, + darwinCarbonCallback, (void *)pHidState); + if (pTapRef) + { + CFRunLoopSourceRef pLoopSourceRef; + pLoopSourceRef = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, pTapRef, 0); + if (pLoopSourceRef) + { + CFRunLoopAddSource(CFRunLoopGetCurrent(), pLoopSourceRef, kCFRunLoopDefaultMode); + CGEventTapEnable(pTapRef, true); + + pHidState->pTapRef = pTapRef; + pHidState->pLoopSourceRef = pLoopSourceRef; + + return 0; + } + else + LogRel2(("Unable to create a loop source\n")); + + CFRelease(pTapRef); + } + else + LogRel2(("Unable to create an event tap\n")); + + return kIOReturnError; +} + +/** Remove Carbon key press callback. */ +static void darwinRemoveCarbonHandler(VBoxHidsState_t *pHidState) +{ + AssertReturnVoid(pHidState); + AssertReturnVoid(pHidState->pTapRef); + AssertReturnVoid(pHidState->pLoopSourceRef); + AssertReturnVoid(pHidState->pFifoEventQueue); + + CGEventTapEnable(pHidState->pTapRef, false); + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), pHidState->pLoopSourceRef, kCFRunLoopDefaultMode); + CFRelease(pHidState->pLoopSourceRef); + CFRelease(pHidState->pTapRef); + + RTSemMutexRequest(pHidState->fifoEventQueueLock, RT_INDEFINITE_WAIT); + CFRelease(pHidState->pFifoEventQueue); + pHidState->pFifoEventQueue = NULL; + RTSemMutexRelease(pHidState->fifoEventQueueLock); + + RTSemMutexDestroy(pHidState->fifoEventQueueLock); +} + +#endif /* !VBOX_WITH_KBD_LEDS_SYNC */ + + +void *DarwinHidDevicesKeepLedsState() +{ +#ifdef VBOX_WITH_KBD_LEDS_SYNC + IOReturn rc; + VBoxHidsState_t *pHidState; + + pHidState = (VBoxHidsState_t *)malloc(sizeof(VBoxHidsState_t)); + AssertReturn(pHidState, NULL); + + pHidState->hidManagerRef = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); + if (pHidState->hidManagerRef) + { + CFDictionaryRef deviceMatchingDictRef = darwinQueryLedDeviceMatchingDictionary(); + if (deviceMatchingDictRef) + { + IOHIDManagerScheduleWithRunLoop(pHidState->hidManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + IOHIDManagerSetDeviceMatching(pHidState->hidManagerRef, deviceMatchingDictRef); + + rc = IOHIDManagerOpen(pHidState->hidManagerRef, kIOHIDOptionsTypeNone); + if (rc == kIOReturnSuccess) + { + pHidState->pDeviceCollection = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); + if (pHidState->pDeviceCollection) + { + if (darwinAddCarbonHandler(pHidState) == 0) + { + /* Populate cache with HID devices */ + CFSetRef pDevicesSet = IOHIDManagerCopyDevices(pHidState->hidManagerRef); + if (pDevicesSet) + { + CFIndex cDevices = CFSetGetCount(pDevicesSet); + + IOHIDDeviceRef *ppDevices = (IOHIDDeviceRef *)malloc((size_t)cDevices * sizeof(IOHIDDeviceRef)); + if (ppDevices) + { + CFSetGetValues(pDevicesSet, (const void **)ppDevices); + for (CFIndex i= 0; i < cDevices; i++) + darwinHidAddDevice(pHidState, (IOHIDDeviceRef)ppDevices[i], false); + + free(ppDevices); + } + + CFRelease(pDevicesSet); + } + + IOHIDManagerRegisterDeviceMatchingCallback(pHidState->hidManagerRef, darwinHidMatchingCallback, (void *)pHidState); + + CFRelease(deviceMatchingDictRef); + + /* This states should be set on broadcast */ + pHidState->guestState.fNumLockOn = + pHidState->guestState.fCapsLockOn = + pHidState->guestState.fScrollLockOn = false; + + /* Finally, subscribe to USB HID notifications in order to prevent LED artifacts on + automatic power management */ + if (darwinUsbHidSubscribeInterestNotifications(pHidState) == 0) + return pHidState; + } + } + + rc = IOHIDManagerClose(pHidState->hidManagerRef, 0); + if (rc != kIOReturnSuccess) + LogRel2(("Warning! Something went wrong in attempt to close HID device manager!\n")); + } + + CFRelease(deviceMatchingDictRef); + } + + CFRelease(pHidState->hidManagerRef); + } + + free(pHidState); + + return NULL; +#else /* !VBOX_WITH_KBD_LEDS_SYNC */ + return NULL; +#endif +} + + +int DarwinHidDevicesApplyAndReleaseLedsState(void *pState) +{ +#ifdef VBOX_WITH_KBD_LEDS_SYNC + VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pState; + IOReturn rc, rc2 = 0; + + AssertReturn(pHidState, kIOReturnError); + + darwinUsbHidUnsubscribeInterestNotifications(pHidState); + + /* Need to unregister Carbon stuff first: */ + darwinRemoveCarbonHandler(pHidState); + + CFDictionaryRef elementMatchingDict = darwinQueryLedElementMatchingDictionary(); + if (elementMatchingDict) + { + /* Restore LEDs: */ + for (CFIndex i = 0; i < CFArrayGetCount(pHidState->pDeviceCollection); i++) + { + /* Cycle through supported devices only. */ + VBoxKbdState_t *pKbd; + pKbd = (VBoxKbdState_t *)CFArrayGetValueAtIndex(pHidState->pDeviceCollection, i); + + if (pKbd) + { + rc = darwinSetDeviceLedsState(pKbd->pDevice, + elementMatchingDict, + pKbd->LED.fNumLockOn, + pKbd->LED.fCapsLockOn, + pKbd->LED.fScrollLockOn); + if (rc != 0) + { + LogRel2(("Unable to restore led states for device (%d)!\n", (int)i)); + rc2 = kIOReturnError; + } + + IOHIDDeviceUnscheduleFromRunLoop(pKbd->pDevice, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + + LogRel2(("Restored LEDs for KBD %d (%p): fNumLockOn=%s, fCapsLockOn=%s, fScrollLockOn=%s\n", + (int)i, pKbd, VBOX_BOOL_TO_STR_STATE(pKbd->LED.fNumLockOn), VBOX_BOOL_TO_STR_STATE(pKbd->LED.fCapsLockOn), + VBOX_BOOL_TO_STR_STATE(pKbd->LED.fScrollLockOn))); + + free(pKbd); + } + } + + CFRelease(elementMatchingDict); + } + + /* Free resources: */ + CFRelease(pHidState->pDeviceCollection); + + rc = IOHIDManagerClose(pHidState->hidManagerRef, 0); + if (rc != kIOReturnSuccess) + { + LogRel2(("Warning! Something went wrong in attempt to close HID device manager!\n")); + rc2 = kIOReturnError; + } + + IOHIDManagerUnscheduleFromRunLoop(pHidState->hidManagerRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); + + CFRelease(pHidState->hidManagerRef); + + free(pHidState); + + return rc2; +#else /* !VBOX_WITH_KBD_LEDS_SYNC */ + (void)pState; + return 0; +#endif /* !VBOX_WITH_KBD_LEDS_SYNC */ +} + +void DarwinHidDevicesBroadcastLeds(void *pState, bool fNumLockOn, bool fCapsLockOn, bool fScrollLockOn) +{ +#ifdef VBOX_WITH_KBD_LEDS_SYNC + VBoxHidsState_t *pHidState = (VBoxHidsState_t *)pState; + IOReturn rc; + + AssertReturnVoid(pHidState); + AssertReturnVoid(pHidState->pDeviceCollection); + + CFDictionaryRef elementMatchingDict = darwinQueryLedElementMatchingDictionary(); + if (elementMatchingDict) + { + LogRel2(("Start LEDs broadcast: fNumLockOn=%s, fCapsLockOn=%s, fScrollLockOn=%s\n", + VBOX_BOOL_TO_STR_STATE(fNumLockOn), VBOX_BOOL_TO_STR_STATE(fCapsLockOn), VBOX_BOOL_TO_STR_STATE(fScrollLockOn))); + + for (CFIndex i = 0; i < CFArrayGetCount(pHidState->pDeviceCollection); i++) + { + /* Cycle through supported devices only. */ + VBoxKbdState_t *pKbd; + pKbd = (VBoxKbdState_t *)CFArrayGetValueAtIndex(pHidState->pDeviceCollection, i); + + if (pKbd && darwinHidDeviceSupported(pKbd->pDevice)) + { + rc = darwinSetDeviceLedsState(pKbd->pDevice, + elementMatchingDict, + fNumLockOn, + fCapsLockOn, + fScrollLockOn); + if (rc != 0) + LogRel2(("Unable to restore led states for device (%d)!\n", (int)i)); + } + } + + LogRel2(("LEDs broadcast completed\n")); + + CFRelease(elementMatchingDict); + } + + /* Dynamically attached device will use these states: */ + pHidState->guestState.fNumLockOn = fNumLockOn; + pHidState->guestState.fCapsLockOn = fCapsLockOn; + pHidState->guestState.fScrollLockOn = fScrollLockOn; +#else /* !VBOX_WITH_KBD_LEDS_SYNC */ + (void)fNumLockOn; + (void)fCapsLockOn; + (void)fScrollLockOn; +#endif /* !VBOX_WITH_KBD_LEDS_SYNC */ +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/DarwinKeyboard.h b/src/VBox/Frontends/VirtualBox/src/platform/darwin/DarwinKeyboard.h new file mode 100644 index 00000000..359b1622 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/DarwinKeyboard.h @@ -0,0 +1,96 @@ +/* $Id: DarwinKeyboard.h $ */ +/** @file + * VBox Qt GUI - Declarations of utility functions for handling Darwin Keyboard specific tasks. + */ + +/* + * 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 FEQT_INCLUDED_SRC_platform_darwin_DarwinKeyboard_h +#define FEQT_INCLUDED_SRC_platform_darwin_DarwinKeyboard_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* GUI includes: */ +#include "UILibraryDefs.h" + +/* Other VBox includes: */ +#include <iprt/cdefs.h> + +/* External includes: */ +#include <CoreFoundation/CFBase.h> + + +RT_C_DECLS_BEGIN + +/** Private hack for missing rightCmdKey enum. */ +#define kEventKeyModifierRightCmdKeyMask (1<<27) + +/** The scancode mask. */ +#define VBOXKEY_SCANCODE_MASK 0x007f +/** Extended key. */ +#define VBOXKEY_EXTENDED 0x0080 +/** Modifier key. */ +#define VBOXKEY_MODIFIER 0x0400 +/** Lock key (like num lock and caps lock). */ +#define VBOXKEY_LOCK 0x0800 + +/** Converts a darwin (virtual) key code to a set 1 scan code. */ +SHARED_LIBRARY_STUFF unsigned DarwinKeycodeToSet1Scancode(unsigned uKeyCode); +/** Adjusts the modifier mask left / right using the current keyboard state. */ +SHARED_LIBRARY_STUFF UInt32 DarwinAdjustModifierMask(UInt32 fModifiers, const void *pvCocoaEvent); +/** Converts a single modifier to a set 1 scan code. */ +SHARED_LIBRARY_STUFF unsigned DarwinModifierMaskToSet1Scancode(UInt32 fModifiers); +/** Converts a single modifier to a darwin keycode. */ +SHARED_LIBRARY_STUFF unsigned DarwinModifierMaskToDarwinKeycode(UInt32 fModifiers); +/** Converts a darwin keycode to a modifier mask. */ +SHARED_LIBRARY_STUFF UInt32 DarwinKeyCodeToDarwinModifierMask(unsigned uKeyCode); + +/** Disables or enabled global hot keys. */ +SHARED_LIBRARY_STUFF void DarwinDisableGlobalHotKeys(bool fDisable); + +/** Start grabbing keyboard events. + * @param fGlobalHotkeys Brings whether to disable global hotkeys or not. */ +SHARED_LIBRARY_STUFF void DarwinGrabKeyboard(bool fGlobalHotkeys); +/** Reverses the actions taken by DarwinGrabKeyboard. */ +SHARED_LIBRARY_STUFF void DarwinReleaseKeyboard(); + +/** Saves the states of leds for all HID devices attached to the system and return it. */ +SHARED_LIBRARY_STUFF void *DarwinHidDevicesKeepLedsState(); + +/** Applies LEDs @a pState release its resources afterwards. */ +SHARED_LIBRARY_STUFF int DarwinHidDevicesApplyAndReleaseLedsState(void *pState); +/** Set states for host keyboard LEDs. + * @note This function will set led values for all + * keyboard devices attached to the system. + * @param pState Brings the pointer to saved LEDs state. + * @param fNumLockOn Turns on NumLock led if TRUE, off otherwise + * @param fCapsLockOn Turns on CapsLock led if TRUE, off otherwise + * @param fScrollLockOn Turns on ScrollLock led if TRUE, off otherwise */ +SHARED_LIBRARY_STUFF void DarwinHidDevicesBroadcastLeds(void *pState, bool fNumLockOn, bool fCapsLockOn, bool fScrollLockOn); + +RT_C_DECLS_END + + +#endif /* !FEQT_INCLUDED_SRC_platform_darwin_DarwinKeyboard_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/DockIconPreview.h b/src/VBox/Frontends/VirtualBox/src/platform/darwin/DockIconPreview.h new file mode 100644 index 00000000..313676f0 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/DockIconPreview.h @@ -0,0 +1,49 @@ +/* $Id: DockIconPreview.h $ */ +/** @file + * VBox Qt GUI - UIDockIconPreview class declaration. + */ + +/* + * 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 + */ + +#ifndef FEQT_INCLUDED_SRC_platform_darwin_DockIconPreview_h +#define FEQT_INCLUDED_SRC_platform_darwin_DockIconPreview_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* GUI includes: */ +#include "UICocoaDockIconPreview.h" + + +/** UICocoaDockIconPreview extension to be used for VM. */ +class UIDockIconPreview : public UICocoaDockIconPreview +{ +public: + + /** Constructor taking passed @a pSession and @a overlayImage. */ + UIDockIconPreview(UISession *pSession, const QPixmap& overlayImage) + : UICocoaDockIconPreview(pSession, overlayImage) {} +}; + +#endif /* !FEQT_INCLUDED_SRC_platform_darwin_DockIconPreview_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/Info.plist b/src/VBox/Frontends/VirtualBox/src/platform/darwin/Info.plist new file mode 100644 index 00000000..44d2d00c --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/Info.plist @@ -0,0 +1,68 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundlePackageType</key> <string>APPL</string> + <key>CFBundleSignature</key> <string>VBOX</string> + <key>CFBundleDevelopmentRegion</key> <string>English</string> + <key>CFBundleIdentifier</key> <string>org.virtualbox.app.VirtualBox</string> + <key>CFBundleName</key> <string>VirtualBox</string> + <key>CFBundleExecutable</key> <string>VirtualBox</string> + <key>CFBundleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleShortVersionString</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleGetInfoString</key> <string>@VBOX_PRODUCT@ Manager @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string> + <key>CFBundleIconFile</key> <string>virtualbox</string> + <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> + <key>LSCanProvideIMVideoDataSource</key> <false/> + <key>NSHighResolutionCapable</key> <true/> + <key>NSSupportsAutomaticGraphicsSwitching</key><true/> + <key>NSCameraUsageDescription</key> <string>VirtualBox needs camera access for emulated webcam passthrough</string> + <key>NSMicrophoneUsageDescription</key> <string>VirtualBox needs microphone access for guest audio input</string> + <key>CFBundleDocumentTypes</key> + <array> + <dict> + <key>CFBundleTypeName</key> <string>VirtualBox Extension Pack</string> + <key>CFBundleTypeExtensions</key> <array><string>vbox-extpack</string></array> + <key>CFBundleTypeRole</key> <string>Viewer</string> + <key>LSHandlerRank</key> <string>Owner</string> + <key>CFBundleTypeIconFile</key> <string>virtualbox-vbox-extpack</string> + </dict> + <dict> + <key>CFBundleTypeName</key> <string>Open Virtualization Format</string> + <key>CFBundleTypeExtensions</key> <array><string>ovf</string></array> + <key>CFBundleTypeRole</key> <string>Viewer</string> + <key>CFBundleTypeIconFile</key> <string>virtualbox-ovf</string> + </dict> + <dict> + <key>CFBundleTypeName</key> <string>Open Virtualization Format Archive</string> + <key>CFBundleTypeExtensions</key> <array><string>ova</string></array> + <key>CFBundleTypeRole</key> <string>Viewer</string> + <key>CFBundleTypeIconFile</key> <string>virtualbox-ova</string> + </dict> + <dict> + <key>CFBundleTypeName</key> <string>Virtual Disk Image</string> + <key>CFBundleTypeExtensions</key> <array><string>vdi</string></array> + <key>CFBundleTypeRole</key> <string>None</string> + <key>CFBundleTypeIconFile</key> <string>virtualbox-vdi</string> + </dict> + <dict> + <key>CFBundleTypeName</key> <string>Virtual Machine Disk Format</string> + <key>CFBundleTypeExtensions</key> <array><string>vmdk</string></array> + <key>CFBundleTypeRole</key> <string>None</string> + <key>CFBundleTypeIconFile</key> <string>virtualbox-vmdk</string> + </dict> + <dict> + <key>CFBundleTypeName</key> <string>Virtual Hard Disk</string> + <key>CFBundleTypeExtensions</key> <array><string>vhd</string></array> + <key>CFBundleTypeRole</key> <string>None</string> + <key>CFBundleTypeIconFile</key> <string>virtualbox-vhd</string> + </dict> + <dict> + <key>CFBundleTypeName</key> <string>Virtual Hard Disk</string> + <key>CFBundleTypeExtensions</key> <array><string>hdd</string></array> + <key>CFBundleTypeRole</key> <string>None</string> + <key>CFBundleTypeIconFile</key> <string>virtualbox-hdd</string> + </dict> + </array> +</dict> +</plist> diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/Makefile.kup b/src/VBox/Frontends/VirtualBox/src/platform/darwin/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/Makefile.kup diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/PkgInfo b/src/VBox/Frontends/VirtualBox/src/platform/darwin/PkgInfo new file mode 100644 index 00000000..30c80e6c --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/PkgInfo @@ -0,0 +1 @@ +APPLVBOX
\ No newline at end of file diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIAbstractDockIconPreview.cpp b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIAbstractDockIconPreview.cpp new file mode 100644 index 00000000..092d86a1 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIAbstractDockIconPreview.cpp @@ -0,0 +1,144 @@ +/* $Id: UIAbstractDockIconPreview.cpp $ */ +/** @file + * VBox Qt GUI - Realtime Dock Icon Preview + */ + +/* + * Copyright (C) 2009-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 + */ + +/* Qt includes: */ +#include <QStyle> + +/* GUI includes: */ +#include "UIAbstractDockIconPreview.h" +#include "UIConverter.h" +#include "UIExtraDataManager.h" +#include "UIFrameBuffer.h" +#include "UIMachineLogic.h" +#include "UIMachineView.h" +#include "UISession.h" +#include "UICommon.h" + +/* COM includes: */ +#include "COMEnums.h" + + +UIAbstractDockIconPreview::UIAbstractDockIconPreview(UISession * /* pSession */, const QPixmap& /* overlayImage */) +{ +} + +void UIAbstractDockIconPreview::updateDockPreview(UIFrameBuffer *pFrameBuffer) +{ + CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); + Assert(cs); + /* Create the image copy of the framebuffer */ + CGDataProviderRef dp = CGDataProviderCreateWithData(pFrameBuffer, pFrameBuffer->address(), pFrameBuffer->bitsPerPixel() / 8 * pFrameBuffer->width() * pFrameBuffer->height(), NULL); + Assert(dp); + CGImageRef ir = CGImageCreate(pFrameBuffer->width(), pFrameBuffer->height(), 8, 32, pFrameBuffer->bytesPerLine(), cs, + kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Host, dp, 0, false, + kCGRenderingIntentDefault); + Assert(ir); + + /* Update the dock preview icon */ + updateDockPreview(ir); + + /* Release the temp data and image */ + CGImageRelease(ir); + CGDataProviderRelease(dp); + CGColorSpaceRelease(cs); +} + +UIAbstractDockIconPreviewHelper::UIAbstractDockIconPreviewHelper(UISession *pSession, const QPixmap& overlayImage) + : m_pSession(pSession) + , m_dockIconRect(CGRectMake(0, 0, 128, 128)) + , m_dockMonitor(NULL) + , m_dockMonitorGlossy(NULL) + , m_updateRect(CGRectMake(0, 0, 0, 0)) + , m_monitorRect(CGRectMake(0, 0, 0, 0)) +{ + m_overlayImage = ::darwinToCGImageRef(&overlayImage); + Assert(m_overlayImage); +} + +void* UIAbstractDockIconPreviewHelper::currentPreviewWindowId() const +{ + /* Get the MachineView which is currently previewed and return the win id + of the viewport. */ + UIMachineView* pView = m_pSession->machineLogic()->dockPreviewView(); + if (pView) + return (void*)pView->viewport()->winId(); + return 0; +} + +UIAbstractDockIconPreviewHelper::~UIAbstractDockIconPreviewHelper() +{ + CGImageRelease(m_overlayImage); + if (m_dockMonitor) + CGImageRelease(m_dockMonitor); + if (m_dockMonitorGlossy) + CGImageRelease(m_dockMonitorGlossy); +} + +void UIAbstractDockIconPreviewHelper::initPreviewImages() +{ + if (!m_dockMonitor) + { + m_dockMonitor = ::darwinToCGImageRef("monitor.png"); + Assert(m_dockMonitor); + /* Center it on the dock icon context */ + m_monitorRect = centerRect(CGRectMake(0, 0, + CGImageGetWidth(m_dockMonitor), + CGImageGetWidth(m_dockMonitor))); + } + + if (!m_dockMonitorGlossy) + { + m_dockMonitorGlossy = ::darwinToCGImageRef("monitor_glossy.png"); + Assert(m_dockMonitorGlossy); + /* This depends on the content of monitor.png */ + m_updateRect = CGRectMake(m_monitorRect.origin.x + 8 /* left-frame */ + 1 /* indent-size */, + m_monitorRect.origin.y + 8 /* top-frame */ + 1 /* indent-size */, + 128 /* .png-width */ - 8 /* left-frame */ - 8 /* right-frame */ - 2 * 1 /* indent-size */, + 128 /* .png-height */ - 8 /* top-frame */ - 25 /* bottom-frame */ - 2 * 1 /* indent-size */); + } +} + +void UIAbstractDockIconPreviewHelper::drawOverlayIcons(CGContextRef context) +{ + /* Determine whether dock icon overlay is not disabled: */ + if (!gEDataManager->dockIconDisableOverlay(uiCommon().managedVMUuid())) + { + /* Initialize overlay rectangle: */ + CGRect overlayRect = CGRectMake(0, 0, 0, 0); + /* Make sure overlay image is valid: */ + if (m_overlayImage) + { + /* Draw overlay image at bottom-right of dock icon: */ + overlayRect = CGRectMake(m_dockIconRect.size.width - CGImageGetWidth(m_overlayImage), + m_dockIconRect.size.height - CGImageGetHeight(m_overlayImage), + CGImageGetWidth(m_overlayImage), + CGImageGetHeight(m_overlayImage)); + CGContextDrawImage(context, flipRect(overlayRect), m_overlayImage); + } + } +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIAbstractDockIconPreview.h b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIAbstractDockIconPreview.h new file mode 100644 index 00000000..cb84b436 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIAbstractDockIconPreview.h @@ -0,0 +1,86 @@ +/* $Id: UIAbstractDockIconPreview.h $ */ +/** @file + * VBox Qt GUI - Abstract class for the dock icon preview. + */ + +/* + * Copyright (C) 2009-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 FEQT_INCLUDED_SRC_platform_darwin_UIAbstractDockIconPreview_h +#define FEQT_INCLUDED_SRC_platform_darwin_UIAbstractDockIconPreview_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* System includes */ +#include <ApplicationServices/ApplicationServices.h> + +/* VBox includes */ +#include "VBoxUtils-darwin.h" + +class UIFrameBuffer; +class UISession; + +class QPixmap; + +class UIAbstractDockIconPreview +{ +public: + UIAbstractDockIconPreview(UISession *pSession, const QPixmap& overlayImage); + virtual ~UIAbstractDockIconPreview() {} + + virtual void updateDockOverlay() = 0; + virtual void updateDockPreview(CGImageRef VMImage) = 0; + virtual void updateDockPreview(UIFrameBuffer *pFrameBuffer); + + virtual void setOriginalSize(int /* aWidth */, int /* aHeight */) {} +}; + +class UIAbstractDockIconPreviewHelper +{ +public: + UIAbstractDockIconPreviewHelper(UISession *pSession, const QPixmap& overlayImage); + virtual ~UIAbstractDockIconPreviewHelper(); + void initPreviewImages(); + void drawOverlayIcons(CGContextRef context); + + void* currentPreviewWindowId() const; + + /* Flipping is necessary cause the drawing context in Mac OS X is flipped by 180 degree */ + inline CGRect flipRect(CGRect rect) const { return ::darwinFlipCGRect(rect, m_dockIconRect); } + inline CGRect centerRect(CGRect rect) const { return ::darwinCenterRectTo(rect, m_dockIconRect); } + inline CGRect centerRectTo(CGRect rect, const CGRect& toRect) const { return ::darwinCenterRectTo(rect, toRect); } + + /* Private member vars */ + UISession *m_pSession; + const CGRect m_dockIconRect; + + CGImageRef m_overlayImage; + CGImageRef m_dockMonitor; + CGImageRef m_dockMonitorGlossy; + + CGRect m_updateRect; + CGRect m_monitorRect; +}; + +#endif /* !FEQT_INCLUDED_SRC_platform_darwin_UIAbstractDockIconPreview_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaApplication.h b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaApplication.h new file mode 100644 index 00000000..5d17cb0d --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaApplication.h @@ -0,0 +1,135 @@ +/* $Id: UICocoaApplication.h $ */ +/** @file + * VBox Qt GUI - UICocoaApplication class declaration. + */ + +/* + * Copyright (C) 2009-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 FEQT_INCLUDED_SRC_platform_darwin_UICocoaApplication_h +#define FEQT_INCLUDED_SRC_platform_darwin_UICocoaApplication_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* Qt includes: */ +#include <QMap> + +/* GUI includes: */ +#include "VBoxCocoaHelper.h" +#include "VBoxUtils-darwin.h" +#include "UILibraryDefs.h" + +/* Forward declarations: */ +class QObject; +class QWidget; + +/* Cocoa declarations: */ +ADD_COCOA_NATIVE_REF(UICocoaApplicationPrivate); +ADD_COCOA_NATIVE_REF(NSAutoreleasePool); +ADD_COCOA_NATIVE_REF(NSString); +ADD_COCOA_NATIVE_REF(NSWindow); +ADD_COCOA_NATIVE_REF(NSButton); + + +/** Event handler callback. */ +typedef bool (*PFNVBOXCACALLBACK)(const void *pvCocoaEvent, const void *pvCarbonEvent, void *pvUser); + +/** Native notification callback type for QObject. */ +typedef void (*PfnNativeNotificationCallbackForQObject)(QObject *pObject, const QMap<QString, QString> &userInfo); +/** Native notification callback type for QWidget. */ +typedef void (*PfnNativeNotificationCallbackForQWidget)(const QString &strNativeNotificationName, QWidget *pWidget); +/** Standard window button callback type for QWidget. */ +typedef void (*PfnStandardWindowButtonCallbackForQWidget)(StandardWindowButtonType emnButtonType, bool fWithOptionKey, QWidget *pWidget); + + +/** Singleton prototype for our private NSApplication object. */ +class SHARED_LIBRARY_STUFF UICocoaApplication +{ +public: + + /** Returns singleton instance. */ + static UICocoaApplication *instance(); + + /** Destructs cocoa application. */ + virtual ~UICocoaApplication(); + + /** Returns whether application is currently active. */ + bool isActive() const; + + /** Hides the application. */ + void hide(); + + /** Hides user elements such as menu-bar and dock. */ + void hideUserElements(); + + /** Register native @a pfnCallback of the @a pvUser taking event @a fMask into account. */ + void registerForNativeEvents(uint32_t fMask, PFNVBOXCACALLBACK pfnCallback, void *pvUser); + /** Unregister native @a pfnCallback of the @a pvUser taking event @a fMask into account. */ + void unregisterForNativeEvents(uint32_t fMask, PFNVBOXCACALLBACK pfnCallback, void *pvUser); + + /** Register passed @a pObject to native notification @a strNativeNotificationName, using @a pCallback as handler. */ + void registerToNotificationOfWorkspace(const QString &strNativeNotificationName, QObject *pObject, PfnNativeNotificationCallbackForQObject pCallback); + /** Unregister passed @a pWidget from native notification @a strNativeNotificationName. */ + void unregisterFromNotificationOfWorkspace(const QString &strNativeNotificationName, QObject *pObject); + + /** Register passed @a pWidget to native notification @a strNativeNotificationName, using @a pCallback as handler. */ + void registerToNotificationOfWindow(const QString &strNativeNotificationName, QWidget *pWidget, PfnNativeNotificationCallbackForQWidget pCallback); + /** Unregister passed @a pWidget from native notification @a strNativeNotificationName. */ + void unregisterFromNotificationOfWindow(const QString &strNativeNotificationName, QWidget *pWidget); + + /** Redirects native notification @a pstrNativeNotificationName for application to registered listener. */ + void nativeNotificationProxyForObject(NativeNSStringRef pstrNativeNotificationName, const QMap<QString, QString> &userInfo); + /** Redirects native notification @a pstrNativeNotificationName for window @a pWindow to registered listener. */ + void nativeNotificationProxyForWidget(NativeNSStringRef pstrNativeNotificationName, NativeNSWindowRef pWindow); + + /** Register callback for standard window @a buttonType of passed @a pWidget as @a pCallback. */ + void registerCallbackForStandardWindowButton(QWidget *pWidget, StandardWindowButtonType enmButtonType, PfnStandardWindowButtonCallbackForQWidget pCallback); + /** Unregister callback for standard window @a buttonType of passed @a pWidget. */ + void unregisterCallbackForStandardWindowButton(QWidget *pWidget, StandardWindowButtonType enmButtonType); + /** Redirects standard window button selector to registered callback. */ + void nativeCallbackProxyForStandardWindowButton(NativeNSButtonRef pButton, bool fWithOptionKey); + +private: + + /** Constructs cocoa application. */ + UICocoaApplication(); + + /** Holds the singleton access instance. */ + static UICocoaApplication *s_pInstance; + + /** Holds the private NSApplication instance. */ + NativeUICocoaApplicationPrivateRef m_pNative; + /** Holds the private NSAutoreleasePool instance. */ + NativeNSAutoreleasePoolRef m_pPool; + + /** Map of notification callbacks registered for corresponding QObject(s). */ + QMap<QObject*, QMap<QString, PfnNativeNotificationCallbackForQObject> > m_objectCallbacks; + /** Map of notification callbacks registered for corresponding QWidget(s). */ + QMap<QWidget*, QMap<QString, PfnNativeNotificationCallbackForQWidget> > m_widgetCallbacks; + + /** Map of callbacks registered for standard window button(s) of corresponding QWidget(s). */ + QMap<QWidget*, QMap<StandardWindowButtonType, PfnStandardWindowButtonCallbackForQWidget> > m_stdWindowButtonCallbacks; +}; + +#endif /* !FEQT_INCLUDED_SRC_platform_darwin_UICocoaApplication_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaApplication.mm b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaApplication.mm new file mode 100644 index 00000000..42196b26 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaApplication.mm @@ -0,0 +1,494 @@ +/* $Id: UICocoaApplication.mm $ */ +/** @file + * VBox Qt GUI - UICocoaApplication class implementation. + */ + +/* + * Copyright (C) 2009-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 + */ + +/* GUI includes: */ +#include "UICocoaApplication.h" + +/* Other VBox includes: */ +#include <iprt/assert.h> + +/* External includes: */ +#import <AppKit/NSApplication.h> +#import <AppKit/NSButton.h> +#import <AppKit/NSEvent.h> +#import <AppKit/NSWindow.h> +#import <Foundation/NSArray.h> + + +/** Class for tracking a callback. */ +@interface CallbackData : NSObject +{ +@public + /** Holds the mask of events to send to this callback. */ + uint32_t fMask; + /** Holds the callback. */ + PFNVBOXCACALLBACK pfnCallback; + /** Holds the user argument. */ + void *pvUser; +} +- (id)initWithMask :(uint32)mask callback :(PFNVBOXCACALLBACK)callback user :(void *)user; +@end /* @interface CallbackData */ + +@implementation CallbackData +/** Performs initialization. */ +- (id)initWithMask :(uint32)mask callback :(PFNVBOXCACALLBACK)callback user :(void *)user +{ + self = [super init]; + if (self) + { + fMask = mask; + pfnCallback = callback; + pvUser = user; + } + return self; +} +@end /* @implementation CallbackData */ + + +/** Class for event handling. */ +@interface UICocoaApplicationPrivate : NSApplication +{ + /** The event mask for which there currently are callbacks. */ + uint32_t m_fMask; + /** Array of callbacks. */ + NSMutableArray *m_pCallbacks; +} +- (id)init; +- (void)sendEvent :(NSEvent *)theEvent; +- (void)setCallback :(uint32_t)fMask :(PFNVBOXCACALLBACK)pfnCallback :(void *)pvUser; +- (void)unsetCallback :(uint32_t)fMask :(PFNVBOXCACALLBACK)pfnCallback :(void *)pvUser; + +- (void)registerToNotificationOfWorkspace :(NSString *)pstrNotificationName; +- (void)unregisterFromNotificationOfWorkspace :(NSString *)pstrNotificationName; + +- (void)registerToNotificationOfWindow :(NSString *)pstrNotificationName :(NSWindow *)pWindow; +- (void)unregisterFromNotificationOfWindow :(NSString *)pstrNotificationName :(NSWindow *)pWindow; + +- (void)notificationCallbackOfObject :(NSNotification *)notification; +- (void)notificationCallbackOfWindow :(NSNotification *)notification; + +- (void)registerSelectorForStandardWindowButton :(NSWindow *)pWindow :(StandardWindowButtonType)enmButtonType; +- (void)selectorForStandardWindowButton :(NSButton *)pButton; +@end /* @interface UICocoaApplicationPrivate */ + +@implementation UICocoaApplicationPrivate +/** Performs initialization. */ +- (id) init +{ + self = [super init]; + if (self) + m_pCallbacks = [[NSMutableArray alloc] init]; + + // WORKAROUND: + // Gently disable El Capitan tries to break everything with the Enter Full Screen action. + // S.a. https://developer.apple.com/library/mac/releasenotes/AppKit/RN-AppKit/ for reference. + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"]; + + return self; +} + +/** Sends an event. + * @param pEvent Brings the event to be sent. */ +- (void) sendEvent :(NSEvent *)pEvent +{ + /* Check if the type matches any of the registered callbacks. */ + uint32_t const fMask = m_fMask; +#if 0 /* for debugging */ + ::darwinPrintEvent("sendEvent: ", pEvent); +#endif + if (fMask != 0) + { + NSEventType EvtType = [pEvent type]; + uint32_t fEvtMask = RT_LIKELY(EvtType < 32) ? RT_BIT_32(EvtType) : 0; + if (fMask & fEvtMask) + { + /* Do the callouts in LIFO order. */ + for (CallbackData *pData in [m_pCallbacks reverseObjectEnumerator]) + { + if (pData->fMask & fEvtMask) + { + if (pData->pfnCallback(pEvent, [pEvent eventRef], pData->pvUser)) + return; + } + + } + } + } + + /* Get on with it. */ + [super sendEvent:pEvent]; +} + +/** Registers an event callback. + * @param fMask Brings the event mask for which the callback is to be invoked. + * @param pfnCallback Brings the callback function. + * @param pvUser Brings the user argument. */ +- (void) setCallback :(uint32_t)fMask :(PFNVBOXCACALLBACK)pfnCallback :(void *)pvUser +{ + /* Add the callback data to the array: */ + CallbackData *pData = [[[CallbackData alloc] initWithMask:fMask callback:pfnCallback user:pvUser] autorelease]; + [m_pCallbacks addObject:pData]; + + /* Update the global mask: */ + m_fMask |= fMask; +} + +/** Deregisters an event callback. + * @param fMask Brings the event mask for which the callback is to be invoked. + * @param pfnCallback Brings the callback function. + * @param pvUser Brings the user argument. */ +- (void) unsetCallback: (uint32_t)fMask :(PFNVBOXCACALLBACK)pfnCallback :(void *)pvUser +{ + /* Loop the event array LIFO fashion searching for a matching callback. */ + for (CallbackData *pData in [m_pCallbacks reverseObjectEnumerator]) + { + if ( pData->pfnCallback == pfnCallback + && pData->pvUser == pvUser + && pData->fMask == fMask) + { + [m_pCallbacks removeObject:pData]; + break; + } + } + uint32_t fNewMask = 0; + for (CallbackData *pData in m_pCallbacks) + fNewMask |= pData->fMask; + m_fMask = fNewMask; +} + +/** Registers to cocoa notification @a pstrNotificationName. */ +- (void) registerToNotificationOfWorkspace :(NSString *)pstrNotificationName +{ + /* Register notification observer: */ + NSNotificationCenter *pNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter]; + [pNotificationCenter addObserver:self + selector:@selector(notificationCallbackOfObject:) + name:pstrNotificationName + object:nil]; +} + +/** Unregister @a pWindow from cocoa notification @a pstrNotificationName. */ +- (void) unregisterFromNotificationOfWorkspace :(NSString *)pstrNotificationName +{ + /* Uninstall notification observer: */ + NSNotificationCenter *pNotificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter]; + [pNotificationCenter removeObserver:self + name:pstrNotificationName + object:nil]; +} + +/** Register @a pWindow to cocoa notification @a pstrNotificationName. */ +- (void) registerToNotificationOfWindow :(NSString *)pstrNotificationName :(NSWindow *)pWindow +{ + /* Register notification observer: */ + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(notificationCallbackOfWindow:) + name:pstrNotificationName + object:pWindow]; +} + +/** Unregister @a pWindow from cocoa notification @a pstrNotificationName. */ +- (void) unregisterFromNotificationOfWindow :(NSString *)pstrNotificationName :(NSWindow *)pWindow +{ + /* Uninstall notification observer: */ + [[NSNotificationCenter defaultCenter] removeObserver:self + name:pstrNotificationName + object:pWindow]; +} + +/** Redirects cocoa @a notification to UICocoaApplication instance. */ +- (void) notificationCallbackOfObject :(NSNotification *)notification +{ + /* Get current notification name: */ + NSString *pstrName = [notification name]; + + /* Prepare user-info: */ + QMap<QString, QString> userInfo; + + /* Process known notifications: */ + if ( [pstrName isEqualToString :@"NSWorkspaceDidActivateApplicationNotification"] + || [pstrName isEqualToString :@"NSWorkspaceDidDeactivateApplicationNotification"]) + { + NSDictionary *pUserInfo = [notification userInfo]; + NSRunningApplication *pApplication = [pUserInfo valueForKey :@"NSWorkspaceApplicationKey"]; + NSString *pstrBundleIndentifier = [pApplication bundleIdentifier]; + userInfo.insert("BundleIdentifier", darwinFromNativeString((NativeNSStringRef)pstrBundleIndentifier)); + } + + /* Redirect known notifications to objects: */ + UICocoaApplication::instance()->nativeNotificationProxyForObject(pstrName, userInfo); +} + +/** Redirects cocoa @a notification to UICocoaApplication instance. */ +- (void) notificationCallbackOfWindow :(NSNotification *)notification +{ + /* Get current notification name: */ + NSString *pstrName = [notification name]; + + /* Redirect known notifications to widgets: */ + UICocoaApplication::instance()->nativeNotificationProxyForWidget(pstrName, [notification object]); +} + +/** Registers selector for standard window @a enmButtonType of the passed @a pWindow. */ +- (void)registerSelectorForStandardWindowButton :(NSWindow *)pWindow :(StandardWindowButtonType)enmButtonType +{ + /* Retrieve corresponding button: */ + NSButton *pButton = Nil; + switch (enmButtonType) + { + case StandardWindowButtonType_Close: pButton = [pWindow standardWindowButton:NSWindowCloseButton]; break; + case StandardWindowButtonType_Miniaturize: pButton = [pWindow standardWindowButton:NSWindowMiniaturizeButton]; break; + case StandardWindowButtonType_Zoom: pButton = [pWindow standardWindowButton:NSWindowZoomButton]; break; + case StandardWindowButtonType_Toolbar: pButton = [pWindow standardWindowButton:NSWindowToolbarButton]; break; + case StandardWindowButtonType_DocumentIcon: pButton = [pWindow standardWindowButton:NSWindowDocumentIconButton]; break; + case StandardWindowButtonType_DocumentVersions: /*pButton = [pWindow standardWindowButton:NSWindowDocumentVersionsButton];*/ break; + case StandardWindowButtonType_FullScreen: /*pButton = [pWindow standardWindowButton:NSWindowFullScreenButton];*/ break; + } + + /* Register selector if button exists: */ + if (pButton != Nil) + { + [pButton setTarget:self]; + [pButton setAction:@selector(selectorForStandardWindowButton:)]; + } +} + +/** Redirects selector of the standard window @a pButton to UICocoaApplication instance callback. */ +- (void)selectorForStandardWindowButton :(NSButton *)pButton +{ + /* Check if Option key is currently held: */ + const bool fWithOptionKey = [NSEvent modifierFlags] & NSAlternateKeyMask; + + /* Redirect selector to callback: */ + UICocoaApplication::instance()->nativeCallbackProxyForStandardWindowButton(pButton, fWithOptionKey); +} +@end /* @implementation UICocoaApplicationPrivate */ + + +/********************************************************************************************************************************* +* Class UICocoaApplication implementation. * +*********************************************************************************************************************************/ + +/* static */ +UICocoaApplication* UICocoaApplication::s_pInstance = 0; + +/* static */ +UICocoaApplication* UICocoaApplication::instance() +{ + if (!s_pInstance) + s_pInstance = new UICocoaApplication; + + return s_pInstance; +} + +UICocoaApplication::UICocoaApplication() +{ + /* Make sure our private NSApplication object is created: */ + m_pNative = (UICocoaApplicationPrivate*)[UICocoaApplicationPrivate sharedApplication]; + // WORKAROUND": + // Create one auto release pool which is in place for all the + // initialization and deinitialization stuff. That is when the + // NSApplication is not running the run loop (there is a separate + // auto release pool defined). + m_pPool = [[NSAutoreleasePool alloc] init]; +} + +UICocoaApplication::~UICocoaApplication() +{ + [m_pNative release]; + [m_pPool release]; +} + +bool UICocoaApplication::isActive() const +{ + return [m_pNative isActive]; +} + +void UICocoaApplication::hide() +{ + [m_pNative hide:m_pNative]; +} + +void UICocoaApplication::hideUserElements() +{ + [m_pNative setPresentationOptions:NSApplicationPresentationHideMenuBar | NSApplicationPresentationHideDock]; +} + +void UICocoaApplication::registerForNativeEvents(uint32_t fMask, PFNVBOXCACALLBACK pfnCallback, void *pvUser) +{ + [m_pNative setCallback:fMask :pfnCallback :pvUser]; +} + +void UICocoaApplication::unregisterForNativeEvents(uint32_t fMask, PFNVBOXCACALLBACK pfnCallback, void *pvUser) +{ + [m_pNative unsetCallback:fMask :pfnCallback :pvUser]; +} + +void UICocoaApplication::registerToNotificationOfWorkspace(const QString &strNativeNotificationName, QObject *pObject, + PfnNativeNotificationCallbackForQObject pCallback) +{ + /* Make sure it is not registered yet: */ + AssertReturnVoid(!m_objectCallbacks.contains(pObject) || !m_objectCallbacks[pObject].contains(strNativeNotificationName)); + + /* Remember callback: */ + m_objectCallbacks[pObject][strNativeNotificationName] = pCallback; + + /* Register observer: */ + NativeNSStringRef pstrNativeNotificationName = darwinToNativeString(strNativeNotificationName.toLatin1().constData()); + [m_pNative registerToNotificationOfWorkspace :pstrNativeNotificationName]; +} + +void UICocoaApplication::unregisterFromNotificationOfWorkspace(const QString &strNativeNotificationName, QObject *pObject) +{ + /* Make sure it is registered yet: */ + AssertReturnVoid(m_objectCallbacks.contains(pObject) && m_objectCallbacks[pObject].contains(strNativeNotificationName)); + + /* Forget callback: */ + m_objectCallbacks[pObject].remove(strNativeNotificationName); + if (m_objectCallbacks[pObject].isEmpty()) + m_objectCallbacks.remove(pObject); + + /* Unregister observer: */ + NativeNSStringRef pstrNativeNotificationName = darwinToNativeString(strNativeNotificationName.toLatin1().constData()); + [m_pNative unregisterFromNotificationOfWorkspace :pstrNativeNotificationName]; +} + +void UICocoaApplication::registerToNotificationOfWindow(const QString &strNativeNotificationName, QWidget *pWidget, + PfnNativeNotificationCallbackForQWidget pCallback) +{ + /* Make sure it is not registered yet: */ + AssertReturnVoid(!m_widgetCallbacks.contains(pWidget) || !m_widgetCallbacks[pWidget].contains(strNativeNotificationName)); + + /* Remember callback: */ + m_widgetCallbacks[pWidget][strNativeNotificationName] = pCallback; + + /* Register observer: */ + NativeNSStringRef pstrNativeNotificationName = darwinToNativeString(strNativeNotificationName.toLatin1().constData()); + NativeNSWindowRef pWindow = darwinToNativeWindow(pWidget); + [m_pNative registerToNotificationOfWindow :pstrNativeNotificationName :pWindow]; +} + +void UICocoaApplication::unregisterFromNotificationOfWindow(const QString &strNativeNotificationName, QWidget *pWidget) +{ + /* Make sure it is registered yet: */ + AssertReturnVoid(m_widgetCallbacks.contains(pWidget) && m_widgetCallbacks[pWidget].contains(strNativeNotificationName)); + + /* Forget callback: */ + m_widgetCallbacks[pWidget].remove(strNativeNotificationName); + if (m_widgetCallbacks[pWidget].isEmpty()) + m_widgetCallbacks.remove(pWidget); + + /* Unregister observer: */ + NativeNSStringRef pstrNativeNotificationName = darwinToNativeString(strNativeNotificationName.toLatin1().constData()); + NativeNSWindowRef pWindow = darwinToNativeWindow(pWidget); + [m_pNative unregisterFromNotificationOfWindow :pstrNativeNotificationName :pWindow]; +} + +void UICocoaApplication::nativeNotificationProxyForObject(NativeNSStringRef pstrNotificationName, + const QMap<QString, + QString> &userInfo) +{ + /* Get notification name: */ + QString strNotificationName = darwinFromNativeString(pstrNotificationName); + + /* Check if existing object(s) have corresponding notification handler: */ + foreach (QObject *pObject, m_objectCallbacks.keys()) + { + const QMap<QString, PfnNativeNotificationCallbackForQObject> &callbacks = m_objectCallbacks[pObject]; + if (callbacks.contains(strNotificationName)) + callbacks[strNotificationName](pObject, userInfo); + } +} + +void UICocoaApplication::nativeNotificationProxyForWidget(NativeNSStringRef pstrNotificationName, NativeNSWindowRef pWindow) +{ + /* Get notification name: */ + QString strNotificationName = darwinFromNativeString(pstrNotificationName); + + /* Check if existing widget(s) have corresponding notification handler: */ + foreach (QWidget *pWidget, m_widgetCallbacks.keys()) + { + if (darwinToNativeWindow(pWidget) == pWindow) + { + const QMap<QString, PfnNativeNotificationCallbackForQWidget> &callbacks = m_widgetCallbacks[pWidget]; + if (callbacks.contains(strNotificationName)) + callbacks[strNotificationName](strNotificationName, pWidget); + } + } +} + +void UICocoaApplication::registerCallbackForStandardWindowButton(QWidget *pWidget, StandardWindowButtonType enmButtonType, + PfnStandardWindowButtonCallbackForQWidget pCallback) +{ + /* Make sure it is not registered yet: */ + AssertReturnVoid( !m_stdWindowButtonCallbacks.contains(pWidget) + || !m_stdWindowButtonCallbacks.value(pWidget).contains(enmButtonType)); + + /* Remember callback: */ + m_stdWindowButtonCallbacks[pWidget][enmButtonType] = pCallback; + + /* Register selector: */ + NativeNSWindowRef pWindow = darwinToNativeWindow(pWidget); + [m_pNative registerSelectorForStandardWindowButton :pWindow :enmButtonType]; +} + +void UICocoaApplication::unregisterCallbackForStandardWindowButton(QWidget *pWidget, StandardWindowButtonType enmButtonType) +{ + /* Make sure it is registered yet: */ + AssertReturnVoid( m_stdWindowButtonCallbacks.contains(pWidget) + && m_stdWindowButtonCallbacks.value(pWidget).contains(enmButtonType)); + + /* Forget callback: */ + m_stdWindowButtonCallbacks[pWidget].remove(enmButtonType); + if (m_stdWindowButtonCallbacks.value(pWidget).isEmpty()) + m_stdWindowButtonCallbacks.remove(pWidget); +} + +void UICocoaApplication::nativeCallbackProxyForStandardWindowButton(NativeNSButtonRef pButton, bool fWithOptionKey) +{ + // WORKAROUND: + // Why not using nested foreach, will you ask? + // It's because Qt 4.x has shadowing issue in Q_FOREACH macro. + // Bug record QTBUG-33585 opened for Qt 4.8.4 and closed as _won't fix_ by one of Qt devs. + + /* Check if passed button is one of the buttons of the registered widget(s): */ + const QList<QWidget*> widgets = m_stdWindowButtonCallbacks.keys(); + for (int iWidgetIndex = 0; iWidgetIndex < widgets.size(); ++iWidgetIndex) + { + QWidget *pWidget = widgets.at(iWidgetIndex); + const QMap<StandardWindowButtonType, PfnStandardWindowButtonCallbackForQWidget> callbacks + = m_stdWindowButtonCallbacks.value(pWidget); + const QList<StandardWindowButtonType> buttonTypes = callbacks.keys(); + for (int iButtonTypeIndex = 0; iButtonTypeIndex < buttonTypes.size(); ++iButtonTypeIndex) + { + StandardWindowButtonType enmButtonType = buttonTypes.at(iButtonTypeIndex); + if (darwinNativeButtonOfWindow(pWidget, enmButtonType) == pButton) + return callbacks.value(enmButtonType)(enmButtonType, fWithOptionKey, pWidget); + } + } +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaDockIconPreview.h b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaDockIconPreview.h new file mode 100644 index 00000000..cd401f4a --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaDockIconPreview.h @@ -0,0 +1,56 @@ +/* $Id: UICocoaDockIconPreview.h $ */ +/** @file + * VBox Qt GUI - UICocoaDockIconPreview class declaration. + */ + +/* + * Copyright (C) 2009-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 FEQT_INCLUDED_SRC_platform_darwin_UICocoaDockIconPreview_h +#define FEQT_INCLUDED_SRC_platform_darwin_UICocoaDockIconPreview_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* Qt includes */ +#include "UIAbstractDockIconPreview.h" + +class UICocoaDockIconPreviewPrivate; + +class UICocoaDockIconPreview: public UIAbstractDockIconPreview +{ +public: + UICocoaDockIconPreview(UISession *pSession, const QPixmap& overlayImage); + ~UICocoaDockIconPreview(); + + virtual void updateDockOverlay(); + virtual void updateDockPreview(CGImageRef VMImage); + virtual void updateDockPreview(UIFrameBuffer *pFrameBuffer); + + virtual void setOriginalSize(int aWidth, int aHeight); + +private: + UICocoaDockIconPreviewPrivate *d; +}; + +#endif /* !FEQT_INCLUDED_SRC_platform_darwin_UICocoaDockIconPreview_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaDockIconPreview.mm b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaDockIconPreview.mm new file mode 100644 index 00000000..baba2a4b --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaDockIconPreview.mm @@ -0,0 +1,342 @@ +/* $Id: UICocoaDockIconPreview.mm $ */ +/** @file + * VBox Qt GUI - Cocoa helper for the dock icon preview. + */ + +/* + * Copyright (C) 2009-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 + */ + +/* VBox includes */ +#include "UICocoaDockIconPreview.h" +#include "VBoxCocoaHelper.h" + +/* System includes */ +#import <Cocoa/Cocoa.h> + +@interface UIDockTileMonitor: NSView +{ + UICocoaDockIconPreviewPrivate *p; + + NSImageView *mScreenContent; + NSImageView *mMonitorGlossy; +} +- (id)initWithFrame:(NSRect)frame parent:(UICocoaDockIconPreviewPrivate*)parent; +- (NSImageView*)screenContent; +- (void)resize:(NSSize)size; +@end + +@interface UIDockTileOverlay: NSView +{ + UICocoaDockIconPreviewPrivate *p; +} +- (id)initWithFrame:(NSRect)frame parent:(UICocoaDockIconPreviewPrivate*)parent; +@end + +@interface UIDockTile: NSView +{ + UICocoaDockIconPreviewPrivate *p; + + UIDockTileMonitor *mMonitor; + NSImageView *mAppIcon; + + UIDockTileOverlay *mOverlay; +} +- (id)initWithParent:(UICocoaDockIconPreviewPrivate*)parent; +- (void)destroy; +- (NSView*)screenContentWithParentView:(NSView*)parentView; +- (void)cleanup; +- (void)restoreAppIcon; +- (void)updateAppIcon; +- (void)restoreMonitor; +- (void)updateMonitorWithImage:(CGImageRef)image; +- (void)resizeMonitor:(NSSize)size; +@end + +/* + * Helper class which allow us to access all members/methods of AbstractDockIconPreviewHelper + * from any Cocoa class. + */ +class UICocoaDockIconPreviewPrivate: public UIAbstractDockIconPreviewHelper +{ +public: + inline UICocoaDockIconPreviewPrivate(UISession *pSession, const QPixmap& overlayImage) + :UIAbstractDockIconPreviewHelper(pSession, overlayImage) + { + mUIDockTile = [[UIDockTile alloc] initWithParent:this]; + } + + inline ~UICocoaDockIconPreviewPrivate() + { + + [mUIDockTile destroy]; + [mUIDockTile release]; + } + + UIDockTile *mUIDockTile; +}; + +/* + * Cocoa wrapper for the abstract dock icon preview class + */ +UICocoaDockIconPreview::UICocoaDockIconPreview(UISession *pSession, const QPixmap& overlayImage) + : UIAbstractDockIconPreview(pSession, overlayImage) +{ + CocoaAutoreleasePool pool; + + d = new UICocoaDockIconPreviewPrivate(pSession, overlayImage); +} + +UICocoaDockIconPreview::~UICocoaDockIconPreview() +{ + CocoaAutoreleasePool pool; + + delete d; +} + +void UICocoaDockIconPreview::updateDockOverlay() +{ + CocoaAutoreleasePool pool; + + [d->mUIDockTile updateAppIcon]; +} + +void UICocoaDockIconPreview::updateDockPreview(CGImageRef VMImage) +{ + CocoaAutoreleasePool pool; + + [d->mUIDockTile updateMonitorWithImage:VMImage]; +} + +void UICocoaDockIconPreview::updateDockPreview(UIFrameBuffer *pFrameBuffer) +{ + CocoaAutoreleasePool pool; + + UIAbstractDockIconPreview::updateDockPreview(pFrameBuffer); +} + +void UICocoaDockIconPreview::setOriginalSize(int width, int height) +{ + CocoaAutoreleasePool pool; + + [d->mUIDockTile resizeMonitor:NSMakeSize(width, height)]; +} + +/* + * Class for arranging/updating the layers for the glossy monitor preview. + */ +@implementation UIDockTileMonitor +- (id)initWithFrame:(NSRect)frame parent:(UICocoaDockIconPreviewPrivate*)parent +{ + self = [super initWithFrame:frame]; + + if (self != nil) + { + p = parent; + /* The screen content view */ + mScreenContent = [[NSImageView alloc] initWithFrame:NSRectFromCGRect(p->flipRect(p->m_updateRect))]; +// [mScreenContent setImageAlignment: NSImageAlignCenter]; + [mScreenContent setImageAlignment: NSImageAlignTopLeft]; + [mScreenContent setImageScaling: NSImageScaleAxesIndependently]; + [self addSubview: mScreenContent]; + /* The state view */ + mMonitorGlossy = [[NSImageView alloc] initWithFrame:NSRectFromCGRect(p->flipRect(p->m_monitorRect))]; + [mMonitorGlossy setImage: ::darwinToNSImageRef(p->m_dockMonitorGlossy)]; + [self addSubview: mMonitorGlossy]; + } + + return self; +} + +- (void)drawRect:(NSRect)aRect +{ + NSImage *dockMonitor = ::darwinToNSImageRef(p->m_dockMonitor); +#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101200 + [dockMonitor drawInRect:NSRectFromCGRect(p->flipRect(p->m_monitorRect)) fromRect:aRect operation:NSCompositingOperationSourceOver fraction:1.0]; +#else + [dockMonitor drawInRect:NSRectFromCGRect(p->flipRect(p->m_monitorRect)) fromRect:aRect operation:NSCompositeSourceOver fraction:1.0]; +#endif + [dockMonitor release]; +} + +- (NSImageView*)screenContent +{ + return mScreenContent; +} + +- (void)resize:(NSSize)size +{ + /* Calculate the new size based on the aspect ratio of the original screen + size */ + float w, h; + if (size.width > size.height) + { + w = p->m_updateRect.size.width; + h = ((float)size.height / size.width * p->m_updateRect.size.height); + } + else + { + w = ((float)size.width / size.height * p->m_updateRect.size.width); + h = p->m_updateRect.size.height; + } + CGRect r = (p->flipRect (p->centerRectTo (CGRectMake (0, 0, (int)w, (int)h), p->m_updateRect))); + r.origin.x = (int)r.origin.x; + r.origin.y = (int)r.origin.y; + r.size.width = (int)r.size.width; + r.size.height = (int)r.size.height; +// printf("gui %f %f %f %f\n", r.origin.x, r.origin.y, r.size.width, r.size.height); + /* Center within the update rect */ + [mScreenContent setFrame:NSRectFromCGRect (r)]; +} +@end + +/* + * Simple implementation for the overlay of the OS & the state icon. Is used both + * in the application icon & preview mode. + */ +@implementation UIDockTileOverlay +- (id)initWithFrame:(NSRect)frame parent:(UICocoaDockIconPreviewPrivate*)parent +{ + self = [super initWithFrame:frame]; + + if (self != nil) + p = parent; + + return self; +} + +- (void)drawRect:(NSRect)aRect +{ + RT_NOREF(aRect); + NSGraphicsContext *nsContext = [NSGraphicsContext currentContext]; + CGContextRef pCGContext = (CGContextRef)[nsContext graphicsPort]; + p->drawOverlayIcons (pCGContext); +} +@end + +/* + * VirtualBox Dock Tile implementation. Manage the switching between the icon + * and preview mode & forwards all update request to the appropriate methods. + */ +@implementation UIDockTile +- (id)initWithParent:(UICocoaDockIconPreviewPrivate*)parent +{ + self = [super init]; + + if (self != nil) + { + p = parent; + /* Add self as the content view of the dock tile */ + NSDockTile *dock = [[NSApplication sharedApplication] dockTile]; + [dock setContentView: self]; + /* App icon is default */ + [self restoreAppIcon]; + /* The overlay */ + mOverlay = [[UIDockTileOverlay alloc] initWithFrame:NSRectFromCGRect(p->flipRect (p->m_dockIconRect)) parent:p]; + [self addSubview: mOverlay]; + } + + return self; +} + +- (void)destroy +{ + /* Remove all content from the application dock tile. */ + [mOverlay removeFromSuperview]; + [mOverlay release]; + mOverlay = nil; + NSDockTile *dock = [[NSApplication sharedApplication] dockTile]; + [dock setContentView: nil]; + /* Cleanup all other resources */ + [self cleanup]; +} + +- (NSView*)screenContentWithParentView:(NSView*)parentView +{ + if (mMonitor != nil) + { + void *pId = p->currentPreviewWindowId(); + if (parentView == pId) + return [mMonitor screenContent]; + } + return nil; +} + +- (void)cleanup +{ + if (mAppIcon != nil) + { + [mAppIcon removeFromSuperview]; + [mAppIcon release]; + mAppIcon = nil; + } + if (mMonitor != nil) + { + [mMonitor removeFromSuperview]; + [mMonitor release]; + mMonitor = nil; + } +} + +- (void)restoreAppIcon +{ + if (mAppIcon == nil) + { + [self cleanup]; + mAppIcon = [[NSImageView alloc] initWithFrame:NSRectFromCGRect (p->flipRect (p->m_dockIconRect))]; + [mAppIcon setImage: [NSImage imageNamed:@"NSApplicationIcon"]]; + [self addSubview: mAppIcon positioned:NSWindowBelow relativeTo:mOverlay]; + } +} + +- (void)updateAppIcon +{ + [self restoreAppIcon]; + [[[NSApplication sharedApplication] dockTile] display]; +} + +- (void)restoreMonitor +{ + if (mMonitor == nil) + { + p->initPreviewImages(); + [self cleanup]; + mMonitor = [[UIDockTileMonitor alloc] initWithFrame:NSRectFromCGRect (p->flipRect (p->m_dockIconRect)) parent:p]; + [self addSubview: mMonitor positioned:NSWindowBelow relativeTo:mOverlay]; + } +} + +- (void)updateMonitorWithImage:(CGImageRef)image +{ + [self restoreMonitor]; + NSImage *nsimage = ::darwinToNSImageRef(image); + [[mMonitor screenContent] setImage: nsimage]; + [nsimage release]; + [[[NSApplication sharedApplication] dockTile] display]; +} + +- (void)resizeMonitor:(NSSize)size +{ + [self restoreMonitor]; + [mMonitor resize:size]; +} +@end + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaSpecialControls.h b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaSpecialControls.h new file mode 100644 index 00000000..773ee8c9 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaSpecialControls.h @@ -0,0 +1,99 @@ +/* $Id: UICocoaSpecialControls.h $ */ +/** @file + * VBox Qt GUI - UICocoaSpecialControls class declaration. + */ + +/* + * Copyright (C) 2009-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 FEQT_INCLUDED_SRC_platform_darwin_UICocoaSpecialControls_h +#define FEQT_INCLUDED_SRC_platform_darwin_UICocoaSpecialControls_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif +#ifdef VBOX_DARWIN_USE_NATIVE_CONTROLS + +/* Qt includes: */ +#include <QWidget> +#ifndef VBOX_IS_QT6_OR_LATER /** @todo qt6: ... */ +# include <QMacCocoaViewContainer> +#endif + +/* GUI includes: */ +#include "VBoxCocoaHelper.h" +#include "UILibraryDefs.h" + +/* Add typedefs for Cocoa types: */ +ADD_COCOA_NATIVE_REF(NSButton); + +/** QMacCocoaViewContainer extension, + * used as cocoa button container. */ +class SHARED_LIBRARY_STUFF UICocoaButton +#ifdef VBOX_IS_QT6_OR_LATER /** @todo qt6: ... */ + : public QWidget +#else + : public QMacCocoaViewContainer +#endif +{ + Q_OBJECT + +signals: + + /** Notifies about button click and whether it's @a fChecked. */ + void clicked(bool fChecked = false); + +public: + + /** Cocoa button types. */ + enum CocoaButtonType + { + HelpButton, + CancelButton, + ResetButton + }; + + /** Constructs cocoa button passing @a pParent to the base-class. + * @param enmType Brings the button type. */ + UICocoaButton(QWidget *pParent, CocoaButtonType enmType); + /** Destructs cocoa button. */ + ~UICocoaButton(); + + /** Returns size-hint. */ + QSize sizeHint() const; + + /** Defines button @a strText. */ + void setText(const QString &strText); + /** Defines button @a strToolTip. */ + void setToolTip(const QString &strToolTip); + + /** Handles button click. */ + void onClicked(); + +private: + + /** Returns native cocoa button reference. */ + NativeNSButtonRef nativeRef() const { return static_cast<NativeNSButtonRef>(cocoaView()); } +}; + +#endif /* VBOX_DARWIN_USE_NATIVE_CONTROLS */ +#endif /* !FEQT_INCLUDED_SRC_platform_darwin_UICocoaSpecialControls_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaSpecialControls.mm b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaSpecialControls.mm new file mode 100644 index 00000000..b83b2e15 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UICocoaSpecialControls.mm @@ -0,0 +1,179 @@ +/* $Id: UICocoaSpecialControls.mm $ */ +/** @file + * VBox Qt GUI - UICocoaSpecialControls implementation. + */ + +/* + * Copyright (C) 2009-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 + */ + +#ifdef VBOX_DARWIN_USE_NATIVE_CONTROLS + +/* Qt includes: */ +#include <QMacCocoaViewContainer> + +/* GUI includes: */ +#include "VBoxUtils-darwin.h" +#include "UICocoaSpecialControls.h" + +/* System includes: */ +#import <AppKit/NSButton.h> + + +/** Private button-target interface. */ +@interface UIButtonTargetPrivate : NSObject +{ + UICocoaButton *m_pRealTarget; +} +// WORKAROUND: +// The next method used to be called initWithObject, but Xcode 4.1 preview 5 +// cannot cope with that for some reason. Hope this doesn't break anything. +-(id)initWithObjectAndLionTrouble:(UICocoaButton*)pObject; +-(IBAction)clicked:(id)pSender; +@end + + +/********************************************************************************************************************************* +* Class UIButtonTargetPrivate implementation. * +*********************************************************************************************************************************/ + +@implementation UIButtonTargetPrivate +-(id)initWithObjectAndLionTrouble:(UICocoaButton*)pObject +{ + self = [super init]; + + m_pRealTarget = pObject; + + return self; +} + +-(IBAction)clicked:(id)pSender +{ + Q_UNUSED(pSender); + m_pRealTarget->onClicked(); +} +@end + + +/********************************************************************************************************************************* +* Class UICocoaButton implementation. * +*********************************************************************************************************************************/ + +UICocoaButton::UICocoaButton(QWidget *pParent, CocoaButtonType enmType) + : QMacCocoaViewContainer(0, pParent) +{ + /* Prepare auto-release pool: */ + NSAutoreleasePool *pPool = [[NSAutoreleasePool alloc] init]; + + /* Prepare native button reference: */ + NativeNSButtonRef pNativeRef; + NSRect initFrame; + + /* Configure button: */ + switch (enmType) + { + case HelpButton: + { + pNativeRef = [[NSButton alloc] init]; + [pNativeRef setTitle: @""]; + [pNativeRef setBezelStyle: NSHelpButtonBezelStyle]; + [pNativeRef setBordered: YES]; + [pNativeRef setAlignment: NSCenterTextAlignment]; + [pNativeRef sizeToFit]; + initFrame = [pNativeRef frame]; + initFrame.size.width += 12; /* Margin */ + [pNativeRef setFrame:initFrame]; + break; + }; + case CancelButton: + { + pNativeRef = [[NSButton alloc] initWithFrame: NSMakeRect(0, 0, 13, 13)]; + [pNativeRef setTitle: @""]; + [pNativeRef setBezelStyle:NSShadowlessSquareBezelStyle]; + [pNativeRef setButtonType:NSMomentaryChangeButton]; + [pNativeRef setImage: [NSImage imageNamed: NSImageNameStopProgressFreestandingTemplate]]; + [pNativeRef setBordered: NO]; + [[pNativeRef cell] setImageScaling: NSImageScaleProportionallyDown]; + initFrame = [pNativeRef frame]; + break; + } + case ResetButton: + { + pNativeRef = [[NSButton alloc] initWithFrame: NSMakeRect(0, 0, 13, 13)]; + [pNativeRef setTitle: @""]; + [pNativeRef setBezelStyle:NSShadowlessSquareBezelStyle]; + [pNativeRef setButtonType:NSMomentaryChangeButton]; + [pNativeRef setImage: [NSImage imageNamed: NSImageNameRefreshFreestandingTemplate]]; + [pNativeRef setBordered: NO]; + [[pNativeRef cell] setImageScaling: NSImageScaleProportionallyDown]; + initFrame = [pNativeRef frame]; + break; + } + } + + /* Install click listener: */ + UIButtonTargetPrivate *bt = [[UIButtonTargetPrivate alloc] initWithObjectAndLionTrouble:this]; + [pNativeRef setTarget:bt]; + [pNativeRef setAction:@selector(clicked:)]; + + /* Put the button to the QCocoaViewContainer: */ + setCocoaView(pNativeRef); + /* Release our reference, since our super class + * takes ownership and we don't need it anymore. */ + [pNativeRef release]; + + /* Finally resize the widget: */ + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + setFixedSize(NSWidth(initFrame), NSHeight(initFrame)); + + /* Cleanup auto-release pool: */ + [pPool release]; +} + +UICocoaButton::~UICocoaButton() +{ +} + +QSize UICocoaButton::sizeHint() const +{ + NSRect frame = [nativeRef() frame]; + return QSize(frame.size.width, frame.size.height); +} + +void UICocoaButton::setText(const QString &strText) +{ + QString s(strText); + /* Set it for accessibility reasons as alternative title: */ + [nativeRef() setAlternateTitle: ::darwinQStringToNSString(s.remove('&'))]; +} + +void UICocoaButton::setToolTip(const QString &strToolTip) +{ + [nativeRef() setToolTip: ::darwinQStringToNSString(strToolTip)]; +} + +void UICocoaButton::onClicked() +{ + emit clicked(false); +} + +#endif /* VBOX_DARWIN_USE_NATIVE_CONTROLS */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIDesktopServices_darwin.cpp b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIDesktopServices_darwin.cpp new file mode 100644 index 00000000..ca0ba697 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIDesktopServices_darwin.cpp @@ -0,0 +1,48 @@ +/* $Id: UIDesktopServices_darwin.cpp $ */ +/** @file + * VBox Qt GUI - Qt GUI - Utility Classes and Functions specific to darwin.. + */ + +/* + * 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 + */ + +/* VBox includes */ +#include "UIDesktopServices.h" +#include "UIDesktopServices_darwin_p.h" +#include "VBoxUtils-darwin.h" + +/* Qt includes */ +#include <QString> + +bool UIDesktopServices::createMachineShortcut(const QString &strSrcFile, const QString &strDstPath, const QString &strName, const QUuid &uUuid) +{ + return ::darwinCreateMachineShortcut(::darwinToNativeString(strSrcFile.toUtf8().constData()), + ::darwinToNativeString(strDstPath.toUtf8().constData()), + ::darwinToNativeString(strName.toUtf8().constData()), + ::darwinToNativeString(uUuid.toString().toUtf8().constData())); +} + +bool UIDesktopServices::openInFileManager(const QString &strFile) +{ + return ::darwinOpenInFileManager(::darwinToNativeString(strFile.toUtf8().constData())); +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIDesktopServices_darwin_cocoa.mm b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIDesktopServices_darwin_cocoa.mm new file mode 100644 index 00000000..95ea82d8 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIDesktopServices_darwin_cocoa.mm @@ -0,0 +1,74 @@ +/* $Id: UIDesktopServices_darwin_cocoa.mm $ */ +/** @file + * VBox Qt GUI - Qt GUI - Utility Classes and Functions specific to darwin. + */ + +/* + * 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 + */ + +/* VBox includes */ +#include "UIDesktopServices_darwin_p.h" + +/* System includes */ +#include <Carbon/Carbon.h> +#import <AppKit/NSWorkspace.h> + +/* Create desktop alias using a bookmark stuff. */ +bool darwinCreateMachineShortcut(NativeNSStringRef pstrSrcFile, NativeNSStringRef pstrDstPath, NativeNSStringRef pstrName, NativeNSStringRef /* pstrUuid */) +{ + RT_NOREF(pstrName); + if (!pstrSrcFile || !pstrDstPath) + return false; + + NSError *pErr = nil; + NSURL *pSrcUrl = [NSURL fileURLWithPath:pstrSrcFile]; + + NSString *pVmFileName = [pSrcUrl lastPathComponent]; + NSString *pSrcPath = [NSString stringWithFormat:@"%@/%@", pstrDstPath, [pVmFileName stringByDeletingPathExtension]]; + NSURL *pDstUrl = [NSURL fileURLWithPath:pSrcPath]; + + bool rc = false; + + if (!pSrcUrl || !pDstUrl) + return false; + + NSData *pBookmark = [pSrcUrl bookmarkDataWithOptions:NSURLBookmarkCreationSuitableForBookmarkFile + includingResourceValuesForKeys:nil + relativeToURL:nil + error:&pErr]; + + if (pBookmark) + { + rc = [NSURL writeBookmarkData:pBookmark + toURL:pDstUrl + options:0 + error:&pErr]; + } + + return rc; +} + +bool darwinOpenInFileManager(NativeNSStringRef pstrFile) +{ + return [[NSWorkspace sharedWorkspace] selectFile:pstrFile inFileViewerRootedAtPath:@""]; +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIDesktopServices_darwin_p.h b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIDesktopServices_darwin_p.h new file mode 100644 index 00000000..a02e7d09 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIDesktopServices_darwin_p.h @@ -0,0 +1,47 @@ +/* $Id: UIDesktopServices_darwin_p.h $ */ +/** @file + * VBox Qt GUI - Qt GUI - Utility Classes and Functions specific to darwin.. + */ + +/* + * 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 + */ + +#ifndef FEQT_INCLUDED_SRC_platform_darwin_UIDesktopServices_darwin_p_h +#define FEQT_INCLUDED_SRC_platform_darwin_UIDesktopServices_darwin_p_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/VBoxCocoa.h> +#include <iprt/cdefs.h> /* for RT_C_DECLS_BEGIN/RT_C_DECLS_END & stuff */ + +ADD_COCOA_NATIVE_REF(NSString); + +RT_C_DECLS_BEGIN + +bool darwinCreateMachineShortcut(NativeNSStringRef pstrSrcFile, NativeNSStringRef pstrDstPath, NativeNSStringRef pstrName, NativeNSStringRef pstrUuid); +bool darwinOpenInFileManager(NativeNSStringRef pstrFile); + +RT_C_DECLS_END + +#endif /* !FEQT_INCLUDED_SRC_platform_darwin_UIDesktopServices_darwin_p_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIWindowMenuManager.cpp b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIWindowMenuManager.cpp new file mode 100644 index 00000000..01b4bcbe --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIWindowMenuManager.cpp @@ -0,0 +1,372 @@ +/* $Id: UIWindowMenuManager.cpp $ */ +/** @file + * VBox Qt GUI - UIWindowMenuManager class implementation. + */ + +/* + * 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 + */ + +/* Qt includes: */ +#include <QActionGroup> +#include <QApplication> +#include <QMenu> + +/* GUI includes: */ +#include "UIWindowMenuManager.h" + +/* Other VBox includes: */ +#include <iprt/assert.h> + + +/** QObject extension + * used as Mac OS X 'Window' menu helper. */ +class UIMenuHelper : public QObject +{ + Q_OBJECT; + +public: + + /** Constructs menu-helper on the basis of passed @a windows. */ + UIMenuHelper(const QList<QWidget*> &windows); + /** Destructs menu-helper. */ + virtual ~UIMenuHelper() RT_OVERRIDE; + + /** Returns 'Window' menu. */ + QMenu *menu() const { return m_pWindowMenu; } + + /** Adds @a pWindow into 'Window' menu. */ + QAction *addWindow(QWidget *pWindow); + /** Removes @a pWindow from 'Window' menu. */ + void removeWindow(QWidget *pWindow); + + /** Handles translation event. */ + void retranslateUi(); + + /** Updates toggle action states according to passed @a pActiveWindow. */ + void updateStatus(QWidget *pActiveWindow); + +private slots: + + /** Handles request to minimize active-window. */ + void sltMinimizeActiveWindow(); + + /** Handles request to raise sender window. */ + void sltRaiseSender(); + +private: + + /** Holds the 'Window' menu instance. */ + QMenu *m_pWindowMenu; + /** Holds the action group instance. */ + QActionGroup *m_pGroup; + /** Holds the 'Minimize' action instance. */ + QAction *m_pMinimizeAction; + /** Holds the hash of the registered menu-helper instances. */ + QHash<QWidget*, QAction*> m_windows; +}; + + +/********************************************************************************************************************************* +* Class UIMenuHelper implementation. * +*********************************************************************************************************************************/ + +UIMenuHelper::UIMenuHelper(const QList<QWidget*> &windows) +{ + /* Prepare 'Window' menu: */ + m_pWindowMenu = new QMenu; + + /* Prepare action group: */ + m_pGroup = new QActionGroup(this); + m_pGroup->setExclusive(true); + + /* Prepare 'Minimize' action: */ + m_pMinimizeAction = new QAction(this); + m_pWindowMenu->addAction(m_pMinimizeAction); + connect(m_pMinimizeAction, SIGNAL(triggered(bool)), + this, SLOT(sltMinimizeActiveWindow())); + + /* Make sure all already available windows are + * properly registered within this menu: */ + for (int i = 0; i < windows.size(); ++i) + addWindow(windows.at(i)); + + /* Apply language settings: */ + retranslateUi(); +} + +UIMenuHelper::~UIMenuHelper() +{ + /* Cleanup 'Window' menu: */ + delete m_pWindowMenu; + + /* Cleanup actions: */ + qDeleteAll(m_windows); +} + +QAction *UIMenuHelper::addWindow(QWidget *pWindow) +{ + QAction *pAction = 0; + if ( pWindow + && !m_windows.contains(pWindow)) + { + if (m_windows.size() < 2) + m_pWindowMenu->addSeparator(); + + /* The main window always first: */ + pAction = new QAction(this); + pAction->setText(pWindow->windowTitle()); + pAction->setMenuRole(QAction::NoRole); + pAction->setData(QVariant::fromValue(pWindow)); + pAction->setCheckable(true); + + /* The first registered one is always + * considered as the main window: */ + if (m_windows.size() == 0) + pAction->setShortcut(QKeySequence("Ctrl+0")); + m_pGroup->addAction(pAction); + connect(pAction, SIGNAL(triggered(bool)), + this, SLOT(sltRaiseSender())); + m_pWindowMenu->addAction(pAction); + m_windows[pWindow] = pAction; + } + return pAction; +} + +void UIMenuHelper::removeWindow(QWidget *pWindow) +{ + if (m_windows.contains(pWindow)) + { + delete m_windows.value(pWindow); + m_windows.remove(pWindow); + } +} + +void UIMenuHelper::retranslateUi() +{ + /* Translate menu: */ + m_pWindowMenu->setTitle(QApplication::translate("UIActionPool", "&Window")); + + /* Translate menu 'Minimize' action: */ + m_pMinimizeAction->setText(QApplication::translate("UIActionPool", "&Minimize")); + m_pMinimizeAction->setShortcut(QKeySequence("Ctrl+M")); + + /* Translate other menu-actions: */ + foreach (QAction *pAction, m_windows.values()) + { + /* Get corresponding window from action's data: */ + QWidget *pWindow = pAction->data().value<QWidget*>(); + /* Use the window's title as the action's text: */ + pAction->setText(pWindow->windowTitle()); + } +} + +void UIMenuHelper::updateStatus(QWidget *pActiveWindow) +{ + /* 'Minimize' action is enabled if there is active-window: */ + m_pMinimizeAction->setEnabled(pActiveWindow != 0); + + /* If there is active-window: */ + if (pActiveWindow) + { + /* Toggle corresponding action on: */ + if (m_windows.contains(pActiveWindow)) + m_windows.value(pActiveWindow)->setChecked(true); + } + /* If there is no active-window: */ + else + { + /* Make sure corresponding action toggled off: */ + if (QAction *pChecked = m_pGroup->checkedAction()) + pChecked->setChecked(false); + } +} + +void UIMenuHelper::sltMinimizeActiveWindow() +{ + if (QWidget *pActiveWindow = qApp->activeWindow()) + pActiveWindow->showMinimized(); +} + +void UIMenuHelper::sltRaiseSender() +{ + AssertReturnVoid(sender()); + if (QAction *pAction = qobject_cast<QAction*>(sender())) + { + if (QWidget *pWidget = pAction->data().value<QWidget*>()) + { + pWidget->show(); + pWidget->raise(); + pWidget->activateWindow(); + } + } +} + + +/********************************************************************************************************************************* +* Class UIWindowMenuManager implementation. * +*********************************************************************************************************************************/ + +/* static */ +UIWindowMenuManager* UIWindowMenuManager::s_pInstance = 0; + +/* static */ +void UIWindowMenuManager::create() +{ + /* Make sure 'Window' menu Manager is not created: */ + AssertReturnVoid(!s_pInstance); + + /* Create 'Window' menu Manager: */ + new UIWindowMenuManager; +} + +/* static */ +void UIWindowMenuManager::destroy() +{ + /* Make sure 'Window' menu Manager is created: */ + AssertPtrReturnVoid(s_pInstance); + + /* Delete 'Window' menu Manager: */ + delete s_pInstance; +} + +QMenu *UIWindowMenuManager::createMenu(QWidget *pWindow) +{ + /* Create helper: */ + UIMenuHelper *pHelper = new UIMenuHelper(m_windows); + /* Register it: */ + m_helpers[pWindow] = pHelper; + + /* Return menu of created helper: */ + return pHelper->menu(); +} + +void UIWindowMenuManager::destroyMenu(QWidget *pWindow) +{ + /* If window is registered: */ + if (m_helpers.contains(pWindow)) + { + /* Delete helper: */ + delete m_helpers.value(pWindow); + /* Unregister it: */ + m_helpers.remove(pWindow); + } +} + +void UIWindowMenuManager::addWindow(QWidget *pWindow) +{ + /* Register window: */ + m_windows.append(pWindow); + /* Add window to all menus we have: */ + foreach (UIMenuHelper *pHelper, m_helpers.values()) + pHelper->addWindow(pWindow); +} + +void UIWindowMenuManager::removeWindow(QWidget *pWindow) +{ + /* Remove window from all menus we have: */ + foreach (UIMenuHelper *pHelper, m_helpers.values()) + pHelper->removeWindow(pWindow); + /* Unregister window: */ + m_windows.removeAll(pWindow); +} + +void UIWindowMenuManager::retranslateUi() +{ + /* Translate all the helpers: */ + foreach (UIMenuHelper *pHelper, m_helpers.values()) + pHelper->retranslateUi(); +} + +UIWindowMenuManager::UIWindowMenuManager() +{ + /* Assign instance: */ + s_pInstance = this; + + /* Install global event-filter: */ + qApp->installEventFilter(this); +} + +UIWindowMenuManager::~UIWindowMenuManager() +{ + /* Cleanup all helpers: */ + qDeleteAll(m_helpers); + + /* Unassign instance: */ + s_pInstance = 0; +} + +bool UIWindowMenuManager::eventFilter(QObject *pObject, QEvent *pEvent) +{ + /* Acquire event type: */ + const QEvent::Type type = pEvent->type(); + +#ifdef VBOX_OSE /// @todo Do we still need it? + // WORKAROUND: + // Stupid Qt: Qt doesn't check if a window is minimized when a command is + // executed. This leads to strange behaviour. The minimized window is + // partly restored, but not usable. As a workaround we raise the parent + // window before we let execute the command. + // Note: fixed in our local Qt build since 4.7.0. + if (pObject && type == QEvent::Show) + { + QWidget *pWidget = qobject_cast<QWidget*>(pObject); + if ( pWidget + && pWidget->parentWidget() + && pWidget->parentWidget()->isMinimized()) + { + pWidget->parentWidget()->show(); + pWidget->parentWidget()->raise(); + pWidget->parentWidget()->activateWindow(); + } + } +#endif /* VBOX_OSE */ + + /* We need to track several events which leads to different window + * activation and change the menu items in that case. */ + if ( type == QEvent::ActivationChange + || type == QEvent::WindowActivate + || type == QEvent::WindowDeactivate + || type == QEvent::WindowStateChange + || type == QEvent::Show + || type == QEvent::Close + || type == QEvent::Hide) + { + QWidget *pActiveWindow = qApp->activeWindow(); + foreach (UIMenuHelper *pHelper, m_helpers.values()) + pHelper->updateStatus(pActiveWindow); + } + + /* Besides our own retranslation, we should also retranslate + * everything on any registered widget title change event: */ + if (pObject && type == QEvent::WindowTitleChange) + { + QWidget *pWidget = qobject_cast<QWidget*>(pObject); + if (pWidget && m_helpers.contains(pWidget)) + retranslateUi(); + } + + /* Call to base-class: */ + return QIWithRetranslateUI3<QObject>::eventFilter(pObject, pEvent); +} + + +#include "UIWindowMenuManager.moc" diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIWindowMenuManager.h b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIWindowMenuManager.h new file mode 100644 index 00000000..33d10ab0 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/UIWindowMenuManager.h @@ -0,0 +1,98 @@ +/* $Id: UIWindowMenuManager.h $ */ +/** @file + * VBox Qt GUI - UIWindowMenuManager class declaration. + */ + +/* + * 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 + */ + +#ifndef FEQT_INCLUDED_SRC_platform_darwin_UIWindowMenuManager_h +#define FEQT_INCLUDED_SRC_platform_darwin_UIWindowMenuManager_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* Qt includes: */ +#include <QHash> +#include <QObject> + +/* GUI includes: */ +#include "QIWithRetranslateUI.h" + +/* Forward declarations: */ +class QMenu; +class UIMenuHelper; + +/** Singleton QObject extension + * used as Mac OS X 'Window' menu Manager. */ +class SHARED_LIBRARY_STUFF UIWindowMenuManager : public QIWithRetranslateUI3<QObject> +{ + Q_OBJECT; + +public: + + /** Creates instance. */ + static void create(); + /** Destroyes instance. */ + static void destroy(); + /** Returns current instance. */ + static UIWindowMenuManager *instance() { return s_pInstance; } + + /** Creates 'Window' menu for passed @a pWindow. */ + QMenu *createMenu(QWidget *pWindow); + /** Destroys 'Window' menu for passed @a pWindow. */ + void destroyMenu(QWidget *pWindow); + + /** Adds @a pWindow to all 'Window' menus. */ + void addWindow(QWidget *pWindow); + /** Removes @a pWindow from all 'Window' menus. */ + void removeWindow(QWidget *pWindow); + + /** Handles translation event. */ + virtual void retranslateUi() RT_OVERRIDE; + +protected: + + /** Constructs 'Window' menu Manager. */ + UIWindowMenuManager(); + /** Destructs 'Window' menu Manager. */ + ~UIWindowMenuManager(); + + /** Preprocesses any Qt @a pEvent for passed @a pObject. */ + virtual bool eventFilter(QObject *pObject, QEvent *pEvent) RT_OVERRIDE; + +private: + + /** Holds the static instance. */ + static UIWindowMenuManager *s_pInstance; + + /** Holds the list of the registered window references. */ + QList<QWidget*> m_windows; + + /** Holds the hash of the registered menu-helper instances. */ + QHash<QWidget*, UIMenuHelper*> m_helpers; +}; + +/** Singleton 'Window' menu Manager 'official' name. */ +#define gpWindowMenuManager UIWindowMenuManager::instance() + +#endif /* !FEQT_INCLUDED_SRC_platform_darwin_UIWindowMenuManager_h */ diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxCocoaHelper.h b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxCocoaHelper.h new file mode 100644 index 00000000..1156292c --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxCocoaHelper.h @@ -0,0 +1,73 @@ +/* $Id: VBoxCocoaHelper.h $ */ +/** @file + * VBox Qt GUI - VBoxCocoa Helper. + */ + +/* + * Copyright (C) 2009-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 FEQT_INCLUDED_SRC_platform_darwin_VBoxCocoaHelper_h +#define FEQT_INCLUDED_SRC_platform_darwin_VBoxCocoaHelper_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* Global includes */ +#include <VBox/VBoxCocoa.h> + +#ifdef __OBJC__ + +/* System includes */ +#import <AppKit/NSImage.h> +#import <Foundation/NSAutoreleasePool.h> +#import <CoreFoundation/CFString.h> + +/* Qt includes */ +#include <QString> +#include <QVarLengthArray> + +inline NSString *darwinQStringToNSString(const QString &aString) +{ + const UniChar *chars = reinterpret_cast<const UniChar *>(aString.unicode()); + CFStringRef str = CFStringCreateWithCharacters(0, chars, aString.length()); + return [(NSString*)CFStringCreateMutableCopy(0, 0, str) autorelease]; +} + +inline QString darwinNSStringToQString(const NSString *aString) +{ + CFStringRef str = reinterpret_cast<const CFStringRef>(aString); + if(!str) + return QString(); + CFIndex length = CFStringGetLength(str); + const UniChar *chars = CFStringGetCharactersPtr(str); + if (chars) + return QString(reinterpret_cast<const QChar *>(chars), length); + + QVarLengthArray<UniChar> buffer(length); + CFStringGetCharacters(str, CFRangeMake(0, length), buffer.data()); + return QString(reinterpret_cast<const QChar *>(buffer.constData()), length); +} + +#endif /* __OBJC__ */ + +#endif /* !FEQT_INCLUDED_SRC_platform_darwin_VBoxCocoaHelper_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxUtils-darwin-cocoa.mm b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxUtils-darwin-cocoa.mm new file mode 100644 index 00000000..cd355442 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxUtils-darwin-cocoa.mm @@ -0,0 +1,730 @@ +/* $Id: VBoxUtils-darwin-cocoa.mm $ */ +/** @file + * VBox Qt GUI - Declarations of utility classes and functions for handling Darwin Cocoa specific tasks. + */ + +/* + * Copyright (C) 2009-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 "VBoxUtils-darwin.h" +#include "VBoxCocoaHelper.h" + +#include <QMenu> + +#include <iprt/assert.h> + +#import <AppKit/NSEvent.h> +#import <AppKit/NSColor.h> +#import <AppKit/NSFont.h> +#import <AppKit/NSScreen.h> +#import <AppKit/NSScroller.h> +#import <AppKit/NSWindow.h> +#import <AppKit/NSImageView.h> + +#import <objc/objc-class.h> + +/* For the keyboard stuff */ +#include <Carbon/Carbon.h> +#include "DarwinKeyboard.h" + +/** Easy way of dynamical call for 10.7 AppKit functionality we do not yet support. */ +#define NSWindowCollectionBehaviorFullScreenPrimary (1 << 7) +#define NSFullScreenWindowMask (1 << 14) + +NativeNSWindowRef darwinToNativeWindowImpl(NativeNSViewRef pView) +{ + NativeNSWindowRef window = NULL; + if (pView) + window = [pView window]; + + return window; +} + +NativeNSViewRef darwinToNativeViewImpl(NativeNSWindowRef pWindow) +{ + NativeNSViewRef view = NULL; + if (pWindow) + view = [pWindow contentView]; + + return view; +} + +NativeNSButtonRef darwinNativeButtonOfWindowImpl(NativeNSWindowRef pWindow, StandardWindowButtonType enmButtonType) +{ + /* Return corresponding button: */ + switch (enmButtonType) + { + case StandardWindowButtonType_Close: return [pWindow standardWindowButton:NSWindowCloseButton]; + case StandardWindowButtonType_Miniaturize: return [pWindow standardWindowButton:NSWindowMiniaturizeButton]; + case StandardWindowButtonType_Zoom: return [pWindow standardWindowButton:NSWindowZoomButton]; + case StandardWindowButtonType_Toolbar: return [pWindow standardWindowButton:NSWindowToolbarButton]; + case StandardWindowButtonType_DocumentIcon: return [pWindow standardWindowButton:NSWindowDocumentIconButton]; + case StandardWindowButtonType_DocumentVersions: /*return [pWindow standardWindowButton:NSWindowDocumentVersionsButton];*/ break; + case StandardWindowButtonType_FullScreen: /*return [pWindow standardWindowButton:NSWindowFullScreenButton];*/ break; + } + /* Return Nul by default: */ + return Nil; +} + +NativeNSImageRef darwinToNSImageRef(const CGImageRef pImage) +{ + /* Create a bitmap rep from the image. */ + NSBitmapImageRep *bitmapRep = [[[NSBitmapImageRep alloc] initWithCGImage:pImage] autorelease]; + /* Create an NSImage and add the bitmap rep to it */ + NSImage *image = [[NSImage alloc] init]; + [image addRepresentation:bitmapRep]; + return image; +} + +NativeNSImageRef darwinToNSImageRef(const QImage *pImage) +{ + /* Create CGImage on the basis of passed QImage: */ + CGImageRef pCGImage = ::darwinToCGImageRef(pImage); + NativeNSImageRef pNSImage = ::darwinToNSImageRef(pCGImage); + CGImageRelease(pCGImage); + /* Apply device pixel ratio: */ + double dScaleFactor = pImage->devicePixelRatio(); + NSSize imageSize = { (CGFloat)pImage->width() / dScaleFactor, + (CGFloat)pImage->height() / dScaleFactor }; + [pNSImage setSize:imageSize]; + /* Return result: */ + return pNSImage; +} + +NativeNSImageRef darwinToNSImageRef(const QPixmap *pPixmap) +{ + CGImageRef pCGImage = ::darwinToCGImageRef(pPixmap); + NativeNSImageRef pNSImage = ::darwinToNSImageRef(pCGImage); + CGImageRelease(pCGImage); + return pNSImage; +} + +NativeNSImageRef darwinToNSImageRef(const char *pczSource) +{ + CGImageRef pCGImage = ::darwinToCGImageRef(pczSource); + NativeNSImageRef pNSImage = ::darwinToNSImageRef(pCGImage); + CGImageRelease(pCGImage); + return pNSImage; +} + +NativeNSStringRef darwinToNativeString(const char* pcszString) +{ + return [NSString stringWithUTF8String: pcszString]; +} + +QString darwinFromNativeString(NativeNSStringRef pString) +{ + return [pString cStringUsingEncoding :NSASCIIStringEncoding]; +} + +void darwinSetShowsToolbarButtonImpl(NativeNSWindowRef pWindow, bool fEnabled) +{ + [pWindow setShowsToolbarButton:fEnabled]; +} + +void darwinLabelWindow(NativeNSWindowRef pWindow, NativeNSImageRef pImage, double dDpr) +{ + /* Get the parent view of the close button. */ + NSView *wv = [[pWindow standardWindowButton:NSWindowCloseButton] superview]; + if (wv) + { + /* We have to calculate the size of the title bar for the center case. */ + NSSize s = [pImage size]; + NSSize s1 = [wv frame].size; + /* Correctly position the label. */ + NSImageView *iv = [[NSImageView alloc] initWithFrame:NSMakeRect(s1.width - s.width / dDpr, + s1.height - s.height / dDpr - 1, + s.width / dDpr, s.height / dDpr)]; + /* Configure the NSImageView for auto moving. */ + [iv setImage:pImage]; + [iv setAutoresizesSubviews:true]; + [iv setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin]; + /* Add it to the parent of the close button. */ + [wv addSubview:iv positioned:NSWindowBelow relativeTo:nil]; + } +} + +void darwinSetShowsResizeIndicatorImpl(NativeNSWindowRef pWindow, bool fEnabled) +{ + [pWindow setShowsResizeIndicator:fEnabled]; +} + +void darwinSetHidesAllTitleButtonsImpl(NativeNSWindowRef pWindow) +{ + /* Remove all title buttons by changing the style mask. This method is + available from 10.6 on only. */ + if ([pWindow respondsToSelector: @selector(setStyleMask:)]) + [pWindow performSelector: @selector(setStyleMask:) withObject: (id)NSTitledWindowMask]; + else + { + /* On pre 10.6 disable all the buttons currently displayed. Don't use + setHidden cause this remove the buttons, but didn't release the + place used for the buttons. */ + NSButton *pButton = [pWindow standardWindowButton:NSWindowCloseButton]; + if (pButton != Nil) + [pButton setEnabled: NO]; + pButton = [pWindow standardWindowButton:NSWindowMiniaturizeButton]; + if (pButton != Nil) + [pButton setEnabled: NO]; + pButton = [pWindow standardWindowButton:NSWindowZoomButton]; + if (pButton != Nil) + [pButton setEnabled: NO]; + pButton = [pWindow standardWindowButton:NSWindowDocumentIconButton]; + if (pButton != Nil) + [pButton setEnabled: NO]; + } +} + +void darwinSetShowsWindowTransparentImpl(NativeNSWindowRef pWindow, bool fEnabled) +{ + if (fEnabled) + { + [pWindow setOpaque:NO]; + [pWindow setBackgroundColor:[NSColor clearColor]]; + [pWindow setHasShadow:NO]; + } + else + { + [pWindow setOpaque:YES]; + [pWindow setBackgroundColor:[NSColor windowBackgroundColor]]; + [pWindow setHasShadow:YES]; + } +} + +void darwinSetWindowHasShadow(NativeNSWindowRef pWindow, bool fEnabled) +{ + if (fEnabled) + [pWindow setHasShadow :YES]; + else + [pWindow setHasShadow :NO]; +} + +void darwinMinaturizeWindow(NativeNSWindowRef pWindow) +{ + RT_NOREF(pWindow); +// [[NSApplication sharedApplication] miniaturizeAll]; +// printf("bla\n"); +// [pWindow miniaturize:pWindow]; +// [[NSApplication sharedApplication] deactivate]; +// [pWindow performMiniaturize:nil]; +} + +void darwinEnableFullscreenSupport(NativeNSWindowRef pWindow) +{ + [pWindow setCollectionBehavior :NSWindowCollectionBehaviorFullScreenPrimary]; +} + +void darwinEnableTransienceSupport(NativeNSWindowRef pWindow) +{ + [pWindow setCollectionBehavior :NSWindowCollectionBehaviorTransient]; +} + +void darwinToggleFullscreenMode(NativeNSWindowRef pWindow) +{ + /* Toggle native fullscreen mode for passed pWindow. This method is available since 10.7 only. */ + if ([pWindow respondsToSelector: @selector(toggleFullScreen:)]) + [pWindow performSelector: @selector(toggleFullScreen:) withObject: (id)nil]; +} + +void darwinToggleWindowZoom(NativeNSWindowRef pWindow) +{ + /* Toggle native window zoom for passed pWindow. This method is available since 10.0. */ + if ([pWindow respondsToSelector: @selector(zoom:)]) + [pWindow performSelector: @selector(zoom:)]; +} + +bool darwinIsInFullscreenMode(NativeNSWindowRef pWindow) +{ + /* Check whether passed pWindow is in native fullscreen mode. */ + return [pWindow styleMask] & NSFullScreenWindowMask; +} + +bool darwinIsOnActiveSpace(NativeNSWindowRef pWindow) +{ + /* Check whether passed pWindow is on active space. */ + return [pWindow isOnActiveSpace]; +} + +bool darwinScreensHaveSeparateSpaces() +{ + /* Check whether screens have separate spaces. + * This method is available since 10.9 only. */ + if ([NSScreen respondsToSelector: @selector(screensHaveSeparateSpaces)]) + return [NSScreen performSelector: @selector(screensHaveSeparateSpaces)]; + else + return false; +} + +bool darwinIsScrollerStyleOverlay() +{ + /* Check whether scrollers by default have legacy style. + * This method is available since 10.7 only. */ + if ([NSScroller respondsToSelector: @selector(preferredScrollerStyle)]) + { + const int enmType = (int)(intptr_t)[NSScroller performSelector: @selector(preferredScrollerStyle)]; + return enmType == NSScrollerStyleOverlay; + } + else + return false; +} + +/** + * Calls the + (void)setMouseCoalescingEnabled:(BOOL)flag class method. + * + * @param fEnabled Whether to enable or disable coalescing. + */ +void darwinSetMouseCoalescingEnabled(bool fEnabled) +{ + [NSEvent setMouseCoalescingEnabled:fEnabled]; +} + +void darwinWindowAnimateResizeImpl(NativeNSWindowRef pWindow, int x, int y, int width, int height) +{ + RT_NOREF(x, y, width); + + /* It seems that Qt doesn't return the height of the window with the + * toolbar height included. So add this size manually. Could easily be that + * the Trolls fix this in the final release. */ + NSToolbar *toolbar = [pWindow toolbar]; + NSRect windowFrame = [pWindow frame]; + int toolbarHeight = 0; + if(toolbar && [toolbar isVisible]) + toolbarHeight = NSHeight(windowFrame) - NSHeight([[pWindow contentView] frame]); + int h = height + toolbarHeight; + int h1 = h - NSHeight(windowFrame); + windowFrame.size.height = h; + windowFrame.origin.y -= h1; + + [pWindow setFrame:windowFrame display:YES animate: YES]; +} + +void darwinWindowAnimateResizeNewImpl(NativeNSWindowRef pWindow, int height, bool fAnimate) +{ + /* It seems that Qt doesn't return the height of the window with the + * toolbar height included. So add this size manually. Could easily be that + * the Trolls fix this in the final release. */ + NSToolbar *toolbar = [pWindow toolbar]; + NSRect windowFrame = [pWindow frame]; + int toolbarHeight = 0; + if(toolbar && [toolbar isVisible]) + toolbarHeight = NSHeight(windowFrame) - NSHeight([[pWindow contentView] frame]); + int h = height + toolbarHeight; + int h1 = h - NSHeight(windowFrame); + windowFrame.size.height = h; + windowFrame.origin.y -= h1; + + [pWindow setFrame:windowFrame display:YES animate: fAnimate ? YES : NO]; +} + +void darwinTest(NativeNSViewRef pViewOld, NativeNSViewRef pViewNew, int h) +{ + NSMutableDictionary *pDicts[3] = { nil, nil, nil }; + int c = 0; + + /* Scaling necessary? */ + if (h != -1) + { + NSWindow *pWindow = [(pViewOld ? pViewOld : pViewNew) window]; + NSToolbar *toolbar = [pWindow toolbar]; + NSRect windowFrame = [pWindow frame]; + /* Dictionary containing all animation parameters. */ + pDicts[c] = [NSMutableDictionary dictionaryWithCapacity:2]; + /* Specify the animation target. */ + [pDicts[c] setObject:pWindow forKey:NSViewAnimationTargetKey]; + /* Scaling effect. */ + [pDicts[c] setObject:[NSValue valueWithRect:windowFrame] forKey:NSViewAnimationStartFrameKey]; + int toolbarHeight = 0; + if(toolbar && [toolbar isVisible]) + toolbarHeight = NSHeight(windowFrame) - NSHeight([[pWindow contentView] frame]); + int h1 = h + toolbarHeight; + int h2 = h1 - NSHeight(windowFrame); + windowFrame.size.height = h1; + windowFrame.origin.y -= h2; + [pDicts[c] setObject:[NSValue valueWithRect:windowFrame] forKey:NSViewAnimationEndFrameKey]; + ++c; + } + /* Fade out effect. */ + if (pViewOld) + { + /* Dictionary containing all animation parameters. */ + pDicts[c] = [NSMutableDictionary dictionaryWithCapacity:2]; + /* Specify the animation target. */ + [pDicts[c] setObject:pViewOld forKey:NSViewAnimationTargetKey]; + /* Fade out effect. */ + [pDicts[c] setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey]; + ++c; + } + /* Fade in effect. */ + if (pViewNew) + { + /* Dictionary containing all animation parameters. */ + pDicts[c] = [NSMutableDictionary dictionaryWithCapacity:2]; + /* Specify the animation target. */ + [pDicts[c] setObject:pViewNew forKey:NSViewAnimationTargetKey]; + /* Fade in effect. */ + [pDicts[c] setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey]; + ++c; + } + /* Create our animation object. */ + NSViewAnimation *pAni = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:pDicts count:c]]; + [pAni setDuration:.15]; + [pAni setAnimationCurve:NSAnimationEaseIn]; + [pAni setAnimationBlockingMode:NSAnimationBlocking]; +// [pAni setAnimationBlockingMode:NSAnimationNonblockingThreaded]; + + /* Run the animation. */ + [pAni startAnimation]; + /* Cleanup */ + [pAni release]; +} + +void darwinWindowInvalidateShadowImpl(NativeNSWindowRef pWindow) +{ + [pWindow invalidateShadow]; +} + +int darwinWindowToolBarHeight(NativeNSWindowRef pWindow) +{ + NSToolbar *toolbar = [pWindow toolbar]; + NSRect windowFrame = [pWindow frame]; + int toolbarHeight = 0; + int theight = (NSHeight([NSWindow contentRectForFrameRect:[pWindow frame] styleMask:[pWindow styleMask]]) - NSHeight([[pWindow contentView] frame])); + /* toolbar height: */ + if(toolbar && [toolbar isVisible]) + /* title bar height: */ + toolbarHeight = NSHeight(windowFrame) - NSHeight([[pWindow contentView] frame]) - theight; + + return toolbarHeight; +} + +int darwinWindowTitleHeight(NativeNSWindowRef pWindow) +{ + NSView *pSuperview = [[pWindow standardWindowButton:NSWindowCloseButton] superview]; + NSSize sz = [pSuperview frame].size; + return sz.height; +} + +bool darwinIsToolbarVisible(NativeNSWindowRef pWindow) +{ + NSToolbar *pToolbar = [pWindow toolbar]; + + return [pToolbar isVisible] == YES; +} + +bool darwinIsWindowMaximized(NativeNSWindowRef pWindow) +{ + /* Mac OS X API NSWindow isZoomed returns true even for almost maximized windows, + * So implementing this by ourseleves by comparing visible screen-frame & window-frame: */ + NSRect windowFrame = [pWindow frame]; + NSRect screenFrame = [[NSScreen mainScreen] visibleFrame]; + + return (windowFrame.origin.x == screenFrame.origin.x) && + (windowFrame.origin.y == screenFrame.origin.y) && + (windowFrame.size.width == screenFrame.size.width) && + (windowFrame.size.height == screenFrame.size.height); +} + +bool darwinOpenFile(NativeNSStringRef pstrFile) +{ + return [[NSWorkspace sharedWorkspace] openFile:pstrFile]; +} + +float darwinSmallFontSize() +{ + float size = [NSFont systemFontSizeForControlSize: NSSmallControlSize]; + + return size; +} + +bool darwinMouseGrabEvents(const void *pvCocoaEvent, const void *pvCarbonEvent, void *pvUser) +{ + NSEvent *pEvent = (NSEvent*)pvCocoaEvent; + NSEventType EvtType = [pEvent type]; + NSWindow *pWin = ::darwinToNativeWindow((QWidget*)pvUser); + if ( pWin == [pEvent window] + && ( EvtType == NSLeftMouseDown + || EvtType == NSLeftMouseUp + || EvtType == NSRightMouseDown + || EvtType == NSRightMouseUp + || EvtType == NSOtherMouseDown + || EvtType == NSOtherMouseUp + || EvtType == NSLeftMouseDragged + || EvtType == NSRightMouseDragged + || EvtType == NSOtherMouseDragged + || EvtType == NSMouseMoved + || EvtType == NSScrollWheel)) + { + /* When the mouse position is not associated to the mouse cursor, the x + and y values are reported as delta values. */ + float x = [pEvent deltaX]; + float y = [pEvent deltaY]; + if (EvtType == NSScrollWheel) + { + /* In the scroll wheel case we have to do some magic, cause a + normal scroll wheel on a mouse behaves different to a trackpad. + The following is used within Qt. We use the same to get a + similar behavior. */ + if ([pEvent respondsToSelector:@selector(deviceDeltaX:)]) + x = (float)(intptr_t)[pEvent performSelector:@selector(deviceDeltaX)] * 2; + else + x = qBound(-120, (int)(x * 10000), 120); + if ([pEvent respondsToSelector:@selector(deviceDeltaY:)]) + y = (float)(intptr_t)[pEvent performSelector:@selector(deviceDeltaY)] * 2; + else + y = qBound(-120, (int)(y * 10000), 120); + } + /* Get the buttons which where pressed when this event occurs. We have + to use Carbon here, cause the Cocoa method [NSEvent pressedMouseButtons] + is >= 10.6. */ + uint32 buttonMask = 0; + GetEventParameter((EventRef)pvCarbonEvent, kEventParamMouseChord, typeUInt32, 0, + sizeof(buttonMask), 0, &buttonMask); + /* Produce a Qt event out of our info. */ + ::darwinSendMouseGrabEvents((QWidget*)pvUser, EvtType, [pEvent buttonNumber], buttonMask, x, y); + return true; + } + return false; +} + +/* Cocoa event handler which checks if the user right clicked at the unified + toolbar or the title area. */ +bool darwinUnifiedToolbarEvents(const void *pvCocoaEvent, const void *pvCarbonEvent, void *pvUser) +{ + RT_NOREF(pvCarbonEvent); + + NSEvent *pEvent = (NSEvent*)pvCocoaEvent; + NSEventType EvtType = [pEvent type]; + NSWindow *pWin = ::darwinToNativeWindow((QWidget*)pvUser); + /* First check for the right event type and that we are processing events + from the window which was registered by the user. */ + if ( EvtType == NSRightMouseDown + && pWin == [pEvent window]) + { + /* Get the mouse position of the event (screen coordinates) */ + NSPoint point = [NSEvent mouseLocation]; + /* Get the frame rectangle of the window (screen coordinates) */ + NSRect winFrame = [pWin frame]; + /* Calculate the height of the title and the toolbar */ + int i = NSHeight(winFrame) - NSHeight([[pWin contentView] frame]); + /* Based on that height create a rectangle of the unified toolbar + title */ + winFrame.origin.y += winFrame.size.height - i; + winFrame.size.height = i; + /* Check if the mouse press event was on the unified toolbar or title */ + if (NSMouseInRect(point, winFrame, NO)) + /* Create a Qt context menu event, with flipped screen coordinates */ + ::darwinCreateContextMenuEvent(pvUser, point.x, NSHeight([[pWin screen] frame]) - point.y); + } + return false; +} + +/** + * Check for some default application key combinations a Mac user expect, like + * CMD+Q or CMD+H. + * + * @returns true if such a key combo was hit, false otherwise. + * @param pEvent The Cocoa event. + */ +bool darwinIsApplicationCommand(ConstNativeNSEventRef pEvent) +{ + NSEventType eEvtType = [pEvent type]; + bool fGlobalHotkey = false; +// +// if ( (eEvtType == NSKeyDown || eEvtType == NSKeyUp) +// && [[NSApp mainMenu] performKeyEquivalent:pEvent]) +// return true; +// return false; +// && [[[NSApp mainMenu] delegate] menuHasKeyEquivalent:[NSApp mainMenu] forEvent:pEvent target:b action:a]) + + switch (eEvtType) + { + case NSKeyDown: + case NSKeyUp: + { + NSUInteger fEvtMask = [pEvent modifierFlags]; + unsigned short KeyCode = [pEvent keyCode]; + if ( ((fEvtMask & (NX_NONCOALSESCEDMASK | NX_COMMANDMASK | NX_DEVICELCMDKEYMASK)) == (NX_NONCOALSESCEDMASK | NX_COMMANDMASK | NX_DEVICELCMDKEYMASK)) /* L+CMD */ + || ((fEvtMask & (NX_NONCOALSESCEDMASK | NX_COMMANDMASK | NX_DEVICERCMDKEYMASK)) == (NX_NONCOALSESCEDMASK | NX_COMMANDMASK | NX_DEVICERCMDKEYMASK))) /* R+CMD */ + { + if ( KeyCode == 0x0c /* CMD+Q (Quit) */ + || KeyCode == 0x04) /* CMD+H (Hide) */ + fGlobalHotkey = true; + } + else if ( ((fEvtMask & (NX_NONCOALSESCEDMASK | NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_COMMANDMASK | NX_DEVICELCMDKEYMASK)) == (NX_NONCOALSESCEDMASK | NX_ALTERNATEMASK | NX_DEVICELALTKEYMASK | NX_COMMANDMASK | NX_DEVICELCMDKEYMASK)) /* L+ALT+CMD */ + || ((fEvtMask & (NX_NONCOALSESCEDMASK | NX_ALTERNATEMASK | NX_DEVICERCMDKEYMASK | NX_COMMANDMASK | NX_DEVICERCMDKEYMASK)) == (NX_NONCOALSESCEDMASK | NX_ALTERNATEMASK | NX_DEVICERCMDKEYMASK | NX_COMMANDMASK | NX_DEVICERCMDKEYMASK))) /* R+ALT+CMD */ + { + if (KeyCode == 0x04) /* ALT+CMD+H (Hide-Others) */ + fGlobalHotkey = true; + } + break; + } + default: break; + } + return fGlobalHotkey; +} + +void darwinRetranslateAppMenu() +{ + /* This is purely Qt internal. If the Trolls change something here, it will + not work anymore, but at least it will not be a burning man. */ + if ([NSApp respondsToSelector:@selector(qt_qcocoamenuLoader)]) + { + id loader = [NSApp performSelector:@selector(qt_qcocoamenuLoader)]; + if ([loader respondsToSelector:@selector(qtTranslateApplicationMenu)]) + [loader performSelector:@selector(qtTranslateApplicationMenu)]; + } +} + +/* Our resize proxy singleton. This class has two major tasks. First it is used + to proxy the windowWillResize selector of the Qt delegate. As this is class + global and therewith done for all Qt window instances, we have to track the + windows we are interested in. This is the second task. */ +@interface UIResizeProxy: NSObject +{ + NSMutableArray *m_pArray; + bool m_fInit; +} ++(UIResizeProxy*)sharedResizeProxy; +-(void)addWindow:(NSWindow*)pWindow; +-(void)removeWindow:(NSWindow*)pWindow; +-(BOOL)containsWindow:(NSWindow*)pWindow; +@end + +static UIResizeProxy *gSharedResizeProxy = nil; + +@implementation UIResizeProxy ++(UIResizeProxy*)sharedResizeProxy +{ + if (gSharedResizeProxy == nil) + gSharedResizeProxy = [[super allocWithZone:NULL] init]; + return gSharedResizeProxy; +} +-(id)init +{ + self = [super init]; + + m_fInit = false; + + return self; +} +- (void)addWindow:(NSWindow*)pWindow +{ + if (!m_fInit) + { + /* Create an array which contains the registered windows. */ + m_pArray = [[NSMutableArray alloc] init]; + /* Swizzle the windowWillResize method. This means replacing the + original method with our own one and reroute the original one to + another name. */ + Class oriClass = [[pWindow delegate] class]; + Class myClass = [UIResizeProxy class]; + SEL oriSel = @selector(windowWillResize:toSize:); + SEL qtSel = @selector(qtWindowWillResize:toSize:); + Method m1 = class_getInstanceMethod(oriClass, oriSel); + Method m2 = class_getInstanceMethod(myClass, oriSel); + Method m3 = class_getInstanceMethod(myClass, qtSel); + /* Overwrite the original implementation with our own one. old contains + the old implementation. */ + IMP old = method_setImplementation(m1, method_getImplementation(m2)); + /* Add a new method to our class with the old implementation. */ + class_addMethod(oriClass, qtSel, old, method_getTypeEncoding(m3)); + m_fInit = true; + } + [m_pArray addObject:pWindow]; +} +- (void)removeWindow:(NSWindow*)pWindow +{ + [m_pArray removeObject:pWindow]; +} +- (BOOL)containsWindow:(NSWindow*)pWindow +{ + return [m_pArray containsObject:pWindow]; +} +- (NSSize)qtWindowWillResize:(NSWindow *)pWindow toSize:(NSSize)newFrameSize +{ + RT_NOREF(pWindow); + /* This is a fake implementation. It will be replaced by the original Qt + method. */ + return newFrameSize; +} +- (NSSize)windowWillResize:(NSWindow *)pWindow toSize:(NSSize)newFrameSize +{ + /* Call the original implementation for newFrameSize. */ + NSSize qtSize = [self qtWindowWillResize:pWindow toSize:newFrameSize]; + /* Check if we are responsible for this window. */ + if (![[UIResizeProxy sharedResizeProxy] containsWindow:pWindow]) + return qtSize; + /* The static modifier method in NSEvent is >= 10.6. It allows us to check + the shift modifier state during the resize. If it is not available the + user have to press shift before he start to resize. */ + if ([NSEvent respondsToSelector:@selector(modifierFlags)]) + { + if (((int)(intptr_t)[NSEvent performSelector:@selector(modifierFlags)] & NSShiftKeyMask) == NSShiftKeyMask) + return qtSize; + } + else + { + /* Shift key pressed when this resize event was initiated? */ + if (([pWindow resizeFlags] & NSShiftKeyMask) == NSShiftKeyMask) + return qtSize; + } + /* The default case is to calculate the aspect radio of the old size and + used it for the new size. */ + NSSize s = [pWindow frame].size; + double a = (double)s.width / s.height; + NSSize newSize = NSMakeSize(newFrameSize.width, newFrameSize.width / a); + /* We have to make sure the new rectangle meets the minimum requirements. */ + NSSize testSize = [self qtWindowWillResize:pWindow toSize:newSize]; + if ( testSize.width > newSize.width + || testSize.height > newSize.height) + { + double w1 = testSize.width / newSize.width; + double h1 = testSize.height / newSize.height; + if ( w1 > 1 + && w1 > h1) + { + newSize.width = testSize.width; + newSize.height = testSize.width * a; + }else if (h1 > 1) + { + newSize.width = testSize.height * a; + newSize.height = testSize.height; + } + } + return newSize; +} +@end + +void darwinInstallResizeDelegate(NativeNSWindowRef pWindow) +{ + [[UIResizeProxy sharedResizeProxy] addWindow:pWindow]; +} + +void darwinUninstallResizeDelegate(NativeNSWindowRef pWindow) +{ + [[UIResizeProxy sharedResizeProxy] removeWindow:pWindow]; +} + +void *darwinCocoaToCarbonEvent(void *pvCocoaEvent) +{ + NSEvent *pEvent = (NSEvent*)pvCocoaEvent; + return (void*)[pEvent eventRef]; +} diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxUtils-darwin.cpp b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxUtils-darwin.cpp new file mode 100644 index 00000000..e5dbfc13 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxUtils-darwin.cpp @@ -0,0 +1,777 @@ +/* $Id: VBoxUtils-darwin.cpp $ */ +/** @file + * VBox Qt GUI - Utility Classes and Functions specific to Darwin. + */ + +/* + * 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 + */ + +/* Qt includes: */ +#include <QMainWindow> +#include <QApplication> +#include <QWidget> +#include <QToolBar> +#include <QPainter> +#include <QPixmap> +#include <QContextMenuEvent> + +/* GUI includes: */ +#include "VBoxUtils-darwin.h" +#include "VBoxCocoaHelper.h" +#include "UICocoaApplication.h" + +/* Other VBox includes: */ +#include <iprt/mem.h> +#include <iprt/assert.h> + +/* System includes: */ +#include <Carbon/Carbon.h> + +NativeNSViewRef darwinToNativeView(QWidget *pWidget) +{ + if (pWidget) + return reinterpret_cast<NativeNSViewRef>(pWidget->winId()); + return nil; +} + +NativeNSWindowRef darwinToNativeWindow(QWidget *pWidget) +{ + if (pWidget) + return ::darwinToNativeWindowImpl(::darwinToNativeView(pWidget)); + return nil; +} + +NativeNSWindowRef darwinToNativeWindow(NativeNSViewRef aView) +{ + return ::darwinToNativeWindowImpl(aView); +} + +NativeNSViewRef darwinToNativeView(NativeNSWindowRef aWindow) +{ + return ::darwinToNativeViewImpl(aWindow); +} + +NativeNSWindowRef darwinNativeButtonOfWindow(QWidget *pWidget, StandardWindowButtonType enmButtonType) +{ + return ::darwinNativeButtonOfWindowImpl(::darwinToNativeWindow(pWidget), enmButtonType); +} + +void darwinSetShowsToolbarButton(QToolBar *aToolBar, bool fEnabled) +{ + QWidget *parent = aToolBar->parentWidget(); + if (parent) + ::darwinSetShowsToolbarButtonImpl(::darwinToNativeWindow(parent), fEnabled); +} + +void darwinLabelWindow(QWidget *pWidget, QPixmap *pPixmap) +{ + ::darwinLabelWindow(::darwinToNativeWindow(pWidget), ::darwinToNSImageRef(pPixmap), pPixmap->devicePixelRatio()); +} + +void darwinSetHidesAllTitleButtons(QWidget *pWidget) +{ + /* Currently only necessary in the Cocoa version */ + ::darwinSetHidesAllTitleButtonsImpl(::darwinToNativeWindow(pWidget)); +} + +void darwinSetShowsWindowTransparent(QWidget *pWidget, bool fEnabled) +{ + ::darwinSetShowsWindowTransparentImpl(::darwinToNativeWindow(pWidget), fEnabled); +} + +void darwinSetWindowHasShadow(QWidget *pWidget, bool fEnabled) +{ + ::darwinSetWindowHasShadow(::darwinToNativeWindow(pWidget), fEnabled); +} + +void darwinWindowAnimateResize(QWidget *pWidget, const QRect &aTarget) +{ + ::darwinWindowAnimateResizeImpl(::darwinToNativeWindow(pWidget), aTarget.x(), aTarget.y(), aTarget.width(), aTarget.height()); +} + +void darwinWindowAnimateResizeNew(QWidget *pWidget, int h, bool fAnimate) +{ + ::darwinWindowAnimateResizeNewImpl(::darwinToNativeWindow(pWidget), h, fAnimate); +} + +void darwinTest(QWidget *pWidget1, QWidget *pWidget2, int h) +{ + ::darwinTest(::darwinToNativeView(pWidget1), ::darwinToNativeView(pWidget2), h); +} + +void darwinWindowInvalidateShape(QWidget *pWidget) +{ + /* Here a simple update is enough! */ + pWidget->update(); +} + +void darwinWindowInvalidateShadow(QWidget *pWidget) +{ + ::darwinWindowInvalidateShadowImpl(::darwinToNativeWindow(pWidget)); +} + +void darwinSetShowsResizeIndicator(QWidget *pWidget, bool fEnabled) +{ + ::darwinSetShowsResizeIndicatorImpl(::darwinToNativeWindow(pWidget), fEnabled); +} + +bool darwinIsWindowMaximized(QWidget *pWidget) +{ + /* Currently only necessary in the Cocoa version */ + return ::darwinIsWindowMaximized(::darwinToNativeWindow(pWidget)); +} + +void darwinMinaturizeWindow(QWidget *pWidget) +{ + return ::darwinMinaturizeWindow(::darwinToNativeWindow(pWidget)); +} + +void darwinEnableFullscreenSupport(QWidget *pWidget) +{ + return ::darwinEnableFullscreenSupport(::darwinToNativeWindow(pWidget)); +} + +void darwinEnableTransienceSupport(QWidget *pWidget) +{ + return ::darwinEnableTransienceSupport(::darwinToNativeWindow(pWidget)); +} + +void darwinToggleFullscreenMode(QWidget *pWidget) +{ + return ::darwinToggleFullscreenMode(::darwinToNativeWindow(pWidget)); +} + +void darwinToggleWindowZoom(QWidget *pWidget) +{ + return ::darwinToggleWindowZoom(::darwinToNativeWindow(pWidget)); +} + +bool darwinIsInFullscreenMode(QWidget *pWidget) +{ + return ::darwinIsInFullscreenMode(::darwinToNativeWindow(pWidget)); +} + +bool darwinIsOnActiveSpace(QWidget *pWidget) +{ + return ::darwinIsOnActiveSpace(::darwinToNativeWindow(pWidget)); +} + +void darwinInstallResizeDelegate(QWidget *pWidget) +{ + ::darwinInstallResizeDelegate(::darwinToNativeWindow(pWidget)); +} + +void darwinUninstallResizeDelegate(QWidget *pWidget) +{ + ::darwinUninstallResizeDelegate(::darwinToNativeWindow(pWidget)); +} + +bool darwinOpenFile(const QString& strFile) +{ + return ::darwinOpenFile(darwinToNativeString(strFile.toUtf8().constData())); +} + +QString darwinSystemLanguage(void) +{ + /* Get the locales supported by our bundle */ + CFArrayRef supportedLocales = ::CFBundleCopyBundleLocalizations(::CFBundleGetMainBundle()); + /* Check them against the languages currently selected by the user */ + CFArrayRef preferredLocales = ::CFBundleCopyPreferredLocalizationsFromArray(supportedLocales); + /* Get the one which is on top */ + CFStringRef localeId = (CFStringRef)::CFArrayGetValueAtIndex(preferredLocales, 0); + /* Convert them to a C-string */ + char localeName[20]; + ::CFStringGetCString(localeId, localeName, sizeof(localeName), kCFStringEncodingUTF8); + /* Some cleanup */ + ::CFRelease(supportedLocales); + ::CFRelease(preferredLocales); + QString id(localeName); + /* Check for some misbehavior */ + if (id.isEmpty() || + id.toLower() == "english") + id = "en"; + return id; +} + +void darwinDisableIconsInMenus(void) +{ + /* No icons in the menu of a mac application. */ + QApplication::instance()->setAttribute(Qt::AA_DontShowIconsInMenus, true); +} + +int darwinWindowToolBarHeight(QWidget *pWidget) +{ + NOREF(pWidget); + return 0; +} + +int darwinWindowTitleHeight(QWidget *pWidget) +{ + return ::darwinWindowTitleHeight(::darwinToNativeWindow(pWidget)); +} + +bool darwinIsToolbarVisible(QToolBar *pToolBar) +{ + bool fResult = false; + QWidget *pParent = pToolBar->parentWidget(); + if (pParent) + fResult = ::darwinIsToolbarVisible(::darwinToNativeWindow(pParent)); + return fResult; +} + + +bool darwinSetFrontMostProcess() +{ + ProcessSerialNumber psn = { 0, kCurrentProcess }; + return ::SetFrontProcess(&psn) == 0; +} + +uint64_t darwinGetCurrentProcessId() +{ + uint64_t processId = 0; + ProcessSerialNumber psn = { 0, kCurrentProcess }; + if (::GetCurrentProcess(&psn) == 0) + processId = RT_MAKE_U64(psn.lowLongOfPSN, psn.highLongOfPSN); + return processId; +} + +/* Proxy icon creation */ +QPixmap darwinCreateDragPixmap(const QPixmap& aPixmap, const QString &aText) +{ + QFontMetrics fm(qApp->font()); + QRect tbRect = fm.boundingRect(aText); + const int h = qMax(aPixmap.height(), fm.ascent() + 1); + const int m = 2; + QPixmap dragPixmap(aPixmap.width() + tbRect.width() + m, h); + dragPixmap.fill(Qt::transparent); + QPainter painter(&dragPixmap); + painter.drawPixmap(0, qAbs(h - aPixmap.height()) / 2.0, aPixmap); + painter.setPen(Qt::white); + painter.drawText(QRect(aPixmap.width() + m, 1, tbRect.width(), h - 1), Qt::AlignLeft | Qt::AlignVCenter, aText); + painter.setPen(Qt::black); + painter.drawText(QRect(aPixmap.width() + m, 0, tbRect.width(), h - 1), Qt::AlignLeft | Qt::AlignVCenter, aText); + painter.end(); + return dragPixmap; +} + +/** + * Callback for deleting the QImage object when CGImageCreate is done + * with it (which is probably not until the returned CFGImageRef is released). + * + * @param info Pointer to the QImage. + */ +static void darwinDataProviderReleaseQImage(void *info, const void *, size_t) +{ + QImage *qimg = (QImage *)info; + delete qimg; +} + +/** + * Converts a QPixmap to a CGImage. + * + * @returns CGImageRef for the new image. (Remember to release it when finished with it.) + * @param aPixmap Pointer to the QPixmap instance to convert. + */ +CGImageRef darwinToCGImageRef(const QImage *pImage) +{ + QImage *imageCopy = new QImage(*pImage); + /** @todo this code assumes 32-bit image input, the lazy bird convert image to 32-bit method is anything but optimal... */ + if (imageCopy->format() != QImage::Format_ARGB32) + *imageCopy = imageCopy->convertToFormat(QImage::Format_ARGB32); + Assert(!imageCopy->isNull()); + + CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); + CGDataProviderRef dp = CGDataProviderCreateWithData(imageCopy, pImage->bits(), pImage->sizeInBytes(), + darwinDataProviderReleaseQImage); + + CGBitmapInfo bmpInfo = kCGImageAlphaFirst | kCGBitmapByteOrder32Host; + CGImageRef ir = CGImageCreate(imageCopy->width(), imageCopy->height(), 8, 32, imageCopy->bytesPerLine(), cs, + bmpInfo, dp, 0 /*decode */, 0 /* shouldInterpolate */, + kCGRenderingIntentDefault); + CGColorSpaceRelease(cs); + CGDataProviderRelease(dp); + + Assert(ir); + return ir; +} + +/** + * Converts a QPixmap to a CGImage. + * + * @returns CGImageRef for the new image. (Remember to release it when finished with it.) + * @param aPixmap Pointer to the QPixmap instance to convert. + */ +CGImageRef darwinToCGImageRef(const QPixmap *pPixmap) +{ + /* It seems Qt releases the memory to an returned CGImageRef when the + * associated QPixmap is destroyed. This shouldn't happen as long a + * CGImageRef has a retrain count. As a workaround we make a real copy. */ + int bitmapBytesPerRow = pPixmap->width() * 4; + int bitmapByteCount = (bitmapBytesPerRow * pPixmap->height()); + /* Create a memory block for the temporary image. It is initialized by zero + * which means black & zero alpha. */ + void *pBitmapData = RTMemAllocZ(bitmapByteCount); + CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB(); + /* Create a context to paint on */ + CGContextRef ctx = CGBitmapContextCreate(pBitmapData, + pPixmap->width(), + pPixmap->height(), + 8, + bitmapBytesPerRow, + cs, + kCGImageAlphaPremultipliedFirst); + /* Get the CGImageRef from Qt */ + CGImageRef qtPixmap = pPixmap->toImage().toCGImage(); + /* Draw the image from Qt & convert the context back to a new CGImageRef. */ + CGContextDrawImage(ctx, CGRectMake(0, 0, pPixmap->width(), pPixmap->height()), qtPixmap); + CGImageRef newImage = CGBitmapContextCreateImage(ctx); + /* Now release all used resources */ + CGImageRelease(qtPixmap); + CGContextRelease(ctx); + CGColorSpaceRelease(cs); + RTMemFree(pBitmapData); + + /* Return the new CGImageRef */ + return newImage; +} + +/** + * Loads an image using Qt and converts it to a CGImage. + * + * @returns CGImageRef for the new image. (Remember to release it when finished with it.) + * @param aSource The source name. + */ +CGImageRef darwinToCGImageRef(const char *pczSource) +{ + QPixmap qpm(QString(":/") + pczSource); + Assert(!qpm.isNull()); + return ::darwinToCGImageRef(&qpm); +} + +void darwinRegisterForUnifiedToolbarContextMenuEvents(QMainWindow *pWindow) +{ + UICocoaApplication::instance()->registerForNativeEvents(RT_BIT_32(3) /* NSRightMouseDown */, ::darwinUnifiedToolbarEvents, pWindow); +} + +void darwinUnregisterForUnifiedToolbarContextMenuEvents(QMainWindow *pWindow) +{ + UICocoaApplication::instance()->unregisterForNativeEvents(RT_BIT_32(3) /* NSRightMouseDown */, ::darwinUnifiedToolbarEvents, pWindow); +} + +void darwinMouseGrab(QWidget *pWidget) +{ + CGAssociateMouseAndMouseCursorPosition(false); + UICocoaApplication::instance()->registerForNativeEvents(RT_BIT_32(1) | /* NSLeftMouseDown */ + RT_BIT_32(2) | /* NSLeftMouseUp */ + RT_BIT_32(3) | /* NSRightMouseDown */ + RT_BIT_32(4) | /* NSRightMouseUp */ + RT_BIT_32(5) | /* NSMouseMoved */ + RT_BIT_32(6) | /* NSLeftMouseDragged */ + RT_BIT_32(7) | /* NSRightMouseDragged */ + RT_BIT_32(25) | /* NSOtherMouseDown */ + RT_BIT_32(26) | /* NSOtherMouseUp */ + RT_BIT_32(27) | /* NSOtherMouseDragged */ + RT_BIT_32(22), /* NSScrollWheel */ + ::darwinMouseGrabEvents, pWidget); +} + +void darwinMouseRelease(QWidget *pWidget) +{ + UICocoaApplication::instance()->unregisterForNativeEvents(RT_BIT_32(1) | /* NSLeftMouseDown */ + RT_BIT_32(2) | /* NSLeftMouseUp */ + RT_BIT_32(3) | /* NSRightMouseDown */ + RT_BIT_32(4) | /* NSRightMouseUp */ + RT_BIT_32(5) | /* NSMouseMoved */ + RT_BIT_32(6) | /* NSLeftMouseDragged */ + RT_BIT_32(7) | /* NSRightMouseDragged */ + RT_BIT_32(25) | /* NSOtherMouseDown */ + RT_BIT_32(26) | /* NSOtherMouseUp */ + RT_BIT_32(27) | /* NSOtherMouseDragged */ + RT_BIT_32(22), /* NSScrollWheel */ + ::darwinMouseGrabEvents, pWidget); + CGAssociateMouseAndMouseCursorPosition(true); +} + +void darwinSendMouseGrabEvents(QWidget *pWidget, int type, int button, int buttons, int x, int y) +{ + QEvent::Type qtType = QEvent::None; + Qt::MouseButtons qtButtons = Qt::NoButton; + Qt::MouseButton qtButton = Qt::NoButton; + Qt::MouseButton qtExtraButton = Qt::NoButton; + Qt::Orientation qtOrientation = Qt::Horizontal; + int wheelDelta = 0; + /* Which button is used in the NSOtherMouse... cases? */ + if (button == 0) + qtExtraButton = Qt::LeftButton; + else if (button == 1) + qtExtraButton = Qt::RightButton; + else if (button == 2) + qtExtraButton = Qt::MiddleButton; + else if (button == 3) + qtExtraButton = Qt::XButton1; + else if (button == 4) + qtExtraButton = Qt::XButton2; + /* Map the NSEvent to a QEvent and define the Qt::Buttons when necessary. */ + switch(type) + { + case 1: /* NSLeftMouseDown */ + { + qtType = QEvent::MouseButtonPress; + qtButton = Qt::LeftButton; + break; + } + case 2: /* NSLeftMouseUp */ + { + qtType = QEvent::MouseButtonRelease; + qtButton = Qt::LeftButton; + break; + } + case 3: /* NSRightMouseDown */ + { + qtType = QEvent::MouseButtonPress; + qtButton = Qt::RightButton; + break; + } + case 4: /* NSRightMouseUp */ + { + qtType = QEvent::MouseButtonRelease; + qtButton = Qt::RightButton; + break; + } + case 5: /* NSMouseMoved */ + { + qtType = QEvent::MouseMove; + break; + } + case 6: /* NSLeftMouseDragged */ + { + qtType = QEvent::MouseMove; + qtButton = Qt::LeftButton; + break; + } + case 7: /* NSRightMouseDragged */ + { + qtType = QEvent::MouseMove; + qtButton = Qt::RightButton; + break; + } + case 22: /* NSScrollWheel */ + { + qtType = QEvent::Wheel; + if (y != 0) + { + wheelDelta = y; + qtOrientation = Qt::Vertical; + } + else if (x != 0) + { + wheelDelta = x; + qtOrientation = Qt::Horizontal; + } + x = y = 0; + break; + } + case 25: /* NSOtherMouseDown */ + { + qtType = QEvent::MouseButtonPress; + qtButton = qtExtraButton; + break; + } + case 26: /* NSOtherMouseUp */ + { + qtType = QEvent::MouseButtonRelease; + qtButton = qtExtraButton; + break; + } + case 27: /* NSOtherMouseDragged */ + { + qtType = QEvent::MouseMove; + qtButton = qtExtraButton; + break; + } + default: return; + } + /* Create a Qt::MouseButtons Mask. */ + if ((buttons & RT_BIT_32(0)) == RT_BIT_32(0)) + qtButtons |= Qt::LeftButton; + if ((buttons & RT_BIT_32(1)) == RT_BIT_32(1)) + qtButtons |= Qt::RightButton; + if ((buttons & RT_BIT_32(2)) == RT_BIT_32(2)) + qtButtons |= Qt::MiddleButton; + if ((buttons & RT_BIT_32(3)) == RT_BIT_32(3)) + qtButtons |= Qt::XButton1; + if ((buttons & RT_BIT_32(4)) == RT_BIT_32(4)) + qtButtons |= Qt::XButton2; + /* Create a new mouse delta event and send it to the widget. */ + UIGrabMouseEvent *pEvent = new UIGrabMouseEvent(qtType, qtButton, qtButtons, x, y, wheelDelta, qtOrientation); + qApp->sendEvent(pWidget, pEvent); +} + +void darwinCreateContextMenuEvent(void *pvUser, int x, int y) +{ + QWidget *pWin = static_cast<QWidget*>(pvUser); + QPoint global(x, y); + QPoint local = pWin->mapFromGlobal(global); + qApp->postEvent(pWin, new QContextMenuEvent(QContextMenuEvent::Mouse, local, global)); +} + +QString darwinResolveAlias(const QString &strFile) +{ + OSErr err = noErr; + FSRef fileRef; + QString strTarget; + do + { + Boolean fDir; + if ((err = FSPathMakeRef((const UInt8*)strFile.toUtf8().constData(), &fileRef, &fDir)) != noErr) + break; + Boolean fAlias = FALSE; + if ((err = FSIsAliasFile(&fileRef, &fAlias, &fDir)) != noErr) + break; + if (fAlias == TRUE) + { + if ((err = FSResolveAliasFile(&fileRef, TRUE, &fAlias, &fDir)) != noErr) + break; + char pszPath[1024]; + if ((err = FSRefMakePath(&fileRef, (UInt8*)pszPath, 1024)) != noErr) + break; + strTarget = QString::fromUtf8(pszPath); + } + else + strTarget = strFile; + }while(0); + + return strTarget; +} + + +/******************************************************************************** + * + * Old carbon stuff. Have to convert soon! + * + ********************************************************************************/ + +/* Event debugging stuff. Borrowed from Knuts Qt patch. */ +#if defined (DEBUG) + +# define MY_CASE(a) case a: return #a +const char * DarwinDebugEventName(UInt32 ekind) +{ + switch (ekind) + { +# if !__LP64__ + MY_CASE(kEventWindowUpdate ); + MY_CASE(kEventWindowDrawContent ); +# endif + MY_CASE(kEventWindowActivated ); + MY_CASE(kEventWindowDeactivated ); + MY_CASE(kEventWindowHandleActivate ); + MY_CASE(kEventWindowHandleDeactivate ); + MY_CASE(kEventWindowGetClickActivation ); + MY_CASE(kEventWindowGetClickModality ); + MY_CASE(kEventWindowShowing ); + MY_CASE(kEventWindowHiding ); + MY_CASE(kEventWindowShown ); + MY_CASE(kEventWindowHidden ); + MY_CASE(kEventWindowCollapsing ); + MY_CASE(kEventWindowCollapsed ); + MY_CASE(kEventWindowExpanding ); + MY_CASE(kEventWindowExpanded ); + MY_CASE(kEventWindowZoomed ); + MY_CASE(kEventWindowBoundsChanging ); + MY_CASE(kEventWindowBoundsChanged ); + MY_CASE(kEventWindowResizeStarted ); + MY_CASE(kEventWindowResizeCompleted ); + MY_CASE(kEventWindowDragStarted ); + MY_CASE(kEventWindowDragCompleted ); + MY_CASE(kEventWindowClosed ); + MY_CASE(kEventWindowTransitionStarted ); + MY_CASE(kEventWindowTransitionCompleted ); +# if !__LP64__ + MY_CASE(kEventWindowClickDragRgn ); + MY_CASE(kEventWindowClickResizeRgn ); + MY_CASE(kEventWindowClickCollapseRgn ); + MY_CASE(kEventWindowClickCloseRgn ); + MY_CASE(kEventWindowClickZoomRgn ); + MY_CASE(kEventWindowClickContentRgn ); + MY_CASE(kEventWindowClickProxyIconRgn ); + MY_CASE(kEventWindowClickToolbarButtonRgn ); + MY_CASE(kEventWindowClickStructureRgn ); +# endif + MY_CASE(kEventWindowCursorChange ); + MY_CASE(kEventWindowCollapse ); + MY_CASE(kEventWindowCollapseAll ); + MY_CASE(kEventWindowExpand ); + MY_CASE(kEventWindowExpandAll ); + MY_CASE(kEventWindowClose ); + MY_CASE(kEventWindowCloseAll ); + MY_CASE(kEventWindowZoom ); + MY_CASE(kEventWindowZoomAll ); + MY_CASE(kEventWindowContextualMenuSelect ); + MY_CASE(kEventWindowPathSelect ); + MY_CASE(kEventWindowGetIdealSize ); + MY_CASE(kEventWindowGetMinimumSize ); + MY_CASE(kEventWindowGetMaximumSize ); + MY_CASE(kEventWindowConstrain ); +# if !__LP64__ + MY_CASE(kEventWindowHandleContentClick ); +# endif + MY_CASE(kEventWindowGetDockTileMenu ); + MY_CASE(kEventWindowProxyBeginDrag ); + MY_CASE(kEventWindowProxyEndDrag ); + MY_CASE(kEventWindowToolbarSwitchMode ); + MY_CASE(kEventWindowFocusAcquired ); + MY_CASE(kEventWindowFocusRelinquish ); + MY_CASE(kEventWindowFocusContent ); + MY_CASE(kEventWindowFocusToolbar ); + MY_CASE(kEventWindowFocusDrawer ); + MY_CASE(kEventWindowSheetOpening ); + MY_CASE(kEventWindowSheetOpened ); + MY_CASE(kEventWindowSheetClosing ); + MY_CASE(kEventWindowSheetClosed ); + MY_CASE(kEventWindowDrawerOpening ); + MY_CASE(kEventWindowDrawerOpened ); + MY_CASE(kEventWindowDrawerClosing ); + MY_CASE(kEventWindowDrawerClosed ); + MY_CASE(kEventWindowDrawFrame ); + MY_CASE(kEventWindowDrawPart ); + MY_CASE(kEventWindowGetRegion ); + MY_CASE(kEventWindowHitTest ); + MY_CASE(kEventWindowInit ); + MY_CASE(kEventWindowDispose ); + MY_CASE(kEventWindowDragHilite ); + MY_CASE(kEventWindowModified ); + MY_CASE(kEventWindowSetupProxyDragImage ); + MY_CASE(kEventWindowStateChanged ); + MY_CASE(kEventWindowMeasureTitle ); + MY_CASE(kEventWindowDrawGrowBox ); + MY_CASE(kEventWindowGetGrowImageRegion ); + MY_CASE(kEventWindowPaint ); + } + static char s_sz[64]; + sprintf(s_sz, "kind=%u", (uint)ekind); + return s_sz; +} +# undef MY_CASE + +/* Convert a class into the 4 char code defined in + * 'Developer/Headers/CFMCarbon/CarbonEvents.h' to + * identify the event. */ +const char * darwinDebugClassName(UInt32 eclass) +{ + char *pclass = (char*)&eclass; + static char s_sz[11]; + sprintf(s_sz, "class=%c%c%c%c", pclass[3], + pclass[2], + pclass[1], + pclass[0]); + return s_sz; +} + +void darwinDebugPrintEvent(const char *psz, EventRef evtRef) +{ + if (!evtRef) + return; + UInt32 ekind = GetEventKind(evtRef), eclass = GetEventClass(evtRef); + if (eclass == kEventClassWindow) + { + switch (ekind) + { +# if !__LP64__ + case kEventWindowDrawContent: + case kEventWindowUpdate: +# endif + case kEventWindowBoundsChanged: + break; + default: + { + WindowRef wid = NULL; + GetEventParameter(evtRef, kEventParamDirectObject, typeWindowRef, NULL, sizeof(WindowRef), NULL, &wid); + QWidget *widget = QWidget::find((WId)wid); + printf("%d %s: (%s) %#x win=%p wid=%p (%s)\n", (int)time(NULL), psz, darwinDebugClassName(eclass), (uint)ekind, wid, widget, DarwinDebugEventName(ekind)); + break; + } + } + } + else if (eclass == kEventClassCommand) + { + WindowRef wid = NULL; + GetEventParameter(evtRef, kEventParamDirectObject, typeWindowRef, NULL, sizeof(WindowRef), NULL, &wid); + QWidget *widget = QWidget::find((WId)wid); + const char *name = "Unknown"; + switch (ekind) + { + case kEventCommandProcess: + name = "kEventCommandProcess"; + break; + case kEventCommandUpdateStatus: + name = "kEventCommandUpdateStatus"; + break; + } + printf("%d %s: (%s) %#x win=%p wid=%p (%s)\n", (int)time(NULL), psz, darwinDebugClassName(eclass), (uint)ekind, wid, widget, name); + } + else if (eclass == kEventClassKeyboard) + { + printf("%d %s: %#x(%s) %#x (kEventClassKeyboard)", (int)time(NULL), psz, (uint)eclass, darwinDebugClassName(eclass), (uint)ekind); + + UInt32 keyCode = 0; + ::GetEventParameter(evtRef, kEventParamKeyCode, typeUInt32, NULL, + sizeof(keyCode), NULL, &keyCode); + printf(" keyCode=%d (%#x) ", (int)keyCode, (unsigned)keyCode); + + char macCharCodes[8] = {0,0,0,0, 0,0,0,0}; + ::GetEventParameter(evtRef, kEventParamKeyCode, typeChar, NULL, + sizeof(macCharCodes), NULL, &macCharCodes[0]); + printf(" macCharCodes={"); + for (unsigned i =0; i < 8 && macCharCodes[i]; i++) + printf( i == 0 ? "%02x" : ",%02x", macCharCodes[i]); + printf("}"); + + UInt32 modifierMask = 0; + ::GetEventParameter(evtRef, kEventParamKeyModifiers, typeUInt32, NULL, + sizeof(modifierMask), NULL, &modifierMask); + printf(" modifierMask=%08x", (unsigned)modifierMask); + + UniChar keyUnicodes[8] = {0,0,0,0, 0,0,0,0}; + ::GetEventParameter(evtRef, kEventParamKeyUnicodes, typeUnicodeText, NULL, + sizeof(keyUnicodes), NULL, &keyUnicodes[0]); + printf(" keyUnicodes={"); + for (unsigned i =0; i < 8 && keyUnicodes[i]; i++) + printf( i == 0 ? "%02x" : ",%02x", keyUnicodes[i]); + printf("}"); + + UInt32 keyboardType = 0; + ::GetEventParameter(evtRef, kEventParamKeyboardType, typeUInt32, NULL, + sizeof(keyboardType), NULL, &keyboardType); + printf(" keyboardType=%08x", (unsigned)keyboardType); + + EventHotKeyID evtHotKeyId = {0,0}; + ::GetEventParameter(evtRef, typeEventHotKeyID, typeEventHotKeyID, NULL, + sizeof(evtHotKeyId), NULL, &evtHotKeyId); + printf(" evtHotKeyId={signature=%08x, .id=%08x}", (unsigned)evtHotKeyId.signature, (unsigned)evtHotKeyId.id); + printf("\n"); + } + else + printf("%d %s: %#x(%s) %#x\n", (int)time(NULL), psz, (uint)eclass, darwinDebugClassName(eclass), (uint)ekind); +} + +#endif /* DEBUG */ diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxUtils-darwin.h b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxUtils-darwin.h new file mode 100644 index 00000000..f545cf11 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VBoxUtils-darwin.h @@ -0,0 +1,319 @@ +/* $Id: VBoxUtils-darwin.h $ */ +/** @file + * VBox Qt GUI - Declarations of utility classes and functions for handling Darwin specific tasks. + */ + +/* + * 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 + */ + +#ifndef FEQT_INCLUDED_SRC_platform_darwin_VBoxUtils_darwin_h +#define FEQT_INCLUDED_SRC_platform_darwin_VBoxUtils_darwin_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* Qt includes: */ +#include <QRect> + +/* GUI includes: */ +#include "UILibraryDefs.h" + +/* Other VBox includes: */ +#include <VBox/VBoxCocoa.h> +#include <ApplicationServices/ApplicationServices.h> +#undef PVM // Stupid, stupid apple headers (sys/param.h)!! +#include <iprt/cdefs.h> + +/* External includes: */ +#include <ApplicationServices/ApplicationServices.h> + +/* Forward declarations: */ +class QImage; +class QMainWindow; +class QMenu; +class QPixmap; +class QToolBar; +class QWidget; + +/* Cocoa declarations: */ +ADD_COCOA_NATIVE_REF(NSButton); +ADD_COCOA_NATIVE_REF(NSEvent); +ADD_COCOA_NATIVE_REF(NSImage); +ADD_COCOA_NATIVE_REF(NSString); +ADD_COCOA_NATIVE_REF(NSView); +ADD_COCOA_NATIVE_REF(NSWindow); + + +/** Mac OS X: Standard window button types. */ +enum StandardWindowButtonType +{ + StandardWindowButtonType_Close, // Since OS X 10.2 + StandardWindowButtonType_Miniaturize, // Since OS X 10.2 + StandardWindowButtonType_Zoom, // Since OS X 10.2 + StandardWindowButtonType_Toolbar, // Since OS X 10.2 + StandardWindowButtonType_DocumentIcon, // Since OS X 10.2 + StandardWindowButtonType_DocumentVersions, // Since OS X 10.7 + StandardWindowButtonType_FullScreen // Since OS X 10.7 +}; + + +RT_C_DECLS_BEGIN + +/******************************************************************************** + * + * Window/View management (OS System native) + * + ********************************************************************************/ +NativeNSWindowRef darwinToNativeWindowImpl(NativeNSViewRef pView); +NativeNSViewRef darwinToNativeViewImpl(NativeNSWindowRef pWindow); +NativeNSButtonRef darwinNativeButtonOfWindowImpl(NativeNSWindowRef pWindow, StandardWindowButtonType enmButtonType); +SHARED_LIBRARY_STUFF NativeNSStringRef darwinToNativeString(const char* pcszString); +QString darwinFromNativeString(NativeNSStringRef pString); + +/******************************************************************************** + * + * Simple setter methods (OS System native) + * + ********************************************************************************/ +void darwinSetShowsToolbarButtonImpl(NativeNSWindowRef pWindow, bool fEnabled); +void darwinSetShowsResizeIndicatorImpl(NativeNSWindowRef pWindow, bool fEnabled); +void darwinSetHidesAllTitleButtonsImpl(NativeNSWindowRef pWindow); +SHARED_LIBRARY_STUFF void darwinLabelWindow(NativeNSWindowRef pWindow, NativeNSImageRef pImage, double dDpr); +void darwinSetShowsWindowTransparentImpl(NativeNSWindowRef pWindow, bool fEnabled); +SHARED_LIBRARY_STUFF void darwinSetWindowHasShadow(NativeNSWindowRef pWindow, bool fEnabled); +SHARED_LIBRARY_STUFF void darwinSetMouseCoalescingEnabled(bool fEnabled); + +void darwintest(NativeNSWindowRef pWindow); +/******************************************************************************** + * + * Simple helper methods (OS System native) + * + ********************************************************************************/ +void darwinWindowAnimateResizeImpl(NativeNSWindowRef pWindow, int x, int y, int width, int height); +void darwinWindowAnimateResizeNewImpl(NativeNSWindowRef pWindow, int height, bool fAnimate); +void darwinTest(NativeNSViewRef pView, NativeNSViewRef pView1, int h); +void darwinWindowInvalidateShapeImpl(NativeNSWindowRef pWindow); +void darwinWindowInvalidateShadowImpl(NativeNSWindowRef pWindow); +int darwinWindowToolBarHeight(NativeNSWindowRef pWindow); +SHARED_LIBRARY_STUFF int darwinWindowTitleHeight(NativeNSWindowRef pWindow); +bool darwinIsToolbarVisible(NativeNSWindowRef pWindow); +SHARED_LIBRARY_STUFF bool darwinIsWindowMaximized(NativeNSWindowRef pWindow); +void darwinMinaturizeWindow(NativeNSWindowRef pWindow); +SHARED_LIBRARY_STUFF void darwinEnableFullscreenSupport(NativeNSWindowRef pWindow); +SHARED_LIBRARY_STUFF void darwinEnableTransienceSupport(NativeNSWindowRef pWindow); +SHARED_LIBRARY_STUFF void darwinToggleFullscreenMode(NativeNSWindowRef pWindow); +SHARED_LIBRARY_STUFF void darwinToggleWindowZoom(NativeNSWindowRef pWindow); +SHARED_LIBRARY_STUFF bool darwinIsInFullscreenMode(NativeNSWindowRef pWindow); +SHARED_LIBRARY_STUFF bool darwinIsOnActiveSpace(NativeNSWindowRef pWindow); +SHARED_LIBRARY_STUFF bool darwinScreensHaveSeparateSpaces(); +SHARED_LIBRARY_STUFF bool darwinIsScrollerStyleOverlay(); + +bool darwinOpenFile(NativeNSStringRef pstrFile); + +SHARED_LIBRARY_STUFF float darwinSmallFontSize(); +SHARED_LIBRARY_STUFF bool darwinSetFrontMostProcess(); +SHARED_LIBRARY_STUFF uint64_t darwinGetCurrentProcessId(); + +void darwinInstallResizeDelegate(NativeNSWindowRef pWindow); +void darwinUninstallResizeDelegate(NativeNSWindowRef pWindow); + +bool darwinUnifiedToolbarEvents(const void *pvCocoaEvent, const void *pvCarbonEvent, void *pvUser); +bool darwinMouseGrabEvents(const void *pvCocoaEvent, const void *pvCarbonEvent, void *pvUser); +void darwinCreateContextMenuEvent(void *pvWin, int x, int y); + +SHARED_LIBRARY_STUFF bool darwinIsApplicationCommand(ConstNativeNSEventRef pEvent); + +void darwinRetranslateAppMenu(); + +void darwinSendMouseGrabEvents(QWidget *pWidget, int type, int button, int buttons, int x, int y); + +SHARED_LIBRARY_STUFF QString darwinResolveAlias(const QString &strFile); + +RT_C_DECLS_END + +DECLINLINE(CGRect) darwinToCGRect(const QRect& aRect) { return CGRectMake(aRect.x(), aRect.y(), aRect.width(), aRect.height()); } +DECLINLINE(CGRect) darwinFlipCGRect(CGRect aRect, double aTargetHeight) { aRect.origin.y = aTargetHeight - aRect.origin.y - aRect.size.height; return aRect; } +DECLINLINE(CGRect) darwinFlipCGRect(CGRect aRect, const CGRect &aTarget) { return darwinFlipCGRect(aRect, aTarget.size.height); } +DECLINLINE(CGRect) darwinCenterRectTo(CGRect aRect, const CGRect& aToRect) +{ + aRect.origin.x = aToRect.origin.x + (aToRect.size.width - aRect.size.width) / 2.0; + aRect.origin.y = aToRect.origin.y + (aToRect.size.height - aRect.size.height) / 2.0; + return aRect; +} + +/******************************************************************************** + * + * Window/View management (Qt Wrapper) + * + ********************************************************************************/ + +/** + * Returns a reference to the native View of the QWidget. + * + * @returns either HIViewRef or NSView* of the QWidget. + * @param pWidget Pointer to the QWidget + */ +NativeNSViewRef darwinToNativeView(QWidget *pWidget); + +/** + * Returns a reference to the native Window of the QWidget. + * + * @returns either WindowRef or NSWindow* of the QWidget. + * @param pWidget Pointer to the QWidget + */ +NativeNSWindowRef darwinToNativeWindow(QWidget *pWidget); + +/* This is necessary because of the C calling convention. Its a simple wrapper + for darwinToNativeWindowImpl to allow operator overloading which isn't + allowed in C. */ +/** + * Returns a reference to the native Window of the View.. + * + * @returns either WindowRef or NSWindow* of the View. + * @param pWidget Pointer to the native View + */ +NativeNSWindowRef darwinToNativeWindow(NativeNSViewRef pView); + +/** + * Returns a reference to the native View of the Window. + * + * @returns either HIViewRef or NSView* of the Window. + * @param pWidget Pointer to the native Window + */ +NativeNSViewRef darwinToNativeView(NativeNSWindowRef pWindow); + +/** + * Returns a reference to the native button of QWidget. + * + * @returns corresponding NSButton* of the QWidget. + * @param pWidget Brings the pointer to the QWidget. + * @param enmButtonType Brings the type of the native button required. + */ +NativeNSButtonRef darwinNativeButtonOfWindow(QWidget *pWidget, StandardWindowButtonType enmButtonType); + +/******************************************************************************** + * + * Graphics stuff (Qt Wrapper) + * + ********************************************************************************/ +/** + * Returns a reference to the CGContext of the QWidget. + * + * @returns CGContextRef of the QWidget. + * @param pWidget Pointer to the QWidget + */ +SHARED_LIBRARY_STUFF CGImageRef darwinToCGImageRef(const QImage *pImage); +SHARED_LIBRARY_STUFF CGImageRef darwinToCGImageRef(const QPixmap *pPixmap); +SHARED_LIBRARY_STUFF CGImageRef darwinToCGImageRef(const char *pczSource); + +SHARED_LIBRARY_STUFF NativeNSImageRef darwinToNSImageRef(const CGImageRef pImage); +SHARED_LIBRARY_STUFF NativeNSImageRef darwinToNSImageRef(const QImage *pImage); +SHARED_LIBRARY_STUFF NativeNSImageRef darwinToNSImageRef(const QPixmap *pPixmap); +SHARED_LIBRARY_STUFF NativeNSImageRef darwinToNSImageRef(const char *pczSource); + +#include <QEvent> +class UIGrabMouseEvent: public QEvent +{ +public: + enum { GrabMouseEvent = QEvent::User + 200 }; + + UIGrabMouseEvent(QEvent::Type type, Qt::MouseButton button, Qt::MouseButtons buttons, int x, int y, int wheelDelta, Qt::Orientation o) + : QEvent((QEvent::Type)GrabMouseEvent) + , m_type(type) + , m_button(button) + , m_buttons(buttons) + , m_x(x) + , m_y(y) + , m_wheelDelta(wheelDelta) + , m_orientation(o) + {} + QEvent::Type mouseEventType() const { return m_type; } + Qt::MouseButton button() const { return m_button; } + Qt::MouseButtons buttons() const { return m_buttons; } + int xDelta() const { return m_x; } + int yDelta() const { return m_y; } + int wheelDelta() const { return m_wheelDelta; } + Qt::Orientation orientation() const { return m_orientation; } + +private: + /* Private members */ + QEvent::Type m_type; + Qt::MouseButton m_button; + Qt::MouseButtons m_buttons; + int m_x; + int m_y; + int m_wheelDelta; + Qt::Orientation m_orientation; +}; + +/******************************************************************************** + * + * Simple setter methods (Qt Wrapper) + * + ********************************************************************************/ +void darwinSetShowsToolbarButton(QToolBar *aToolBar, bool fEnabled); +SHARED_LIBRARY_STUFF void darwinLabelWindow(QWidget *pWidget, QPixmap *pPixmap); +void darwinSetShowsResizeIndicator(QWidget *pWidget, bool fEnabled); +SHARED_LIBRARY_STUFF void darwinSetHidesAllTitleButtons(QWidget *pWidget); +void darwinSetShowsWindowTransparent(QWidget *pWidget, bool fEnabled); +SHARED_LIBRARY_STUFF void darwinSetWindowHasShadow(QWidget *pWidget, bool fEnabled); +SHARED_LIBRARY_STUFF void darwinDisableIconsInMenus(void); + +void darwinTest(QWidget *pWidget1, QWidget *pWidget2, int h); + +/******************************************************************************** + * + * Simple helper methods (Qt Wrapper) + * + ********************************************************************************/ +SHARED_LIBRARY_STUFF void darwinWindowAnimateResize(QWidget *pWidget, const QRect &aTarget); +void darwinWindowAnimateResizeNew(QWidget *pWidget, int h, bool fAnimate); +void darwinWindowInvalidateShape(QWidget *pWidget); +void darwinWindowInvalidateShadow(QWidget *pWidget); +int darwinWindowToolBarHeight(QWidget *pWidget); +SHARED_LIBRARY_STUFF int darwinWindowTitleHeight(QWidget *pWidget); +bool darwinIsToolbarVisible(QToolBar *pToolBar); +SHARED_LIBRARY_STUFF bool darwinIsWindowMaximized(QWidget *pWidget); +void darwinMinaturizeWindow(QWidget *pWidget); +SHARED_LIBRARY_STUFF void darwinEnableFullscreenSupport(QWidget *pWidget); +SHARED_LIBRARY_STUFF void darwinEnableTransienceSupport(QWidget *pWidget); +SHARED_LIBRARY_STUFF void darwinToggleFullscreenMode(QWidget *pWidget); +SHARED_LIBRARY_STUFF void darwinToggleWindowZoom(QWidget *pWidget); +SHARED_LIBRARY_STUFF bool darwinIsInFullscreenMode(QWidget *pWidget); +SHARED_LIBRARY_STUFF bool darwinIsOnActiveSpace(QWidget *pWidget); +bool darwinOpenFile(const QString &strFile); + +QString darwinSystemLanguage(void); +QPixmap darwinCreateDragPixmap(const QPixmap& aPixmap, const QString &aText); + +void darwinInstallResizeDelegate(QWidget *pWidget); +void darwinUninstallResizeDelegate(QWidget *pWidget); + +SHARED_LIBRARY_STUFF void darwinRegisterForUnifiedToolbarContextMenuEvents(QMainWindow *pWindow); +SHARED_LIBRARY_STUFF void darwinUnregisterForUnifiedToolbarContextMenuEvents(QMainWindow *pWindow); + +SHARED_LIBRARY_STUFF void darwinMouseGrab(QWidget *pWidget); +SHARED_LIBRARY_STUFF void darwinMouseRelease(QWidget *pWidget); + +SHARED_LIBRARY_STUFF void *darwinCocoaToCarbonEvent(void *pvCocoaEvent); + +#endif /* !FEQT_INCLUDED_SRC_platform_darwin_VBoxUtils_darwin_h */ diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/VM-Info.plist b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VM-Info.plist new file mode 100644 index 00000000..db617644 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VM-Info.plist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundlePackageType</key> <string>APPL</string> + <key>CFBundleSignature</key> <string>VBVM</string> + <key>CFBundleDevelopmentRegion</key> <string>English</string> + <key>CFBundleIdentifier</key> <string>org.virtualbox.app.VirtualBoxVM</string> + <key>CFBundleName</key> <string>VirtualBox VM</string> + <key>CFBundleExecutable</key> <string>VirtualBoxVM</string> + <key>CFBundleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleShortVersionString</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleGetInfoString</key> <string>@VBOX_PRODUCT@ @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string> + <key>CFBundleIconFile</key> <string>virtualbox</string> + <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> + <key>LSCanProvideIMVideoDataSource</key> <true/> + <key>NSHighResolutionCapable</key> <true/> + <key>NSSupportsAutomaticGraphicsSwitching</key><true/> + <key>NSCameraUsageDescription</key> <string>VirtualBox needs camera access for emulated webcam passthrough</string> + <key>NSMicrophoneUsageDescription</key> <string>VirtualBox needs microphone access for guest audio input</string> +</dict> +</plist> diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/VM-PkgInfo b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VM-PkgInfo new file mode 100644 index 00000000..be1f395e --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/VM-PkgInfo @@ -0,0 +1 @@ +APPLVBVM
\ No newline at end of file diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/tstDarwinKeyboard.cpp b/src/VBox/Frontends/VirtualBox/src/platform/darwin/tstDarwinKeyboard.cpp new file mode 100644 index 00000000..0c6955ae --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/tstDarwinKeyboard.cpp @@ -0,0 +1,108 @@ +/* $Id: tstDarwinKeyboard.cpp $ */ +/** @file + * VBox Qt GUI Testcase - Common GUI Library - Darwin Keyboard routines. + * + * @todo Move this up somewhere so that the two SDL GUIs can use parts of this code too (-HID crap). + */ + +/* + * 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 <iprt/initterm.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/time.h> +#include <iprt/assert.h> + +#include "DarwinKeyboard.h" + + +int main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + AssertReleaseRCReturn(rc, 1); + + /* + * Warmup tests. + */ + RTPrintf("tstDarwinKeyboard: Warmup...\n"); + + RTTimeNanoTS(); + DarwinGrabKeyboard(true); + DarwinReleaseKeyboard(); + + RTTimeNanoTS(); + DarwinGrabKeyboard(true); + DarwinReleaseKeyboard(); + +/* Test these too: +unsigned DarwinKeycodeToSet1Scancode(unsigned uKeyCode); +UInt32 DarwinAdjustModifierMask(UInt32 fModifiers); +unsigned DarwinModifierMaskToSet1Scancode(UInt32 fModifiers); +unsigned DarwinModifierMaskToDarwinKeycode(UInt32 fModifiers); +UInt32 DarwinKeyCodeToDarwinModifierMask(unsigned uKeyCode); +unsigned DarwinEventToSet1Scancode(EventRef Event, UInt32 *pfCurKeyModifiers); +void DarwinDisableGlobalHotKeys(bool fDisable); +*/ + + /* + * Grab and release the keyboard a lot of times and time it. + * We're looking both at performance and for memory and reference leaks here. + */ + RTPrintf("tstDarwinKeyboard: Profiling Grab and Release"); + RTStrmFlush(g_pStdOut); + const uint64_t u64Start = RTTimeNanoTS(); + uint64_t u64Grab = 0; + uint64_t u64Release = 0; + unsigned i; + for (i = 0; i < 20; i++) + { + uint64_t u64 = RTTimeNanoTS(); + DarwinGrabKeyboard(argc != 1); + u64Grab += RTTimeNanoTS() - u64; + + u64 = RTTimeNanoTS(); + DarwinReleaseKeyboard(); + u64Release += RTTimeNanoTS() - u64; + + if ((i % 10) == 0) + { + RTPrintf("."); + RTStrmFlush(g_pStdOut); + } + } + const uint64_t u64Elapsed = RTTimeNanoTS() - u64Start; + RTPrintf("\n" + "tstDarwinKeyboard: %u times in %RU64 ms - %RU64 ms per call\n", + i, u64Elapsed / 1000000, (u64Elapsed / i) / 1000000); + RTPrintf("tstDarwinKeyboard: DarwinGrabKeyboard: %RU64 ms total - %RU64 ms per call\n", + u64Grab / 1000000, (u64Grab / i) / 1000000); + RTPrintf("tstDarwinKeyboard: DarwinReleaseKeyboard: %RU64 ms total - %RU64 ms per call\n", + u64Release / 1000000, (u64Release / i) / 1000000); + + return 0; +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/vmstarter-Info.plist b/src/VBox/Frontends/VirtualBox/src/platform/darwin/vmstarter-Info.plist new file mode 100644 index 00000000..303e0d48 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/vmstarter-Info.plist @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundlePackageType</key> <string>APPL</string> + <key>CFBundleSignature</key> <string>VMST</string> + <key>CFBundleDevelopmentRegion</key> <string>English</string> + <key>CFBundleIdentifier</key> <string>org.virtualbox.app.vmstarter</string> + <key>CFBundleName</key> <string>vmstarter</string> + <key>CFBundleExecutable</key> <string>vmstarter</string> + <key>CFBundleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleShortVersionString</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleGetInfoString</key> <string>@VBOX_PRODUCT@ vmstarter @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string> + <key>CFBundleIconFile</key> <string>virtualbox</string> + <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> + <key>LSUIElement</key> <true/> + <key>NSHighResolutionCapable</key> <true/> + <key>NSSupportsAutomaticGraphicsSwitching</key><true/> + <key>CFBundleDocumentTypes</key> + <array> + <dict> + <key>CFBundleTypeName</key> <string>VirtualBox Machine Definition</string> + <key>CFBundleTypeExtensions</key> <array><string>vbox</string></array> + <key>CFBundleTypeRole</key> <string>Editor</string> + <key>LSHandlerRank</key> <string>Owner</string> + <key>CFBundleTypeIconFile</key> <string>virtualbox-vbox</string> + </dict> + </array> +</dict> +</plist> diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/vmstarter-PkgInfo b/src/VBox/Frontends/VirtualBox/src/platform/darwin/vmstarter-PkgInfo new file mode 100644 index 00000000..c1388087 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/vmstarter-PkgInfo @@ -0,0 +1 @@ +APPLVBST diff --git a/src/VBox/Frontends/VirtualBox/src/platform/darwin/vmstarter.mm b/src/VBox/Frontends/VirtualBox/src/platform/darwin/vmstarter.mm new file mode 100644 index 00000000..1aac4bd6 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/darwin/vmstarter.mm @@ -0,0 +1,140 @@ +/* $Id: vmstarter.mm $ */ +/** @file + * VBox Qt GUI - Helper application for starting vbox the right way when the user double clicks on a file type association. + */ + +/* + * 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 + */ + +#import <Cocoa/Cocoa.h> +#include <iprt/cdefs.h> + +@interface AppDelegate: NSObject +{ +NSString *m_strVBoxPath; +} +@end + +@implementation AppDelegate +-(id) init +{ + self = [super init]; + if (self) + { + /* Get the path of VBox by looking where our bundle is located. */ + m_strVBoxPath = [[[[NSBundle mainBundle] bundlePath] + stringByAppendingPathComponent:@"/../../../../VirtualBox.app"] + stringByStandardizingPath]; + /* We kill ourself after 1 seconds */ + [NSTimer scheduledTimerWithTimeInterval:1.0 + target:NSApp + selector:@selector(terminate:) + userInfo:nil + repeats:NO]; + } + + return self; +} + +- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames +{ + RT_NOREF(sender); + + BOOL fResult = FALSE; + NSWorkspace *pWS = [NSWorkspace sharedWorkspace]; + /* We need to check if vbox is running already. If so we sent an open + event. If not we start a new process with the file as parameter. */ + NSArray *pApps = [pWS runningApplications]; + bool fVBoxRuns = false; + for (NSRunningApplication *pApp in pApps) + { + if ([pApp.bundleIdentifier isEqualToString:@"org.virtualbox.app.VirtualBox"]) + { + fVBoxRuns = true; + break; + } + } + if (fVBoxRuns) + { + /* Send the open event. + * Todo: check for an method which take a list of files. */ + for (NSString *filename in filenames) + fResult = [pWS openFile:filename withApplication:m_strVBoxPath andDeactivate:TRUE]; + } + else + { + /* Fire up a new instance of VBox. We prefer LSOpenApplication over + NSTask, cause it makes sure that VBox will become the front most + process after starting up. */ +/** @todo should replace all this with -[NSWorkspace + * launchApplicationAtURL:options:configuration:error:] because LSOpenApplication is deprecated in + * 10.10 while, FSPathMakeRef is deprecated since 10.8. */ + /* The state horror show starts right here: */ + OSStatus err = noErr; + Boolean fDir; + void *asyncLaunchRefCon = NULL; + FSRef fileRef; + CFStringRef file = NULL; + CFArrayRef args = NULL; + void **list = (void**)malloc(sizeof(void*) * [filenames count]); + for (size_t i = 0; i < [filenames count]; ++i) + list[i] = [filenames objectAtIndex:i]; + do + { + NSString *strVBoxExe = [m_strVBoxPath stringByAppendingPathComponent:@"Contents/MacOS/VirtualBox"]; + err = FSPathMakeRef((const UInt8*)[strVBoxExe UTF8String], &fileRef, &fDir); + if (err != noErr) + break; + args = CFArrayCreate(NULL, (const void **)list, [filenames count], &kCFTypeArrayCallBacks); + if (args == NULL) + break; + LSApplicationParameters par = { 0, 0, &fileRef, asyncLaunchRefCon, 0, args, 0 }; + err = LSOpenApplication(&par, NULL); + if (err != noErr) + break; + fResult = TRUE; + }while(0); + if (list) /* Why bother checking, because you've crashed already if it's NULL! */ + free(list); + if (file) + CFRelease(file); + if (args) + CFRelease(args); + } +} +@end + +int main() +{ + /* Global auto release pool. */ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + /* Create our own delegate for the application. */ + AppDelegate *pAppDelegate = [[AppDelegate alloc] init]; + [[NSApplication sharedApplication] setDelegate: (id<NSApplicationDelegate>)pAppDelegate]; /** @todo check out ugly cast */ + pAppDelegate = nil; + /* Start the event loop. */ + [NSApp run]; + /* Cleanup */ + [pool release]; + return 0; +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/os2/VBoxHlp.asm b/src/VBox/Frontends/VirtualBox/src/platform/os2/VBoxHlp.asm new file mode 100644 index 00000000..573c797a --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/os2/VBoxHlp.asm @@ -0,0 +1,71 @@ +; $Id: VBoxHlp.asm $ +;; @file +; VBox Qt GUI - Implementation of OS/2-specific helpers that require to reside in a DLL. +; +; This stub is used to avoid linking the helper DLL to the C runtime. +; + +; +; 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 +; + +;; @todo BEGINCODE gives us this: +; +; 02-03-2008 22:19:37 SYS3175 PID 4383 TID 0001 Slot 0076 +; D:\CODING\INNOTEK\VBOX\OUT\OS2.X86\RELEASE\BIN\VIRTUALBOX.EXE +; c0000005 +; 17d40000 +; P1=00000008 P2=0000bea4 P3=XXXXXXXX P4=XXXXXXXX +; EAX=00001489 EBX=00000000 ECX=00000000 EDX=00000000 +; ESI=00000000 EDI=00001489 +; DS=be7f DSACC=00f3 DSLIM=0000003f +; ES=0053 ESACC=f0f3 ESLIM=ffffffff +; FS=150b FSACC=00f3 FSLIM=00000030 +; GS=0000 GSACC=**** GSLIM=******** +; CS:EIP=bea7:00000000 CSACC=00f2 CSLIM=00000002 +; SS:ESP=01d7:0000ffe8 SSACC=00f3 SSLIM=0000ffff +; EBP=00000000 FLG=00012202 +; +; VBOXHLP.DLL 0003:00000000 +; +; Looks like the previous 'segment TEXT32 ...' definition in asmdefs.mac +; is ignored and the segment is redefined as if it had no 'CLASS=CODE...' +; attributes... + +;%include "iprt/asmdefs.mac" +; +;BEGINCODE + +segment TEXT32 public CLASS=CODE align=16 use32 flat + +extern _DLL_InitTerm + +; Low-level DLL entry point - Forward to the C code. +..start: + jmp _DLL_InitTerm + + +; emxomfld may generate references to this for weak symbols. It is usually +; found in in libend.lib. +ABSOLUTE 0 +global WEAK$ZERO +WEAK$ZERO: + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/os2/VBoxHlp.cpp b/src/VBox/Frontends/VirtualBox/src/platform/os2/VBoxHlp.cpp new file mode 100644 index 00000000..b5dd9eda --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/os2/VBoxHlp.cpp @@ -0,0 +1,198 @@ +/* $Id: VBoxHlp.cpp $ */ +/** @file + * VBox Qt GUI - Implementation of OS/2-specific helpers that require to reside in a DLL + */ + +/* + * 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 + */ + +#define OS2EMX_PLAIN_CHAR + +#define INCL_BASE +#define INCL_PM +#define INCL_DOSINFOSEG +#define INCL_DOSDEVIOCTL +#include <os2.h> + +#include "VBoxHlp.h" + +/** + * Undocumented PM hook that is called before the pressed key is checked + * against the global accelerator table. + * + * Taken from the xWorkplace source code where it appears to come from the + * ProgramCommander/2 source code. Thanks to Ulrich Moeller and Roman Stangl. + */ +#define HK_PREACCEL 17 + +/* NOTE: all global data is per-process (DATA32 is multiple, nonshared). */ + +/* Module handle of this DLL */ +static HMODULE gThisModule = NULLHANDLE; + +static PGINFOSEG gGIS = NULL; +static PLINFOSEG gLIS = NULL; + +/* Parameters for the keyboard hook (VBoxHlpInstallKbdHook()) */ +HAB gKbdHookHab = NULLHANDLE; +HWND gKbdHookHwnd = NULLHANDLE; +ULONG gKbdHookMsg = 0; + +/** + * Message Input hook used to monitor the system message queue. + * + * @param aHab Anchor handle. + * @param aHwnd Pointer to the QMSG structure. + * @param aFS Flags from WinPeekMsg(), either PM_NOREMOVE or + * PM_REMOVE. + * + * @return @c TRUE to steal the given message or @c FALSE to pass it to the + * rest of the hook chain. + */ +static +BOOL EXPENTRY vboxInputHook (HAB aHab, PQMSG aMsg, ULONG aFS) +{ + if (aMsg->msg == WM_CHAR) + { + /* For foreign processes that didn't call VBoxHlpInstallKbdHook(), + * gKbdHookHwnd remains NULL. If it's the case while in this input + * hook, it means that the given foreign process is in foreground + * now. Since forwarding should work only for processes that + * called VBoxHlpInstallKbdHook(), we ignore the message. */ + if (gKbdHookHwnd != NULLHANDLE) + { + MRESULT reply = + WinSendMsg (gKbdHookHwnd, gKbdHookMsg, aMsg->mp1, aMsg->mp2); + return (BOOL) reply; + } + } + + return FALSE; +} + +/** + * Installs a hook that will intercept all keyboard input (WM_CHAR) messages + * and forward them to the given window handle using the given message + * identifier. Messages are intercepted only when the given top-level window + * is the active desktop window (i.e. a window receiving keyboard input). + * + * When the WM_CHAR message is intercepted, it is forwarded as is (including + * all parameters) except that the message ID is changed to the given message + * ID. The result of the WinSendMsg() call is then converted to BOOL and if + * it results to TRUE then the message is considered to be processed, + * otherwise it is passed back to the system for normal processing. + * + * If the hook is already installed for the same or another window, this + * method will return @c false. + * + * @note This function is not thread-safe and must be called only on the main + * thread once per process. + * + * @param aHab Window anchor block. + * @param aHwnd Top-level window handle to forward WM_CHAR messages to. + * @param aMsg Message ID to use when forwarding. + * + * @return @c true on success and @c false otherwise. */ +VBOXHLPDECL(bool) VBoxHlpInstallKbdHook (HAB aHab, HWND aHwnd, + unsigned long aMsg) +{ + if (gKbdHookHwnd != NULLHANDLE || + aHwnd == NULLHANDLE) + return false; + + BOOL ok = WinSetHook (aHab, NULLHANDLE, HK_PREACCEL, + (PFN) vboxInputHook, gThisModule); + + if (ok) + { + gKbdHookHab = aHab; + gKbdHookHwnd = aHwnd; + gKbdHookMsg = aMsg; + } + + return (bool) ok; +} + +/** + * Uninstalls the keyboard hook installed by VBoxHlpInstallKbdHook(). + * All arguments must match arguments passed to VBoxHlpInstallKbdHook(), + * otherwise this method will do nothing and return @c false. + * + * @return @c true on success and @c false otherwise. + */ +VBOXHLPDECL(bool) VBoxHlpUninstallKbdHook (HAB aHab, HWND aHwnd, + unsigned long aMsg) +{ + if (gKbdHookHab != aHab || + gKbdHookHwnd != aHwnd || + gKbdHookMsg != aMsg) + return false; + + BOOL ok = WinReleaseHook (aHab, NULLHANDLE, HK_PREACCEL, + (PFN) vboxInputHook, gThisModule); + + if (ok) + { + gKbdHookHab = NULLHANDLE; + gKbdHookHwnd = NULLHANDLE; + gKbdHookMsg = 0; + } + + return (bool) ok; +} + +/** + * DLL entry point. + * + * @param aHandle DLL module handle. + * @param aFlag 0 on initialization or 1 on termination. + * + * @return Non-zero for success or 0 for failure. + */ +ULONG _System _DLL_InitTerm (HMODULE aHandle, ULONG aFlag) +{ + bool ok = true; + + if (aFlag == 0) + { + /* DLL initialization */ + + gThisModule = aHandle; + + gGIS = GETGINFOSEG(); + gLIS = GETLINFOSEG(); + } + else + { + /* DLL termination */ + + /* Make sure we release the hook if the user forgets to do so. */ + if (gKbdHookHwnd != NULLHANDLE) + WinReleaseHook (gKbdHookHab, NULLHANDLE, HK_PREACCEL, + (PFN) vboxInputHook, gThisModule); + + gThisModule = NULLHANDLE; + } + + return (unsigned long) ok; +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/os2/VBoxHlp.h b/src/VBox/Frontends/VirtualBox/src/platform/os2/VBoxHlp.h new file mode 100644 index 00000000..0df97704 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/os2/VBoxHlp.h @@ -0,0 +1,49 @@ +/* $Id: VBoxHlp.h $ */ +/** @file + * VBox Qt GUI - Declaration of OS/2-specific helpers that require to reside in a DLL. + */ + +/* + * 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 + */ + +#ifndef FEQT_INCLUDED_SRC_platform_os2_VBoxHlp_h +#define FEQT_INCLUDED_SRC_platform_os2_VBoxHlp_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> + +#ifdef IN_VBOXHLP +# define VBOXHLPDECL(type) DECLEXPORT(type) RTCALL +#else +# define VBOXHLPDECL(type) DECLIMPORT(type) RTCALL +#endif + +VBOXHLPDECL(bool) VBoxHlpInstallKbdHook (HAB aHab, HWND aHwnd, + unsigned long aMsg); + +VBOXHLPDECL(bool) VBoxHlpUninstallKbdHook (HAB aHab, HWND aHwnd, + unsigned long aMsg); + +#endif /* !FEQT_INCLUDED_SRC_platform_os2_VBoxHlp_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/win/Makefile.kup b/src/VBox/Frontends/VirtualBox/src/platform/win/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/win/Makefile.kup diff --git a/src/VBox/Frontends/VirtualBox/src/platform/win/UIDesktopServices_win.cpp b/src/VBox/Frontends/VirtualBox/src/platform/win/UIDesktopServices_win.cpp new file mode 100644 index 00000000..df4deef6 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/win/UIDesktopServices_win.cpp @@ -0,0 +1,88 @@ +/* $Id: UIDesktopServices_win.cpp $ */ +/** @file + * VBox Qt GUI - Qt GUI - Utility Classes and Functions specific to Windows.. + */ + +/* + * 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 + */ + +/* VBox includes */ +#include "UIDesktopServices.h" + +/* Qt includes */ +#include <QDir> +#include <QCoreApplication> +#include <QUuid> + +/* System includes */ +#include <iprt/win/shlobj.h> + + +bool UIDesktopServices::createMachineShortcut(const QString & /* strSrcFile */, const QString &strDstPath, const QString &strName, const QUuid &uUuid) +{ + IShellLink *pShl = NULL; + IPersistFile *pPPF = NULL; + const QString strVBox = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + "/" + VBOX_GUI_VMRUNNER_IMAGE); + QFileInfo fi(strVBox); + QString strVBoxDir = QDir::toNativeSeparators(fi.absolutePath()); + HRESULT rc = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void**)(&pShl)); + if (FAILED(rc)) + return false; + do + { + rc = pShl->SetPath(strVBox.utf16()); + if (FAILED(rc)) + break; + rc = pShl->SetWorkingDirectory(strVBoxDir.utf16()); + if (FAILED(rc)) + break; + QString strArgs = QString("--comment \"%1\" --startvm \"%2\"").arg(strName).arg(uUuid.toString()); + rc = pShl->SetArguments(strArgs.utf16()); + if (FAILED(rc)) + break; + QString strDesc = QString("Starts the VirtualBox machine %1").arg(strName); + rc = pShl->SetDescription(strDesc.utf16()); + if (FAILED(rc)) + break; + rc = pShl->QueryInterface(IID_IPersistFile, (void**)&pPPF); + if (FAILED(rc)) + break; + QString strLink = QString("%1\\%2.lnk").arg(strDstPath).arg(strName); + rc = pPPF->Save(strLink.utf16(), TRUE); + } while(0); + if (pPPF) + pPPF->Release(); + if (pShl) + pShl->Release(); + return SUCCEEDED(rc); +} + +bool UIDesktopServices::openInFileManager(const QString &strFile) +{ + QFileInfo fi(strFile); + QString strTmp = QDir::toNativeSeparators(fi.absolutePath()); + + intptr_t rc = (intptr_t)ShellExecute(NULL, L"explore", strTmp.utf16(), NULL, NULL, SW_SHOWNORMAL); + + return rc > 32 ? true : false; +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/win/VBoxUtils-win.cpp b/src/VBox/Frontends/VirtualBox/src/platform/win/VBoxUtils-win.cpp new file mode 100644 index 00000000..7bd7c440 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/win/VBoxUtils-win.cpp @@ -0,0 +1,128 @@ +/* $Id: VBoxUtils-win.cpp $ */ +/** @file + * VBox Qt GUI - Declarations of utility classes and functions for handling Windows specific tasks. + */ + +/* + * 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 + */ + +/* GUI includes: */ +#include "VBoxUtils-win.h" + + +/** Namespace for native window sub-system functions. */ +namespace NativeWindowSubsystem +{ + /** Enumerates visible always-on-top (top-most) windows. */ + BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam) RT_NOTHROW_PROTO; + /** Contains visible top-most-window rectangles. */ + QList<QRect> topMostRects; +} + +BOOL CALLBACK NativeWindowSubsystem::EnumWindowsProc(HWND hWnd, LPARAM) RT_NOTHROW_DEF +{ + /* Ignore NULL HWNDs: */ + if (!hWnd) + return TRUE; + + /* Ignore hidden windows: */ + if (!IsWindowVisible(hWnd)) + return TRUE; + + /* Get window style: */ + LONG uStyle = GetWindowLong(hWnd, GWL_STYLE); + /* Ignore minimized windows: */ + if (uStyle & WS_MINIMIZE) + return TRUE; + + /* Get extended window style: */ + LONG uExtendedStyle = GetWindowLong(hWnd, GWL_EXSTYLE); + /* Ignore non-top-most windows: */ + if (!(uExtendedStyle & WS_EX_TOPMOST)) + return TRUE; + + /* Get that window rectangle: */ + RECT rect; + GetWindowRect(hWnd, &rect); + topMostRects << QRect(QPoint(rect.left, rect.top), QPoint(rect.right - 1, rect.bottom - 1)); + + /* Proceed to the next window: */ + return TRUE; +} + +const QRegion NativeWindowSubsystem::areaCoveredByTopMostWindows() +{ + /* Prepare the top-most region: */ + QRegion topMostRegion; + /* Initialize the list of the top-most rectangles: */ + topMostRects.clear(); + /* Populate the list of top-most rectangles: */ + EnumWindows((WNDENUMPROC)EnumWindowsProc, 0); + /* Update the top-most region with top-most rectangles: */ + for (int iRectIndex = 0; iRectIndex < topMostRects.size(); ++iRectIndex) + topMostRegion += topMostRects[iRectIndex]; + /* Return top-most region: */ + return topMostRegion; +} + +const void NativeWindowSubsystem::setScreenSaverActive(BOOL fDisableScreenSaver) +{ + BOOL fIsActive; + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &fIsActive, 0); + if (fIsActive == !fDisableScreenSaver) + return; + //printf("before %d\n", fIsActive); + + SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, !fDisableScreenSaver, NULL, 0); + + SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, &fIsActive, 0); + /*if (fIsActive == !fDisableScreenSaver) + printf("success %d %d\n", fIsActive, fDisableScreenSaver); +*/ +} + +BOOL NativeWindowSubsystem::ShutdownBlockReasonCreateAPI(HWND hWnd, LPCWSTR pwszReason) +{ + BOOL fResult = FALSE; + typedef BOOL(WINAPI *PFNSHUTDOWNBLOCKREASONCREATE)(HWND hWnd, LPCWSTR pwszReason); + + PFNSHUTDOWNBLOCKREASONCREATE pfn = (PFNSHUTDOWNBLOCKREASONCREATE)GetProcAddress( + GetModuleHandle(L"User32.dll"), "ShutdownBlockReasonCreate"); + _ASSERTE(pfn); + if (pfn) + fResult = pfn(hWnd, pwszReason); + return fResult; +} + +bool NativeWindowSubsystem::WinActivateWindow(WId wId, bool) +{ + bool fResult = true; + HWND handle = (HWND)wId; + + if (IsIconic(handle)) + fResult &= !!ShowWindow(handle, SW_RESTORE); + else if (!IsWindowVisible(handle)) + fResult &= !!ShowWindow(handle, SW_SHOW); + + fResult &= !!SetForegroundWindow(handle); + return fResult; +} diff --git a/src/VBox/Frontends/VirtualBox/src/platform/win/VBoxUtils-win.h b/src/VBox/Frontends/VirtualBox/src/platform/win/VBoxUtils-win.h new file mode 100644 index 00000000..6153684e --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/win/VBoxUtils-win.h @@ -0,0 +1,57 @@ +/* $Id: VBoxUtils-win.h $ */ +/** @file + * VBox Qt GUI - Declarations of utility classes and functions for handling Windows specific tasks. + */ + +/* + * 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 + */ + +#ifndef FEQT_INCLUDED_SRC_platform_win_VBoxUtils_win_h +#define FEQT_INCLUDED_SRC_platform_win_VBoxUtils_win_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* Qt includes: */ +#include <QRegion> + +/* GUI includes: */ +#include "UILibraryDefs.h" + +/* External includes: */ +#include <iprt/win/windows.h> + +/* Namespace for native window sub-system functions: */ +namespace NativeWindowSubsystem +{ + /* Returns area covered by visible always-on-top (top-most) windows: */ + SHARED_LIBRARY_STUFF const QRegion areaCoveredByTopMostWindows(); + SHARED_LIBRARY_STUFF const void setScreenSaverActive(BOOL fDisableScreenSaver); + + /** Wraps WinAPI ShutdownBlockReasonCreate function. */ + SHARED_LIBRARY_STUFF BOOL ShutdownBlockReasonCreateAPI(HWND hWnd, LPCWSTR pwszReason); + + /** Activates window with certain @a wId, @a fSwitchDesktop if requested. */ + bool WinActivateWindow(WId wId, bool fSwitchDesktop); +} + +#endif /* !FEQT_INCLUDED_SRC_platform_win_VBoxUtils_win_h */ diff --git a/src/VBox/Frontends/VirtualBox/src/platform/win/VirtualBox.VisualElementsManifest.xml b/src/VBox/Frontends/VirtualBox/src/platform/win/VirtualBox.VisualElementsManifest.xml new file mode 100644 index 00000000..2a511ebf --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/win/VirtualBox.VisualElementsManifest.xml @@ -0,0 +1,9 @@ +<Application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <VisualElements + BackgroundColor="#447aaa" + ShowNameOnSquare150x150Logo="off" + ForegroundText="light" + Square150x150Logo="VirtualBox_150px.png" + Square70x70Logo="VirtualBox_70px.png" + /> +</Application> diff --git a/src/VBox/Frontends/VirtualBox/src/platform/win/WinKeyboard.cpp b/src/VBox/Frontends/VirtualBox/src/platform/win/WinKeyboard.cpp new file mode 100644 index 00000000..a50c1f79 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/win/WinKeyboard.cpp @@ -0,0 +1,310 @@ +/* $Id: WinKeyboard.cpp $ */ +/** @file + * VBox Qt GUI - Declarations of utility functions for handling Windows Keyboard specific tasks. + */ + +/* + * Copyright (C) 2014-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 + */ + +/* Defines: */ +#define LOG_GROUP LOG_GROUP_GUI + +/* GUI includes: */ +#include "WinKeyboard.h" + +/* Other VBox includes: */ +#include <iprt/assert.h> +#include <VBox/log.h> + +/* External includes: */ +#include <stdio.h> + + +/* Beautification of log output */ +#define VBOX_BOOL_TO_STR_STATE(x) (x) ? "ON" : "OFF" +#define VBOX_CONTROL_TO_STR_NAME(x) ((x == VK_CAPITAL) ? "CAPS" : (x == VK_SCROLL ? "SCROLL" : ((x == VK_NUMLOCK) ? "NUM" : "UNKNOWN"))) + +/* A structure that contains internal control state representation */ +typedef struct VBoxModifiersState_t { + bool fNumLockOn; /** A state of NUM LOCK */ + bool fCapsLockOn; /** A state of CAPS LOCK */ + bool fScrollLockOn; /** A state of SCROLL LOCK */ +} VBoxModifiersState_t; + +/** + * Get current state of a keyboard modifier. + * + * @param idModifier Modifier to examine (VK_CAPITAL, VK_SCROLL or VK_NUMLOCK) + * + * @returns modifier state or asserts if wrong modifier is specified. + */ +static bool winGetModifierState(int idModifier) +{ + AssertReturn((idModifier == VK_CAPITAL) || (idModifier == VK_SCROLL) || (idModifier == VK_NUMLOCK), false); + return (GetKeyState(idModifier) & 0x0001) != 0; +} + +/** + * Set current state of a keyboard modifier. + * + * @param idModifier Modifier to set (VK_CAPITAL, VK_SCROLL or VK_NUMLOCK) + * @param fState State to set + */ +static void winSetModifierState(int idModifier, bool fState) +{ + AssertReturnVoid((idModifier == VK_CAPITAL) || (idModifier == VK_SCROLL) || (idModifier == VK_NUMLOCK)); + + /* If the modifier is already in desired state, just do nothing. Otherwise, toggle it. */ + if (winGetModifierState(idModifier) != fState) + { + /* Simulate KeyUp+KeyDown keystroke */ + keybd_event(idModifier, 0, KEYEVENTF_EXTENDEDKEY, 0); + keybd_event(idModifier, 0, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0); + + /* Process posted above keyboard events immediately: */ + MSG msg; + while (::PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE)) + ::DispatchMessage(&msg); + + LogRel2(("HID LEDs sync: setting %s state to %s (0x%X).\n", + VBOX_CONTROL_TO_STR_NAME(idModifier), VBOX_BOOL_TO_STR_STATE(fState), MapVirtualKey(idModifier, MAPVK_VK_TO_VSC))); + } + else + { + LogRel2(("HID LEDs sync: setting %s state: skipped: state is already %s (0x%X).\n", + VBOX_CONTROL_TO_STR_NAME(idModifier), VBOX_BOOL_TO_STR_STATE(fState), MapVirtualKey(idModifier, MAPVK_VK_TO_VSC))); + } +} + +/** Set all HID LEDs at once. */ +static bool winSetHidLeds(bool fNumLockOn, bool fCapsLockOn, bool fScrollLockOn) +{ + winSetModifierState(VK_NUMLOCK, fNumLockOn); + winSetModifierState(VK_CAPITAL, fCapsLockOn); + winSetModifierState(VK_SCROLL, fScrollLockOn); + return true; +} + +/** Check if specified LED states correspond to the system modifier states. */ +bool winHidLedsInSync(bool fNumLockOn, bool fCapsLockOn, bool fScrollLockOn) +{ + if (winGetModifierState(VK_NUMLOCK) != fNumLockOn) + return false; + + if (winGetModifierState(VK_CAPITAL) != fCapsLockOn) + return false; + + if (winGetModifierState(VK_SCROLL) != fScrollLockOn) + return false; + + return true; +} + +/** + * Allocate memory and store modifier states there. + * + * @returns allocated memory witch contains modifier states or NULL. + */ +void * WinHidDevicesKeepLedsState(void) +{ + VBoxModifiersState_t *pState; + + pState = (VBoxModifiersState_t *)malloc(sizeof(VBoxModifiersState_t)); + if (pState) + { + pState->fNumLockOn = winGetModifierState(VK_NUMLOCK); + pState->fCapsLockOn = winGetModifierState(VK_CAPITAL); + pState->fScrollLockOn = winGetModifierState(VK_SCROLL); + + LogRel2(("HID LEDs sync: host state captured: NUM(%s) CAPS(%s) SCROLL(%s)\n", + VBOX_BOOL_TO_STR_STATE(pState->fNumLockOn), + VBOX_BOOL_TO_STR_STATE(pState->fCapsLockOn), + VBOX_BOOL_TO_STR_STATE(pState->fScrollLockOn))); + + return (void *)pState; + } + + LogRel2(("HID LEDs sync: unable to allocate memory for HID LEDs synchronization data. LEDs sync will be disabled.\n")); + + return NULL; +} + +/** + * Restore host modifier states and free memory. + */ +void WinHidDevicesApplyAndReleaseLedsState(void *pData) +{ + VBoxModifiersState_t *pState = (VBoxModifiersState_t *)pData; + + if (pState) + { + LogRel2(("HID LEDs sync: attempt to restore host state: NUM(%s) CAPS(%s) SCROLL(%s)\n", + VBOX_BOOL_TO_STR_STATE(pState->fNumLockOn), + VBOX_BOOL_TO_STR_STATE(pState->fCapsLockOn), + VBOX_BOOL_TO_STR_STATE(pState->fScrollLockOn))); + + if (winSetHidLeds(pState->fNumLockOn, pState->fCapsLockOn, pState->fScrollLockOn)) + LogRel2(("HID LEDs sync: host state restored\n")); + else + LogRel2(("HID LEDs sync: host state restore failed\n")); + + free(pState); + } +} + +/** + * Broadcast HID modifier states. + * + * @param fNumLockOn NUM LOCK state + * @param fCapsLockOn CAPS LOCK state + * @param fScrollLockOn SCROLL LOCK state + */ +void WinHidDevicesBroadcastLeds(bool fNumLockOn, bool fCapsLockOn, bool fScrollLockOn) +{ + LogRel2(("HID LEDs sync: start broadcast guest modifier states: NUM(%s) CAPS(%s) SCROLL(%s)\n", + VBOX_BOOL_TO_STR_STATE(fNumLockOn), + VBOX_BOOL_TO_STR_STATE(fCapsLockOn), + VBOX_BOOL_TO_STR_STATE(fScrollLockOn))); + + if (winSetHidLeds(fNumLockOn, fCapsLockOn, fScrollLockOn)) + LogRel2(("HID LEDs sync: broadcast completed\n")); + else + LogRel2(("HID LEDs sync: broadcast failed\n")); +} + +/** @brief doesCurrentLayoutHaveAltGr + * + * @return true if this keyboard layout has an AltGr key, false otherwise + * Check to see whether the current keyboard layout actually has an AltGr key + * by checking whether any of the keys which might do produce a symbol when + * AltGr (Control + Alt) is depressed. Generally this loop will exit pretty + * early (it exits on the first iteration for my German layout). If there is + * no AltGr key in the layout then it will run right through, but that should + * hopefully not happen very often. + * + * In theory we could do this once and cache the result, but that involves + * tracking layout switches to invalidate the cache, and I don't think that the + * added complexity is worth the price. */ +static bool doesCurrentLayoutHaveAltGr() +{ + /* Keyboard state array with VK_CONTROL and VK_MENU depressed. */ + static const BYTE s_auKeyStates[256] = + { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x80, 0x80 }; + WORD ach; + unsigned i; + + for (i = '0'; i <= VK_OEM_102; ++i) + { + if (ToAscii(i, 0, s_auKeyStates, &ach, 0)) + break; + /* Skip ranges of virtual keys which are undefined or not relevant. */ + if (i == '9') + i = 'A' - 1; + if (i == 'Z') + i = VK_OEM_1 - 1; + if (i == VK_OEM_3) + i = VK_OEM_4 - 1; + if (i == VK_OEM_8) + i = VK_OEM_102 - 1; + } + if (i > VK_OEM_102) + return false; + return true; +} + +void WinAltGrMonitor::updateStateFromKeyEvent(unsigned iDownScanCode, + bool fKeyDown, bool fExtendedKey) +{ + LONG messageTime = GetMessageTime(); + /* We do not want the make/break: */ + AssertRelease(~iDownScanCode & 0x80); + /* Depending on m_enmFakeControlDetectionState: */ + switch (m_enmFakeControlDetectionState) + { + case NONE: + case FAKE_CONTROL_DOWN: + if ( iDownScanCode == 0x1D /* left control */ + && fKeyDown + && !fExtendedKey) + m_enmFakeControlDetectionState = LAST_EVENT_WAS_LEFT_CONTROL_DOWN; + else + m_enmFakeControlDetectionState = NONE; + break; + case LAST_EVENT_WAS_LEFT_CONTROL_DOWN: + if ( iDownScanCode == 0x38 /* Alt */ + && fKeyDown + && fExtendedKey + && m_timeOfLastKeyEvent == messageTime + && doesCurrentLayoutHaveAltGr()) + { + m_enmFakeControlDetectionState = FAKE_CONTROL_DOWN; + break; + } + else + m_enmFakeControlDetectionState = LEFT_CONTROL_DOWN; + RT_FALL_THRU(); + case LEFT_CONTROL_DOWN: + if ( iDownScanCode == 0x1D /* left control */ + && !fKeyDown + && !fExtendedKey) + m_enmFakeControlDetectionState = NONE; + break; + default: + AssertReleaseMsgFailed(("Unknown AltGr detection state.\n")); + } + m_timeOfLastKeyEvent = messageTime; +} + +bool WinAltGrMonitor::isLeftControlReleaseNeeded() const +{ + return m_enmFakeControlDetectionState == FAKE_CONTROL_DOWN; +} + +bool WinAltGrMonitor::isCurrentEventDefinitelyFake(unsigned iDownScanCode, + bool fKeyDown, + bool fExtendedKey) const +{ + if (iDownScanCode != 0x1d /* scan code: Control */ || fExtendedKey) + return false; + + LONG messageTime = GetMessageTime(); + MSG peekMsg; + if (!PeekMessage(&peekMsg, NULL, WM_KEYFIRST, WM_KEYLAST, PM_NOREMOVE)) + return false; + if (messageTime != (LONG)peekMsg.time) + return false; + + if ( fKeyDown + && peekMsg.message != WM_KEYDOWN + && peekMsg.message != WM_SYSKEYDOWN) + return false; + if ( !fKeyDown + && peekMsg.message != WM_KEYUP + && peekMsg.message != WM_SYSKEYUP) + return false; + if ( (RT_HIWORD(peekMsg.lParam) & 0xFF) != 0x38 /* scan code: Alt */ + || !(RT_HIWORD(peekMsg.lParam) & KF_EXTENDED)) + return false; + if (!doesCurrentLayoutHaveAltGr()) + return false; + return true; +} diff --git a/src/VBox/Frontends/VirtualBox/src/platform/win/WinKeyboard.h b/src/VBox/Frontends/VirtualBox/src/platform/win/WinKeyboard.h new file mode 100644 index 00000000..c9eb5c6b --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/win/WinKeyboard.h @@ -0,0 +1,102 @@ +/* $Id: WinKeyboard.h $ */ +/** @file + * VBox Qt GUI - Declarations of utility functions for handling Windows Keyboard specific tasks. + */ + +/* + * Copyright (C) 2014-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 FEQT_INCLUDED_SRC_platform_win_WinKeyboard_h +#define FEQT_INCLUDED_SRC_platform_win_WinKeyboard_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* GUI includes: */ +#include "UILibraryDefs.h" + +/* Other VBox includes: */ +#include <iprt/win/windows.h> + +SHARED_LIBRARY_STUFF void * WinHidDevicesKeepLedsState(void); +SHARED_LIBRARY_STUFF void WinHidDevicesApplyAndReleaseLedsState(void *pData); +SHARED_LIBRARY_STUFF void WinHidDevicesBroadcastLeds(bool fNumLockOn, bool fCapsLockOn, bool fScrollLockOn); + +SHARED_LIBRARY_STUFF bool winHidLedsInSync(bool fNumLockOn, bool fCapsLockOn, bool fScrollLockOn); + +/** Helper class to deal with Windows AltGr handling. + * + * Background: Windows sends AltGr key down and up events as two events: a + * left control event and a right alt one. Since the left control event does + * not correspond to actually pressing or releasing the left control key we + * would like to detect it and handle it. This class monitors all key down and + * up events and if it detects that a left control down event has been sendt + * although left control should be up it tells us to insert a left control up + * event into the event stream. While this does not let us filter out the + * unwanted event at source, it should still make guest system keyboard handling + * work correctly. */ +class SHARED_LIBRARY_STUFF WinAltGrMonitor +{ +public: + + /** Constructor. */ + WinAltGrMonitor() : m_enmFakeControlDetectionState(NONE), m_timeOfLastKeyEvent(0) {} + + /** All key events should be fed to this method. + * @param iDownScanCode the scan code stripped of the make/break bit + * @param fKeyDown is this a key down event? + * @param fExtended is this an extended scan code? */ + void updateStateFromKeyEvent(unsigned iDownScanCode, bool fKeyDown, bool fExtended); + + /** Do we need to insert a left control up into the stream? */ + bool isLeftControlReleaseNeeded() const; + + /** Can we tell for sure at this point that the current message is a fake + * control event? This method might fail to recognise a fake event, but + * should never incorrectly flag a non-fake one. + * @note We deliberately do not call this from the host combination editor + * in an attempt to ensure that the other code path also gets enough + * test coverage. + */ + bool isCurrentEventDefinitelyFake(unsigned iDownScanCode, + bool fKeyDown, + bool fExtendedKey) const; + +private: + + /** State detection for fake control events which we may have missed. */ + enum + { + /** No interesting state. */ + NONE, + /** The last keypress might be a fake control. */ + LAST_EVENT_WAS_LEFT_CONTROL_DOWN, + /** Left control is down, so we ignore fake control events. */ + LEFT_CONTROL_DOWN, + /** A fake control down event and no up was passed to the guest. */ + FAKE_CONTROL_DOWN + } m_enmFakeControlDetectionState; + LONG m_timeOfLastKeyEvent; +}; + +#endif /* !FEQT_INCLUDED_SRC_platform_win_WinKeyboard_h */ + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/x11/Makefile.kup b/src/VBox/Frontends/VirtualBox/src/platform/x11/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/x11/Makefile.kup diff --git a/src/VBox/Frontends/VirtualBox/src/platform/x11/UIDesktopServices_x11.cpp b/src/VBox/Frontends/VirtualBox/src/platform/x11/UIDesktopServices_x11.cpp new file mode 100644 index 00000000..ca1a6c1e --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/x11/UIDesktopServices_x11.cpp @@ -0,0 +1,86 @@ +/* $Id: UIDesktopServices_x11.cpp $ */ +/** @file + * VBox Qt GUI - Qt GUI - Utility Classes and Functions specific to X11.. + */ + +/* + * 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 + */ + +/* VBox includes */ +#include "UIDesktopServices.h" + +/* Qt includes */ +#include <QCoreApplication> +#include <QDesktopServices> +#include <QDir> +#include <QFile> +#include <QTextStream> +#include <QUrl> + + +bool UIDesktopServices::createMachineShortcut(const QString & /* strSrcFile */, const QString &strDstPath, const QString &strName, const QUuid &uUuid) +{ + QFile link(strDstPath + QDir::separator() + strName + ".desktop"); + if (link.open(QFile::WriteOnly | QFile::Truncate)) + { + const QString strVBox = QDir::toNativeSeparators(QCoreApplication::applicationDirPath() + "/" + VBOX_GUI_VMRUNNER_IMAGE); + QTextStream out(&link); +#ifndef VBOX_IS_QT6_OR_LATER /* QTextStream defaults to UTF-8 only since qt6 */ + out.setCodec("UTF-8"); +#endif +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) +# define QT_ENDL Qt::endl +#else +# define QT_ENDL endl +#endif + /* Create a link which starts VirtualBox with the machine uuid. */ + out << "[Desktop Entry]" << QT_ENDL + << "Encoding=UTF-8" << QT_ENDL + << "Version=1.0" << QT_ENDL + << "Name=" << strName << QT_ENDL + << "Comment=Starts the VirtualBox machine " << strName << QT_ENDL + << "Type=Application" << QT_ENDL + << "Exec=" << strVBox << " --comment \"" << strName << "\" --startvm \"" << uUuid.toString() << "\"" << QT_ENDL + << "Icon=virtualbox-vbox.png" << QT_ENDL; + /* This would be a real file link entry, but then we could also simply + * use a soft link (on most UNIX fs): + out << "[Desktop Entry]" << QT_ENDL + << "Encoding=UTF-8" << QT_ENDL + << "Version=1.0" << QT_ENDL + << "Name=" << strName << QT_ENDL + << "Type=Link" << QT_ENDL + << "Icon=virtualbox-vbox.png" << QT_ENDL + */ + link.setPermissions(link.permissions() | QFile::ExeOwner); + /** @todo r=bird: check status here perhaps, might've run out of disk space or + * some such thing... */ + return true; + } + return false; +} + +bool UIDesktopServices::openInFileManager(const QString &strFile) +{ + QFileInfo fi(strFile); + return QDesktopServices::openUrl(QUrl("file://" + QDir::toNativeSeparators(fi.absolutePath()), QUrl::TolerantMode)); +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/x11/VBoxUtils-x11.cpp b/src/VBox/Frontends/VirtualBox/src/platform/x11/VBoxUtils-x11.cpp new file mode 100644 index 00000000..e8b8db43 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/x11/VBoxUtils-x11.cpp @@ -0,0 +1,679 @@ +/* $Id: VBoxUtils-x11.cpp $ */ +/** @file + * VBox Qt GUI - Declarations of utility classes and functions for handling X11 specific tasks. + */ + +/* + * 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 + */ + +/* Qt includes: */ +#ifdef VBOX_WITH_SCREENSAVER_CONTROL +#include <QtDBus/QDBusConnection> +#include <QtDBus/QDBusConnectionInterface> +#include <QtDBus/QDBusInterface> +#include <QtDBus/QDBusReply> +#include <QtXml/QDomDocument> +#include <QtXml/QDomElement> +#endif +#include <QWidget> +#ifdef VBOX_IS_QT6_OR_LATER /** @todo qt6: ... */ +# include <QGuiApplication> +#else +# include <QX11Info> +#endif + +/* GUI includes: */ +#include "VBoxUtils-x11.h" + +/* Other VBox includes: */ +#include <iprt/assert.h> +#include <VBox/log.h> + +/* Other includes: */ +#undef BOOL /* Undefine the VBox/com/defs.h variant */ +#define BOOL X11BOOL /* Typedef'ed in Xmd.h via dpms.h, causing -Wpch-invalid to trigger. */ +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/extensions/dpms.h> +#undef BOOL /* Restore the VBox/com/defs.h variant */ +#define BOOL PRBool + + +bool NativeWindowSubsystem::X11IsCompositingManagerRunning() +{ + /* For each screen it manage, compositing manager MUST acquire ownership + * of a selection named _NET_WM_CM_Sn, where n is the screen number. */ + Display *pDisplay = NativeWindowSubsystem::X11GetDisplay(); + Atom atom_property_name = XInternAtom(pDisplay, "_NET_WM_CM_S0", True); + return XGetSelectionOwner(pDisplay, atom_property_name); +} + +X11WMType NativeWindowSubsystem::X11WindowManagerType() +{ + /* Ask if root-window supports check for WM name: */ + Display *pDisplay = NativeWindowSubsystem::X11GetDisplay(); + Atom atom_property_name; + Atom atom_returned_type; + int iReturnedFormat; + unsigned long ulReturnedItemCount; + unsigned long ulDummy; + unsigned char *pcData = 0; + X11WMType wmType = X11WMType_Unknown; + atom_property_name = XInternAtom(pDisplay, "_NET_SUPPORTING_WM_CHECK", True); + if (XGetWindowProperty(pDisplay, NativeWindowSubsystem::X11GetAppRootWindow(), atom_property_name, + 0, 512, False, XA_WINDOW, &atom_returned_type, + &iReturnedFormat, &ulReturnedItemCount, &ulDummy, &pcData) == Success) + { + Window WMWindow = None; + if (atom_returned_type == XA_WINDOW && iReturnedFormat == 32) + WMWindow = *((Window*)pcData); + if (pcData) + XFree(pcData); + if (WMWindow != None) + { + /* Ask root-window for WM name: */ + atom_property_name = XInternAtom(pDisplay, "_NET_WM_NAME", True); + Atom utf8Atom = XInternAtom(pDisplay, "UTF8_STRING", True); + if (XGetWindowProperty(pDisplay, WMWindow, atom_property_name, + 0, 512, False, utf8Atom, &atom_returned_type, + &iReturnedFormat, &ulReturnedItemCount, &ulDummy, &pcData) == Success) + { + /** @todo r=bird: 6 QString conversions cannot be very efficient. */ + if (QString((const char*)pcData).contains("Compiz", Qt::CaseInsensitive)) + wmType = X11WMType_Compiz; + else + if (QString((const char*)pcData).contains("GNOME Shell", Qt::CaseInsensitive)) + wmType = X11WMType_GNOMEShell; + else + if (QString((const char*)pcData).contains("KWin", Qt::CaseInsensitive)) + wmType = X11WMType_KWin; + else + if (QString((const char*)pcData).contains("Metacity", Qt::CaseInsensitive)) + wmType = X11WMType_Metacity; + else + if (QString((const char*)pcData).contains("Mutter", Qt::CaseInsensitive)) + wmType = X11WMType_Mutter; + else + if (QString((const char*)pcData).contains("Xfwm4", Qt::CaseInsensitive)) + wmType = X11WMType_Xfwm4; + if (pcData) + XFree(pcData); + } + } + } + return wmType; +} + +bool NativeWindowSubsystem::X11CheckExtension(const char *pExtensionName) +{ + /* Check extension: */ + Display *pDisplay = NativeWindowSubsystem::X11GetDisplay(); + int major_opcode; + int first_event; + int first_error; + return XQueryExtension(pDisplay, pExtensionName, &major_opcode, &first_event, &first_error); +} + +#ifdef VBOX_WITH_SCREENSAVER_CONTROL +bool X11CheckDBusConnection(const QDBusConnection &connection) +{ + if (!connection.isConnected()) + { + const QDBusError lastError = connection.lastError(); + if (lastError.isValid()) + { + LogRel(("QDBus error. Could not connect to D-Bus server: %s: %s\n", + lastError.name().toUtf8().constData(), + lastError.message().toUtf8().constData())); + } + else + LogRel(("QDBus error. Could not connect to D-Bus server: Unable to load dbus libraries\n")); + return false; + } + return true; +} + +QStringList X11FindDBusScreenSaverServices(const QDBusConnection &connection) +{ + QStringList serviceNames; + + QDBusReply<QStringList> replyr = connection.interface()->registeredServiceNames(); + if (!replyr.isValid()) + { + const QDBusError replyError = replyr.error(); + LogRel(("QDBus error. Could not query registered service names %s %s", + replyError.name().toUtf8().constData(), replyError.message().toUtf8().constData())); + return serviceNames; + } + + for (int i = 0; i < replyr.value().size(); ++i) + { + const QString strServiceName = replyr.value()[i]; + if (strServiceName.contains("screensaver", Qt::CaseInsensitive)) + serviceNames << strServiceName; + } + if (serviceNames.isEmpty()) + LogRel(("QDBus error. No screen saver service found among registered DBus services.")); + + return serviceNames; +} +#endif /* VBOX_WITH_SCREENSAVER_CONTROL */ + +bool NativeWindowSubsystem::X11CheckDBusScreenSaverServices() +{ +#ifdef VBOX_WITH_SCREENSAVER_CONTROL + QDBusConnection connection = QDBusConnection::sessionBus(); + if (!X11CheckDBusConnection(connection)) + return false; + + QDBusReply<QStringList> replyr = connection.interface()->registeredServiceNames(); + if (!replyr.isValid()) + { + const QDBusError replyError = replyr.error(); + LogRel(("QDBus error. Could not query registered service names %s %s", + replyError.name().toUtf8().constData(), replyError.message().toUtf8().constData())); + return false; + } + for (int i = 0; i < replyr.value().size(); ++i) + { + const QString strServiceName = replyr.value()[i]; + if (strServiceName.contains("screensaver", Qt::CaseInsensitive)) + return true; + } + LogRel(("QDBus error. No screen saver service found among registered DBus services.")); +#endif /* VBOX_WITH_SCREENSAVER_CONTROL */ + return false; +} + +#ifdef VBOX_WITH_SCREENSAVER_CONTROL +void X11IntrospectInterfaceNode(const QDomElement &interface, + const QString &strServiceName, + QVector<X11ScreenSaverInhibitMethod*> &methods) +{ + QDomElement child = interface.firstChildElement(); + while (!child.isNull()) + { + if (child.tagName() == "method" && child.attribute("name") == "Inhibit") + { + X11ScreenSaverInhibitMethod *newMethod = new X11ScreenSaverInhibitMethod; + newMethod->m_iCookie = 0; + newMethod->m_strServiceName = strServiceName; + newMethod->m_strInterface = interface.attribute("name"); + newMethod->m_strPath = "/"; + newMethod->m_strPath.append(interface.attribute("name")); + newMethod->m_strPath.replace(".", "/"); + methods.append(newMethod); + } + child = child.nextSiblingElement(); + } +} + +void X11IntrospectServices(const QDBusConnection &connection, + const QString &strService, + const QString &strPath, + QVector<X11ScreenSaverInhibitMethod*> &methods) +{ + QDBusMessage call = QDBusMessage::createMethodCall(strService, strPath.isEmpty() ? QLatin1String("/") : strPath, + QLatin1String("org.freedesktop.DBus.Introspectable"), + QLatin1String("Introspect")); + QDBusReply<QString> xmlReply = connection.call(call); + + if (!xmlReply.isValid()) + return; + + QDomDocument doc; + doc.setContent(xmlReply); + QDomElement node = doc.documentElement(); + QDomElement child = node.firstChildElement(); + while (!child.isNull()) + { + if (child.tagName() == QLatin1String("node")) + { + QString subPath = strPath + QLatin1Char('/') + child.attribute(QLatin1String("name")); + X11IntrospectServices(connection, strService, subPath, methods); + } + else if (child.tagName() == QLatin1String("interface")) + X11IntrospectInterfaceNode(child, strService, methods); + child = child.nextSiblingElement(); + } +} +#endif /* VBOX_WITH_SCREENSAVER_CONTROL */ + +QVector<X11ScreenSaverInhibitMethod*> NativeWindowSubsystem::X11FindDBusScrenSaverInhibitMethods() +{ + QVector<X11ScreenSaverInhibitMethod*> methods; + +#ifdef VBOX_WITH_SCREENSAVER_CONTROL + QDBusConnection connection = QDBusConnection::sessionBus(); + if (!X11CheckDBusConnection(connection)) + return methods; + + QStringList services = X11FindDBusScreenSaverServices(connection); + foreach(const QString &strServiceName, services) + X11IntrospectServices(connection, strServiceName, "", methods); +#endif /* VBOX_WITH_SCREENSAVER_CONTROL */ + + return methods; +} + +void NativeWindowSubsystem::X11InhibitUninhibitScrenSaver(bool fInhibit, QVector<X11ScreenSaverInhibitMethod*> &inOutInhibitMethods) +{ +#ifdef VBOX_WITH_SCREENSAVER_CONTROL + QDBusConnection connection = QDBusConnection::sessionBus(); + if (!X11CheckDBusConnection(connection)) + return; + for (int i = 0; i < inOutInhibitMethods.size(); ++i) + { + QDBusInterface screenSaverInterface(inOutInhibitMethods[i]->m_strServiceName, inOutInhibitMethods[i]->m_strPath, + inOutInhibitMethods[i]->m_strInterface, connection); + if (!screenSaverInterface.isValid()) + { + QDBusError error = screenSaverInterface.lastError(); + LogRel(("QDBus error for service %s: %s. %s\n", + inOutInhibitMethods[i]->m_strServiceName.toUtf8().constData(), + error.name().toUtf8().constData(), + error.message().toUtf8().constData())); + continue; + } + QDBusReply<uint> reply; + if (fInhibit) + { + reply = screenSaverInterface.call("Inhibit", "Oracle VirtualBox", "ScreenSaverInhibit"); + if (reply.isValid()) + inOutInhibitMethods[i]->m_iCookie = reply.value(); + } + else + { + reply = screenSaverInterface.call("UnInhibit", inOutInhibitMethods[i]->m_iCookie); + } + if (!reply.isValid()) + { + QDBusError error = reply.error(); + LogRel(("QDBus inhibition call error for service %s: %s. %s\n", + inOutInhibitMethods[i]->m_strServiceName.toUtf8().constData(), + error.name().toUtf8().constData(), + error.message().toUtf8().constData())); + } + } +#else + Q_UNUSED(fInhibit); + Q_UNUSED(inOutInhibitMethods); +#endif /* VBOX_WITH_SCREENSAVER_CONTROL */ +} + +char *XXGetProperty(Display *pDpy, Window windowHandle, Atom propType, const char *pszPropName) +{ + Atom propNameAtom = XInternAtom(pDpy, pszPropName, True /* only_if_exists */); + if (propNameAtom == None) + return NULL; + + Atom actTypeAtom = None; + int actFmt = 0; + unsigned long nItems = 0; + unsigned long nBytesAfter = 0; + unsigned char *propVal = NULL; + int rc = XGetWindowProperty(pDpy, windowHandle, propNameAtom, + 0, LONG_MAX, False /* delete */, + propType, &actTypeAtom, &actFmt, + &nItems, &nBytesAfter, &propVal); + if (rc != Success) + return NULL; + + return reinterpret_cast<char*>(propVal); +} + +bool XXSendClientMessage(Display *pDpy, Window windowHandle, const char *pszMsg, + unsigned long aData0 = 0, unsigned long aData1 = 0, + unsigned long aData2 = 0, unsigned long aData3 = 0, + unsigned long aData4 = 0) +{ + Atom msgAtom = XInternAtom(pDpy, pszMsg, True /* only_if_exists */); + if (msgAtom == None) + return false; + + XEvent ev; + + ev.xclient.type = ClientMessage; + ev.xclient.serial = 0; + ev.xclient.send_event = True; + ev.xclient.display = pDpy; + ev.xclient.window = windowHandle; + ev.xclient.message_type = msgAtom; + + /* Always send as 32 bit for now: */ + ev.xclient.format = 32; + ev.xclient.data.l[0] = aData0; + ev.xclient.data.l[1] = aData1; + ev.xclient.data.l[2] = aData2; + ev.xclient.data.l[3] = aData3; + ev.xclient.data.l[4] = aData4; + + return XSendEvent(pDpy, DefaultRootWindow(pDpy), False, + SubstructureRedirectMask, &ev) != 0; +} + +bool NativeWindowSubsystem::X11ActivateWindow(WId wId, bool fSwitchDesktop) +{ + bool fResult = true; + Display *pDisplay = NativeWindowSubsystem::X11GetDisplay(); + + if (fSwitchDesktop) + { + /* Try to find the desktop ID using the NetWM property: */ + CARD32 *pDesktop = (CARD32*)XXGetProperty(pDisplay, wId, XA_CARDINAL, "_NET_WM_DESKTOP"); + if (pDesktop == NULL) + // WORKAROUND: + // if the NetWM properly is not supported try to find + // the desktop ID using the GNOME WM property. + pDesktop = (CARD32*)XXGetProperty(pDisplay, wId, XA_CARDINAL, "_WIN_WORKSPACE"); + + if (pDesktop != NULL) + { + bool ok = XXSendClientMessage(pDisplay, DefaultRootWindow(pDisplay), "_NET_CURRENT_DESKTOP", *pDesktop); + if (!ok) + { + Log1WarningFunc(("Couldn't switch to pDesktop=%08X\n", pDesktop)); + fResult = false; + } + XFree(pDesktop); + } + else + { + Log1WarningFunc(("Couldn't find a pDesktop ID for wId=%08X\n", wId)); + fResult = false; + } + } + + bool ok = XXSendClientMessage(pDisplay, wId, "_NET_ACTIVE_WINDOW"); + fResult &= !!ok; + + XRaiseWindow(pDisplay, wId); + return fResult; +} + +bool NativeWindowSubsystem::X11SupportsFullScreenMonitorsProtocol() +{ + /* This method tests whether the current X11 window manager supports full-screen mode as we need it. + * Unfortunately the EWMH specification was not fully clear about whether we can expect to find + * all of these atoms on the _NET_SUPPORTED root window property, so we have to test with all + * interesting window managers. If this fails for a user when you think it should succeed + * they should try executing: + * xprop -root | egrep -w '_NET_WM_FULLSCREEN_MONITORS|_NET_WM_STATE|_NET_WM_STATE_FULLSCREEN' + * in an X11 terminal window. + * All three strings should be found under a property called "_NET_SUPPORTED(ATOM)". */ + + /* Using a global to get at the display does not feel right, but that is how it is done elsewhere in the code. */ + Display *pDisplay = NativeWindowSubsystem::X11GetDisplay(); + Atom atomSupported = XInternAtom(pDisplay, "_NET_SUPPORTED", + True /* only_if_exists */); + Atom atomWMFullScreenMonitors = XInternAtom(pDisplay, + "_NET_WM_FULLSCREEN_MONITORS", + True /* only_if_exists */); + Atom atomWMState = XInternAtom(pDisplay, + "_NET_WM_STATE", + True /* only_if_exists */); + Atom atomWMStateFullScreen = XInternAtom(pDisplay, + "_NET_WM_STATE_FULLSCREEN", + True /* only_if_exists */); + bool fFoundFullScreenMonitors = false; + bool fFoundState = false; + bool fFoundStateFullScreen = false; + Atom atomType; + int cFormat; + unsigned long cItems; + unsigned long cbLeft; + Atom *pAtomHints; + int rc; + unsigned i; + + if ( atomSupported == None || atomWMFullScreenMonitors == None + || atomWMState == None || atomWMStateFullScreen == None) + return false; + /* Get atom value: */ + rc = XGetWindowProperty(pDisplay, DefaultRootWindow(pDisplay), + atomSupported, 0, 0x7fffffff /*LONG_MAX*/, + False /* delete */, XA_ATOM, &atomType, + &cFormat, &cItems, &cbLeft, + (unsigned char **)&pAtomHints); + if (rc != Success) + return false; + if (pAtomHints == NULL) + return false; + if (atomType == XA_ATOM && cFormat == 32 && cbLeft == 0) + for (i = 0; i < cItems; ++i) + { + if (pAtomHints[i] == atomWMFullScreenMonitors) + fFoundFullScreenMonitors = true; + if (pAtomHints[i] == atomWMState) + fFoundState = true; + if (pAtomHints[i] == atomWMStateFullScreen) + fFoundStateFullScreen = true; + } + XFree(pAtomHints); + return fFoundFullScreenMonitors && fFoundState && fFoundStateFullScreen; +} + +bool NativeWindowSubsystem::X11SetFullScreenMonitor(QWidget *pWidget, ulong uScreenId) +{ + return XXSendClientMessage(NativeWindowSubsystem::X11GetDisplay(), + pWidget->window()->winId(), + "_NET_WM_FULLSCREEN_MONITORS", + uScreenId, uScreenId, uScreenId, uScreenId, + 1 /* Source indication (1 = normal application) */); +} + +QVector<Atom> flagsNetWmState(QWidget *pWidget) +{ + /* Get display: */ + Display *pDisplay = NativeWindowSubsystem::X11GetDisplay(); + + /* Prepare atoms: */ + QVector<Atom> resultNetWmState; + Atom net_wm_state = XInternAtom(pDisplay, "_NET_WM_STATE", True /* only if exists */); + + /* Get the size of the property data: */ + Atom actual_type; + int iActualFormat; + ulong uPropertyLength; + ulong uBytesLeft; + uchar *pPropertyData = 0; + if (XGetWindowProperty(pDisplay, pWidget->window()->winId(), + net_wm_state, 0, 0, False, XA_ATOM, &actual_type, &iActualFormat, + &uPropertyLength, &uBytesLeft, &pPropertyData) == Success && + actual_type == XA_ATOM && iActualFormat == 32) + { + resultNetWmState.resize(uBytesLeft / 4); + XFree((char*)pPropertyData); + pPropertyData = 0; + + /* Fetch all data: */ + if (XGetWindowProperty(pDisplay, pWidget->window()->winId(), + net_wm_state, 0, resultNetWmState.size(), False, XA_ATOM, &actual_type, &iActualFormat, + &uPropertyLength, &uBytesLeft, &pPropertyData) != Success) + resultNetWmState.clear(); + else if (uPropertyLength != (ulong)resultNetWmState.size()) + resultNetWmState.resize(uPropertyLength); + + /* Put it into resultNetWmState: */ + if (!resultNetWmState.isEmpty()) + memcpy(resultNetWmState.data(), pPropertyData, resultNetWmState.size() * sizeof(Atom)); + if (pPropertyData) + XFree((char*)pPropertyData); + } + + /* Return result: */ + return resultNetWmState; +} + +#if 0 // unused for now? +bool NativeWindowSubsystem::isFullScreenFlagSet(QWidget *pWidget) +{ + /* Get display: */ + Display *pDisplay = NativeWindowSubsystem::X11GetDisplay(); + + /* Prepare atoms: */ + Atom net_wm_state_fullscreen = XInternAtom(pDisplay, "_NET_WM_STATE_FULLSCREEN", True /* only if exists */); + + /* Check if flagsNetWmState(pWidget) contains full-screen flag: */ + return flagsNetWmState(pWidget).contains(net_wm_state_fullscreen); +} + +void NativeWindowSubsystem::setFullScreenFlag(QWidget *pWidget) +{ + /* Get display: */ + Display *pDisplay = NativeWindowSubsystem::X11GetDisplay(); + + /* Prepare atoms: */ + QVector<Atom> resultNetWmState = flagsNetWmState(pWidget); + Atom net_wm_state = XInternAtom(pDisplay, "_NET_WM_STATE", True /* only if exists */); + Atom net_wm_state_fullscreen = XInternAtom(pDisplay, "_NET_WM_STATE_FULLSCREEN", True /* only if exists */); + + /* Append resultNetWmState with fullscreen flag if necessary: */ + if (!resultNetWmState.contains(net_wm_state_fullscreen)) + { + resultNetWmState.append(net_wm_state_fullscreen); + /* Apply property to widget again: */ + XChangeProperty(pDisplay, pWidget->window()->winId(), + net_wm_state, XA_ATOM, 32, PropModeReplace, + (unsigned char*)resultNetWmState.data(), resultNetWmState.size()); + } +} +#endif // unused for now? + +void NativeWindowSubsystem::X11SetSkipTaskBarFlag(QWidget *pWidget) +{ + /* Get display: */ + Display *pDisplay = NativeWindowSubsystem::X11GetDisplay(); + + /* Prepare atoms: */ + QVector<Atom> resultNetWmState = flagsNetWmState(pWidget); + Atom net_wm_state = XInternAtom(pDisplay, "_NET_WM_STATE", True /* only if exists */); + Atom net_wm_state_skip_taskbar = XInternAtom(pDisplay, "_NET_WM_STATE_SKIP_TASKBAR", True /* only if exists */); + + /* Append resultNetWmState with skip-taskbar flag if necessary: */ + if (!resultNetWmState.contains(net_wm_state_skip_taskbar)) + { + resultNetWmState.append(net_wm_state_skip_taskbar); + /* Apply property to widget again: */ + XChangeProperty(pDisplay, pWidget->window()->winId(), + net_wm_state, XA_ATOM, 32, PropModeReplace, + (unsigned char*)resultNetWmState.data(), resultNetWmState.size()); + } +} + +void NativeWindowSubsystem::X11SetSkipPagerFlag(QWidget *pWidget) +{ + /* Get display: */ + Display *pDisplay = NativeWindowSubsystem::X11GetDisplay(); + + /* Prepare atoms: */ + QVector<Atom> resultNetWmState = flagsNetWmState(pWidget); + Atom net_wm_state = XInternAtom(pDisplay, "_NET_WM_STATE", True /* only if exists */); + Atom net_wm_state_skip_pager = XInternAtom(pDisplay, "_NET_WM_STATE_SKIP_PAGER", True /* only if exists */); + + /* Append resultNetWmState with skip-pager flag if necessary: */ + if (!resultNetWmState.contains(net_wm_state_skip_pager)) + { + resultNetWmState.append(net_wm_state_skip_pager); + /* Apply property to widget again: */ + XChangeProperty(pDisplay, pWidget->window()->winId(), + net_wm_state, XA_ATOM, 32, PropModeReplace, + (unsigned char*)resultNetWmState.data(), resultNetWmState.size()); + } +} + +void NativeWindowSubsystem::X11SetWMClass(QWidget *pWidget, const QString &strNameString, const QString &strClassString) +{ + /* Make sure all arguments set: */ + AssertReturnVoid(pWidget && !strNameString.isNull() && !strClassString.isNull()); + + /* Define QByteArray objects to make sure data is alive within the scope: */ + QByteArray nameByteArray; + /* Check the existence of RESOURCE_NAME env. variable and override name string if necessary: */ + const char resourceName[] = "RESOURCE_NAME"; + if (qEnvironmentVariableIsSet(resourceName)) + nameByteArray = qgetenv(resourceName); + else + nameByteArray = strNameString.toLatin1(); + QByteArray classByteArray = strClassString.toLatin1(); + + AssertReturnVoid(nameByteArray.data() && classByteArray.data()); + + XClassHint windowClass; + windowClass.res_name = nameByteArray.data(); + windowClass.res_class = classByteArray.data(); + /* Set WM_CLASS of the window to passed name and class strings: */ + XSetClassHint(NativeWindowSubsystem::X11GetDisplay(), pWidget->window()->winId(), &windowClass); +} + +void NativeWindowSubsystem::X11SetXwaylandMayGrabKeyboardFlag(QWidget *pWidget) +{ + XXSendClientMessage(NativeWindowSubsystem::X11GetDisplay(), pWidget->window()->winId(), + "_XWAYLAND_MAY_GRAB_KEYBOARD", 1); +} + +Display *NativeWindowSubsystem::X11GetDisplay() +{ +#ifdef VBOX_IS_QT6_OR_LATER /** QX11Info is replaced with QNativeInterface::QX11Application since qt6 */ + Display *pDisplay = 0; + if (qApp) + { + QNativeInterface::QX11Application *pX11App = qApp->nativeInterface<QNativeInterface::QX11Application>(); + if (pX11App) + pDisplay = pX11App->display(); + } +#else + Display *pDisplay = QX11Info::display(); +#endif + Assert(pDisplay); + return pDisplay; +} + +xcb_connection_t *NativeWindowSubsystem::X11GetConnection() +{ +#ifdef VBOX_IS_QT6_OR_LATER /** QX11Info is replaced with QNativeInterface::QX11Application since qt6 */ + xcb_connection_t *pConnection = 0; + if (qApp) + { + QNativeInterface::QX11Application *pX11App = qApp->nativeInterface<QNativeInterface::QX11Application>(); + if (pX11App) + pConnection = pX11App->connection(); + } +#else + xcb_connection_t *pConnection = QX11Info::connection(); +#endif + Assert(pConnection); + return pConnection; +} + +uint32_t NativeWindowSubsystem::X11GetAppRootWindow() +{ +#ifdef VBOX_IS_QT6_OR_LATER /** QX11Info is replaced with QNativeInterface::QX11Application since qt6 */ + Window idWindow = 0; + Display *pDisplay = NativeWindowSubsystem::X11GetDisplay(); + if (pDisplay) + idWindow = DefaultRootWindow(pDisplay); + return idWindow; +#else + return QX11Info::appRootWindow(); +#endif +} diff --git a/src/VBox/Frontends/VirtualBox/src/platform/x11/VBoxUtils-x11.h b/src/VBox/Frontends/VirtualBox/src/platform/x11/VBoxUtils-x11.h new file mode 100644 index 00000000..12b4a172 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/x11/VBoxUtils-x11.h @@ -0,0 +1,140 @@ +/* $Id: VBoxUtils-x11.h $ */ +/** @file + * VBox Qt GUI - Declarations of utility classes and functions for handling X11 specific tasks. + */ + +/* + * 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 FEQT_INCLUDED_SRC_platform_x11_VBoxUtils_x11_h +#define FEQT_INCLUDED_SRC_platform_x11_VBoxUtils_x11_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* Qt includes: */ +#include <QString> +#include <QVector> +#include <QWindow> + +/* GUI includes: */ +#include "UILibraryDefs.h" + +/** X11: Known Window Manager types. */ +enum X11WMType +{ + X11WMType_Unknown, + X11WMType_Compiz, + X11WMType_GNOMEShell, + X11WMType_KWin, + X11WMType_Metacity, + X11WMType_Mutter, + X11WMType_Xfwm4, +}; + +/** X11: Screen-saver inhibit methods. */ +struct SHARED_LIBRARY_STUFF X11ScreenSaverInhibitMethod +{ + QString m_strServiceName; + QString m_strInterface; + QString m_strPath; + uint m_iCookie; +}; + +/** X11: XCB size-hints. */ +typedef struct +{ + /** User specified flags */ + uint32_t flags; + /** User-specified position */ + int32_t x, y; + /** User-specified size */ + int32_t width, height; + /** Program-specified minimum size */ + int32_t min_width, min_height; + /** Program-specified maximum size */ + int32_t max_width, max_height; + /** Program-specified resize increments */ + int32_t width_inc, height_inc; + /** Program-specified minimum aspect ratios */ + int32_t min_aspect_num, min_aspect_den; + /** Program-specified maximum aspect ratios */ + int32_t max_aspect_num, max_aspect_den; + /** Program-specified base size */ + int32_t base_width, base_height; + /** Program-specified window gravity */ + uint32_t win_gravity; +} xcb_size_hints_t; + +/* X11 structs to avoid dragging in unnecessary X headers: */ +struct xcb_connection_t; +struct _XDisplay; + +/* Namespace for native window sub-system functions: */ +namespace NativeWindowSubsystem +{ + /** X11: Determines and returns whether the compositing manager is running. */ + bool X11IsCompositingManagerRunning(); + /** X11: Determines and returns current Window Manager type. */ + X11WMType X11WindowManagerType(); + + /** X11: Returns true if XLib extension with name @p extensionName is avaible, false otherwise. */ + bool X11CheckExtension(const char *extensionName); + + /** X11: Returns whether there are any DBus services whose name contains the substring 'screensaver'. */ + bool X11CheckDBusScreenSaverServices(); + /** X11: Returns the list of Inhibit methods found by introspecting DBus services. */ + SHARED_LIBRARY_STUFF QVector<X11ScreenSaverInhibitMethod*> X11FindDBusScrenSaverInhibitMethods(); + /** X11: Disables/enables Screen Saver through QDBus. */ + SHARED_LIBRARY_STUFF void X11InhibitUninhibitScrenSaver(bool fInhibit, QVector<X11ScreenSaverInhibitMethod*> &inOutInhibitMethods); + + /** Activates window with certain @a wId, @a fSwitchDesktop if requested. */ + bool X11ActivateWindow(WId wId, bool fSwitchDesktop); + + /** X11: Test whether the current window manager supports full screen mode. */ + SHARED_LIBRARY_STUFF bool X11SupportsFullScreenMonitorsProtocol(); + /** X11: Performs mapping of the passed @a pWidget to host-screen with passed @a uScreenId. */ + SHARED_LIBRARY_STUFF bool X11SetFullScreenMonitor(QWidget *pWidget, ulong uScreenId); + + /** X11: Sets _NET_WM_STATE_SKIP_TASKBAR flag for passed @a pWidget. */ + SHARED_LIBRARY_STUFF void X11SetSkipTaskBarFlag(QWidget *pWidget); + /** X11: Sets _NET_WM_STATE_SKIP_PAGER flag for passed @a pWidget. */ + SHARED_LIBRARY_STUFF void X11SetSkipPagerFlag(QWidget *pWidget); + + /** X11: Assigns WM_CLASS property for passed @a pWidget. */ + SHARED_LIBRARY_STUFF void X11SetWMClass(QWidget *pWidget, const QString &strNameString, const QString &strClassString); + + /** X11: Tell the WM we are well behaved wrt Xwayland keyboard-grabs. This will + * make the WM turn our grab into a Wayland shortcut inhibition request, + * so that e.g. alt+tab will get send to the VM instead of moving the + * focus away from the VM. */ + SHARED_LIBRARY_STUFF void X11SetXwaylandMayGrabKeyboardFlag(QWidget *pWidget); + + /** X11: Gets the X11 display pointer. */ + SHARED_LIBRARY_STUFF struct _XDisplay *X11GetDisplay(); + /** X11: Gets the X11 connection. */ + SHARED_LIBRARY_STUFF struct xcb_connection_t *X11GetConnection(); + /** X11: Gets the X11 root (desktop) window. */ + SHARED_LIBRARY_STUFF uint32_t X11GetAppRootWindow(); +} + +#endif /* !FEQT_INCLUDED_SRC_platform_x11_VBoxUtils_x11_h */ diff --git a/src/VBox/Frontends/VirtualBox/src/platform/x11/XKeyboard-new.cpp b/src/VBox/Frontends/VirtualBox/src/platform/x11/XKeyboard-new.cpp new file mode 100644 index 00000000..59e183bf --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/x11/XKeyboard-new.cpp @@ -0,0 +1,289 @@ +/* $Id: XKeyboard-new.cpp $ */ +/** @file + * VBox Qt GUI - Implementation of Linux-specific keyboard functions. + */ + +/* + * 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 + */ + +/* Define GUI log group. + * This define should go *before* VBox/log.h include: */ +#define LOG_GROUP LOG_GROUP_GUI + +/* Qt includes: */ +#include <QString> +#include <QStringList> + +/* Other VBox includes: */ +#include <VBox/log.h> + +/* GUI includes: */ +#include "XKeyboard.h" + +/* Other VBox includes: */ +#include "VBox/VBoxKeyboard.h" + +/* External includes: */ +#include <X11/XKBlib.h> +#include <X11/keysym.h> + + +/* VBoxKeyboard uses the deprecated XKeycodeToKeysym(3) API, but uses it safely. */ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + +static unsigned gfByLayoutOK = 1; +static unsigned gfByTypeOK = 1; +static unsigned gfByXkbOK = 1; + +/** Prints a key to the release log in the format needed for the Wine layout tables. */ +static void printKey(Display *pDisplay, int cKeys) +{ + bool fWasEscape = false; + + for (int i = 0; i < 2; ++i) + { + int iKeySym = XKeycodeToKeysym(pDisplay, cKeys, i); + + int iValue = iKeySym & 0xff; + if ('\\' == iValue) + { + LogRel(("\\\\")); + } + else if ('"' == iValue) + { + LogRel(("\\\"")); + } + else if ((iValue > 32) && (iValue < 127)) + { + if ( fWasEscape + && ( ((iValue >= '0') && (iValue <= '9')) + || ((iValue >= 'A') && (iValue <= 'F')) + || ((iValue >= 'a') && (iValue <= 'f')))) + { + LogRel(("\"\"")); + } + LogRel(("%c", (char)iValue)); + } + else + { + LogRel(("\\x%x", iValue)); + fWasEscape = true; + } + } +} + +/** Dumps the keyboard layout to the release log. */ +static void dumpLayout(Display *pDisplay) +{ + LogRel(("Your keyboard layout does not appear to be fully supported by\n" + "VirtualBox. If you are experiencing keyboard problems this.\n" + "information may help us to resolve them.\n" + "(Note: please tell us if you are using a custom layout.)\n\n" + "The correct table for your layout is:\n")); + /* First, build up a table of scan-to-key code mappings */ + unsigned scanToKeycode[512] = { 0 }; + int iMinKey, iMaxKey; + XDisplayKeycodes(pDisplay, &iMinKey, &iMaxKey); + for (int i = iMinKey; i < iMaxKey; ++i) + scanToKeycode[X11DRV_KeyEvent(pDisplay, i)] = i; + LogRel(("\"")); + printKey(pDisplay, scanToKeycode[0x29]); /* `~ */ + for (int i = 2; i <= 0xd; ++i) /* 1! - =+ */ + { + LogRel(("\",\"")); + printKey(pDisplay, scanToKeycode[i]); + } + LogRel(("\",\n")); + LogRel(("\"")); + printKey(pDisplay, scanToKeycode[0x10]); /* qQ */ + for (int i = 0x11; i <= 0x1b; ++i) /* wW - ]} */ + { + LogRel(("\",\"")); + printKey(pDisplay, scanToKeycode[i]); + } + LogRel(("\",\n")); + LogRel(("\"")); + printKey(pDisplay, scanToKeycode[0x1e]); /* aA */ + for (int i = 0x1f; i <= 0x28; ++i) /* sS - '" */ + { + LogRel(("\",\"")); + printKey(pDisplay, scanToKeycode[i]); + } + LogRel(("\",\"")); + printKey(pDisplay, scanToKeycode[0x2b]); /* \| */ + LogRel(("\",\n")); + LogRel(("\"")); + printKey(pDisplay, scanToKeycode[0x2c]); /* zZ */ + for (int i = 0x2d; i <= 0x35; ++i) /* xX - /? */ + { + LogRel(("\",\"")); + printKey(pDisplay, scanToKeycode[i]); + } + LogRel(("\",\"")); + printKey(pDisplay, scanToKeycode[0x56]); /* The 102nd key */ + LogRel(("\",\"")); + printKey(pDisplay, scanToKeycode[0x73]); /* The Brazilian key */ + LogRel(("\",\"")); + printKey(pDisplay, scanToKeycode[0x7d]); /* The Yen key */ + LogRel(("\"\n\n")); +} + +/** Dumps the keyboard type tables to the release log. */ +static void dumpType(Display *pDisplay) +{ + LogRel(("Your keyboard type does not appear to be known to VirtualBox. If\n" + "you are experiencing keyboard problems this information may help us\n" + "to resolve them. Please also provide information about what type\n" + "of keyboard you have and whether you are using a remote X server or\n" + "something similar.\n\n" + "The tables for your keyboard are:\n")); + for (unsigned i = 0; i < 256; ++i) + { + LogRel(("0x%x", X11DRV_KeyEvent(pDisplay, i))); + if (i < 255) + LogRel((", ")); + if (15 == (i % 16)) + LogRel(("\n")); + } + LogRel(("and\n")); + LogRel(("NULL, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x,\n0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\n", + XKeysymToKeycode(pDisplay, XK_Control_L), + XKeysymToKeycode(pDisplay, XK_Shift_L), + XKeysymToKeycode(pDisplay, XK_Caps_Lock), + XKeysymToKeycode(pDisplay, XK_Tab), + XKeysymToKeycode(pDisplay, XK_Escape), + XKeysymToKeycode(pDisplay, XK_Return), + XKeysymToKeycode(pDisplay, XK_Up), + XKeysymToKeycode(pDisplay, XK_Down), + XKeysymToKeycode(pDisplay, XK_Left), + XKeysymToKeycode(pDisplay, XK_Right), + XKeysymToKeycode(pDisplay, XK_F1), + XKeysymToKeycode(pDisplay, XK_F2), + XKeysymToKeycode(pDisplay, XK_F3), + XKeysymToKeycode(pDisplay, XK_F4), + XKeysymToKeycode(pDisplay, XK_F5), + XKeysymToKeycode(pDisplay, XK_F6), + XKeysymToKeycode(pDisplay, XK_F7), + XKeysymToKeycode(pDisplay, XK_F8))); +} + +/** Builds a table mapping the X server's scan codes to PC keyboard scan codes. + * The logic of the function is that while the X server may be using a different + * set of scan codes (if for example it is running on a non-PC machine), the + * keyboard layout should be similar to a PC layout. So we look at the symbols + * attached to each key on the X server, find the PC layout which is closest to + * it and remember the mappings. */ +bool initXKeyboard(Display *pDisplay, int (*remapScancodes)[2]) +{ + X11DRV_InitKeyboard(pDisplay, &gfByLayoutOK, &gfByTypeOK, &gfByXkbOK, remapScancodes); + + /* It will almost always work to some extent.. */ + return true; +} + +void initMappedX11Keyboard(Display *pDisplay, const QString &remapScancodes) +{ + /* Initialize X11 keyboard including the remapping specified in the + * global property GUI/RemapScancodes. This property is a string of + * comma-separated x=y pairs, where x is the X11 keycode and y is the + * keyboard scancode that is emitted when the key attached to the X11 + * keycode is pressed. */ + + int (*scancodes)[2] = NULL; + int (*scancodesTail)[2] = NULL; + + if (remapScancodes != QString()) + { +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QStringList tuples = remapScancodes.split(",", Qt::SkipEmptyParts); +#else + QStringList tuples = remapScancodes.split(",", QString::SkipEmptyParts); +#endif + scancodes = scancodesTail = new int [tuples.size()+1][2]; + for (int i = 0; i < tuples.size(); ++i) + { + QStringList keyc2scan = tuples.at(i).split("="); + (*scancodesTail)[0] = keyc2scan.at(0).toUInt(); + (*scancodesTail)[1] = keyc2scan.at(1).toUInt(); + /* Do not advance on (ignore) identity mappings as this + * is the stop signal to initXKeyboard and friends: */ + if ((*scancodesTail)[0] != (*scancodesTail)[1]) + ++scancodesTail; + } + (*scancodesTail)[0] = (*scancodesTail)[1] = 0; + } + + /* Initialize the X keyboard subsystem: */ + initXKeyboard(pDisplay, scancodes); + + if (scancodes) + delete scancodes; +} + +unsigned handleXKeyEvent(Display *pDisplay, unsigned int iDetail) +{ + /* Call the WINE event handler: */ + unsigned iKey = X11DRV_KeyEvent(pDisplay, iDetail); + LogRel3(("VBoxKeyboard: converting keycode %d to scancode %s0x%x\n", + iDetail, iKey > 0x100 ? "0xe0 " : "", iKey & 0xff)); + return iKey; +} + +void doXKeyboardLogging(Display *pDisplay) +{ + if (((1 == gfByTypeOK) || (1 == gfByXkbOK)) && (gfByLayoutOK != 1)) + dumpLayout(pDisplay); + if (((1 == gfByLayoutOK) || (1 == gfByXkbOK)) && (gfByTypeOK != 1)) + dumpType(pDisplay); + if ((gfByLayoutOK != 1) && (gfByTypeOK != 1) && (gfByXkbOK != 1)) + { + LogRel(("Failed to recognize the keyboard mapping or to guess it based on\n" + "the keyboard layout. It is very likely that some keys will not\n" + "work correctly in the guest. If this is the case, please submit\n" + "a bug report, giving us information about your keyboard type,\n" + "its layout and other relevant information such as whether you\n" + "are using a remote X server or something similar. \n")); + unsigned *keyc2scan = X11DRV_getKeyc2scan(); + + LogRel(("The keycode-to-scancode table is: %d=%d",0,keyc2scan[0])); + for (int i = 1; i < 256; i++) + LogRel((",%d=%d",i,keyc2scan[i])); + LogRel(("\n")); + } + LogRel(("X Server details: vendor: %s, release: %d, protocol version: %d.%d, display string: %s\n", + ServerVendor(pDisplay), VendorRelease(pDisplay), ProtocolVersion(pDisplay), + ProtocolRevision(pDisplay), DisplayString(pDisplay))); + LogRel(("Using %s for keycode to scan code conversion\n", + gfByXkbOK ? "XKB" + : gfByTypeOK ? "known keycode mapping" + : "host keyboard layout detection")); +} + +unsigned long wrapXkbKeycodeToKeysym(Display *pDisplay, unsigned char cCode, + unsigned int cGroup, unsigned int cIndex) +{ + KeySym cSym = XkbKeycodeToKeysym(pDisplay, cCode, cGroup, cIndex); + if (cSym != NoSymbol) + return cSym; + return XKeycodeToKeysym(pDisplay, cCode, cGroup * 2 + cIndex % 2); +} + diff --git a/src/VBox/Frontends/VirtualBox/src/platform/x11/XKeyboard.h b/src/VBox/Frontends/VirtualBox/src/platform/x11/XKeyboard.h new file mode 100644 index 00000000..9e394f17 --- /dev/null +++ b/src/VBox/Frontends/VirtualBox/src/platform/x11/XKeyboard.h @@ -0,0 +1,55 @@ +/* $Id: XKeyboard.h $ */ +/** @file + * VBox Qt GUI - Declarations of Linux-specific keyboard functions. + */ + +/* + * 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 FEQT_INCLUDED_SRC_platform_x11_XKeyboard_h +#define FEQT_INCLUDED_SRC_platform_x11_XKeyboard_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +/* GUI includes: */ +#include "UILibraryDefs.h" + +/* Forward declarations: */ +class QString; +typedef struct _XDisplay Display; + +/** Initializes the X keyboard subsystem. */ +SHARED_LIBRARY_STUFF void initMappedX11Keyboard(Display *pDisplay, const QString &remapScancodes); + +/** Handles native XKey events. */ +SHARED_LIBRARY_STUFF unsigned handleXKeyEvent(Display *pDisplay, unsigned int iDetail); + +/** Handles log requests from initXKeyboard after release logging is started. */ +SHARED_LIBRARY_STUFF void doXKeyboardLogging(Display *pDisplay); + +/** Wraps for the XkbKeycodeToKeysym(3) API which falls back to the deprecated XKeycodeToKeysym(3) if it is unavailable. */ +SHARED_LIBRARY_STUFF unsigned long wrapXkbKeycodeToKeysym(Display *pDisplay, unsigned char cCode, + unsigned int cGroup, unsigned int cIndex); + +#endif /* !FEQT_INCLUDED_SRC_platform_x11_XKeyboard_h */ + |